sendfile()の挙動を観察する。
sendfileについての備忘など。
1. sendfile()
NAME sendfile - transfer data between file descriptors SYNOPSIS #include <sys/sendfile.h> ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); DESCRIPTION sendfile() copies data between one file descriptor and another. Because this copying is done within the kernel, sendfile() is more efficient than the combi- nation of read(2) and write(2), which would require transferring data to and from user space.
fd間のストリームでデータ共有を行うシステムコール。
read(), write()等のバイト情報をユーザランドのバッファに吐き出す処理が発生しない。
そのため、メモリキャッシュ及びソケットバッファのカーネル空間に閉じた処理での完結が可能。
2. 何が起こるのか。
このシステムコールを呼ぶとどんな動きをするのか。
nginxのsendfileディレクティブを使って、httpリクエスト発生時に、fd周り、メモリ、バッファ読み込み、吐き出し周りの挙動を観察してみる。
設定自体はsendfileディレクティブを有効化するのみ。Module ngx_http_core_module
通常、おまじない的にtcp_nopushも同時に有効化するが、 転送されるファイルの内容
と HTTPヘッダ情報
のパケットの扱いを観察するため、ここではコメントアウトしておく。
http { server { sendfile on; #tcp_nopush on; ~~ ~~ ~~ } }
2-1. sendfile無効の場合
{{EPOLLIN, {u32=2655952912, u64=140396546924560}}}, 512, -1) = 1 accept4(6, {sa_family=AF_INET, sin_port=htons(50376), sin_addr=inet_addr("{{ source ip }}")}, [16], SOCK_NONBLOCK) = 3 epoll_ctl(8, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLET, {u32=2655953296, u64=140396546924944}}) = 0 epoll_wait(8, {{EPOLLIN, {u32=2655953296, u64=140396546924944}}}, 512, 60000) = 1 recvfrom(3, "GET / HTTP/1.1\r\nHost: xxx.xxx.xxx"..., 1024, 0, NULL, NULL) = 396 stat("/usr/shere/nginx/html/index.html", {st_mode=S_IFREG|0664, st_size=55, ...}) = 0 open("/usr/shere/nginx/html/index.html", O_RDONLY|O_NONBLOCK) = 9 fstat(9, {st_mode=S_IFREG|0664, st_size=55, ...}) = 0 pread(9, "sendfile test"..., 55, 0) = 55 writev(3, [{"HTTP/1.1 200 OK\r\nServer: nginx/1"..., 244}, {"sendfile test"..., 55}], 2) = 299 write(4, "{{ source ip }} - - [03/Nov/2018:06"..., 197) = 197 close(9) = 0 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0 recvfrom(3, 0x1468830, 1024, 0, 0, 0) = -1 EAGAIN (Resource temporarily unavailable) epoll_wait(8,
2-2. sendfile有効の場合
{{EPOLLIN, {u32=1826033680, u64=139940450471952}}}, 512, -1) = 1 accept4(6, {sa_family=AF_INET, sin_port=htons(49775), sin_addr=inet_addr("{{ source ip }}")}, [16], SOCK_NONBLOCK) = 3 epoll_ctl(8, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLET, {u32=1826034064, u64=139940450472336}}) = 0 epoll_wait(8, {{EPOLLIN, {u32=1826034064, u64=139940450472336}}}, 512, 60000) = 1 recvfrom(3, "GET / HTTP/1.1\r\nHost: xxx.xxx.xxx"..., 1024, 0, NULL, NULL) = 396 stat("/usr/shere/nginx/html/index.html", {st_mode=S_IFREG|0664, st_size=55, ...}) = 0 open("/usr/shere/nginx/html/index.html", O_RDONLY|O_NONBLOCK) = 9 fstat(9, {st_mode=S_IFREG|0664, st_size=55, ...}) = 0 setsockopt(3, SOL_TCP, TCP_CORK, [1], 4) = 0 writev(3, [{"HTTP/1.1 200 OK\r\nServer: nginx/1"..., 244}], 1) = 244 sendfile(3, 9, [0], 55) = 55 write(4, "{{ source ip }} - - [03/Nov/2018:05"..., 197) = 197 close(9) = 0 setsockopt(3, SOL_TCP, TCP_CORK, [0], 4) = 0 recvfrom(3, 0x1071830, 1024, 0, 0, 0) = -1 EAGAIN (Resource temporarily unavailable) epoll_wait(8,
pread()、つまりfdからの読み込み -> バッファ吐き出しが実行されていない。 fd間での通信のみで、ユーザ空間でのデータの受け渡しは発生していないように見える。
3. パケットを観察してみる。
パケットのやり取りに差異はあるか、観察してみる。
3-1. sendfile無効の場合
1 0.000000000 {{ source ip }} -> {{ dest ip }} TCP 66 52140 -> 80 [FIN, ACK] Seq=1 Ack=1 Win=4108 Len=0 TSval=1758067629 TSecr=2399948 2 0.000181005 {{ dest ip }} -> {{ source ip }} TCP 66 80 -> 52140 [FIN, ACK] Seq=1 Ack=2 Win=235 Len=0 TSval=2401820 TSecr=1758067629 3 0.000684120 {{ source ip }} -> {{ dest ip }} TCP 66 52140 -> 80 [ACK] Seq=2 Ack=2 Win=4108 Len=0 TSval=1758067629 TSecr=2401820 4 4.198461822 {{ source ip }} -> {{ dest ip }} TCP 78 52143 -> 80 [SYN, ECN, CWR] Seq=0 Win=65535 Len=0 MSS=1460 WS=32 TSval=1758071810 TSecr=0 SACK_PERM=1 5 4.198607450 {{ dest ip }} -> {{ source ip }} TCP 74 80 -> 52143 [SYN, ACK, ECN] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=2402869 TSecr=1758071810 WS=128 6 4.198970205 {{ source ip }} -> {{ dest ip }} TCP 66 52143 -> 80 [ACK] Seq=1 Ack=1 Win=131744 Len=0 TSval=1758071810 TSecr=2402869 7 4.199186363 {{ source ip }} -> {{ dest ip }} HTTP 462 GET / HTTP/1.1 8 4.199219557 {{ dest ip }} -> {{ source ip }} TCP 66 80 -> 52143 [ACK] Seq=1 Ack=397 Win=30080 Len=0 TSval=2402870 TSecr=1758071810 9 4.199341119 {{ dest ip }} -> {{ source ip }} HTTP 365 HTTP/1.1 200 OK (text/html) 10 4.199707812 {{ source ip }} -> {{ dest ip }} TCP 66 52143 -> 80 [ACK] Seq=397 Ack=300 Win=131456 Len=0 TSval=1758071811 TSecr=2402870
3-2. sendfile有効の場合
1 0.000000000 {{ source ip }} -> {{ dest ip }} TCP 78 50661 -> 80 [SYN, ECN, CWR] Seq=0 Win=65535 Len=0 MSS=1460 WS=32 TSval=1756061495 TSecr=0 SACK_PERM=1 2 0.000035493 {{ dest ip }} -> {{ source ip }} TCP 74 80 -> 50661 [SYN, ACK, ECN] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=1901979 TSecr=1756061495 WS=128 3 0.000814846 {{ source ip }} -> {{ dest ip }} TCP 66 50661 -> 80 [ACK] Seq=1 Ack=1 Win=131744 Len=0 TSval=1756061496 TSecr=1901979 4 0.001317144 {{ source ip }} -> {{ dest ip }} HTTP 462 GET / HTTP/1.1 5 0.001343425 {{ dest ip }} -> {{ source ip }} TCP 66 80 -> 50661 [ACK] Seq=1 Ack=397 Win=30080 Len=0 TSval=1901979 TSecr=1756061496 6 0.001587658 {{ dest ip }} -> {{ source ip }} TCP 310 HTTP/1.1 200 OK [TCP segment of a reassembled PDU] 7 0.001735116 {{ dest ip }} -> {{ source ip }} HTTP 121 HTTP/1.1 200 OK (text/html) 8 0.002948842 {{ source ip }} -> {{ dest ip }} TCP 66 50661 -> 80 [ACK] Seq=397 Ack=245 Win=131520 Len=0 TSval=1756061497 TSecr=1901979 9 0.003177666 {{ source ip }} -> {{ dest ip }} TCP 66 50661 -> 80 [ACK] Seq=397 Ack=300 Win=131456 Len=0 TSval=1756061497 TSecr=1901979
リクエストの受信から返送まで問題なくできている。
(sendfile有効化時は、ヘッダ情報とファイルの内容が1パケットで送りきれていない。
今回の実験では、前述したとおりtcp_nopushを有効化していないため、ヘッダとファイルの内容自体が別パケットで送られている。)
4. 図
図にしてみる。
4-1. sendfile無効化
- 接続待ち(epoll_xxx)
- ソケットの受信データを取得(recvfrom)
- ファイル確認(stat)
- ファイルサイズ確認(fstat)
- ヘッダ情報とデータを返送(writev)
- アクセスログに追記(write)
- ファイルクローズ(close)
4-2. sendfile有効化
- 接続待ち(epoll_xxx)
- ソケットの受信データを取得(recvfrom)
- ファイル確認(stat)
- ファイルサイズ確認(fstat)
- ヘッダ情報を返送(writev)
- データの読み込みと送信(sendfile)
- アクセスログに追記(write)
- ファイルクローズ(close)
5. 速度比較
の3パターンでレスポンス完了までの時間を測定してみる。
基本tcp_nopushとのセットというのはこういうことなのかな。
転送するファイルの内容次第というのもあると思うが、ファイルディスクリプタ間での通信で完了できるとはいえ、
パケット分割の負荷については、改めて考慮しておく。