概要
netstatの実行でたまに見るSendQとかRecvQの値の決定はどこで行っているかを確認した際のメモ
Send-Q/Recv-Qとは
Recv-Q このソケットに接続されたユーザープログラムに渡されなかったデータのバイト数。 Send-Q リモートホストに受け入れられなかったデータのバイト数。
manから抜粋。それぞれカーネルにデータはるけどアプリケーションで処理されていないデータ量を表している模様。send(2)の成功は相手への通信の成功じゃないみたいなイメージを持ってると意味はそれとなく理解しやすい。
Send-Q/Recv-Qの値はどこで決まるのか
答えとしてはソケットごとに設定するか設定せずにカーネルパラメータの値がそのまま入るパターンがある。setsockopt(2) の SO_RCVBUF, SO_SNDBUFなんかで設定すればその値が入るし設定しなければnet.core.(r|w)mem_defaultの値が設定される。
アプリケーションが適切にキューからデータを処理していれば問題になることはなくて高負荷状態でアプリケーションがいっぱいいっぱいのケースでこの値がmaxを超えるとパケットロストが発生する。
この値をいじることでデータをOSが保存しておく量が増えるがそもそもアプリケーションにボトルネックがあるのが明白ならこの値を買えるのではなくアプリケーションのチューニングが先になるはず。(インメモリなデータを大量にためたところでスケールアウトしづらくなるだけなので)
Send-Qについての注意
Send-Qの問題は、受信者が原因ではなく、送信者と受信者の間のルーティングの問題が原因である可能性がある。Send-Qに入ったが相手側が処理したAckを返してないのではなくAck自体がネットワーク設定によって帰ってないパターンの想定が必要。
また死ぬほど遅いクライアントが単純にデータのダウンロードが遅くAckを返せていないケースもある。(お昼の私のスマホなんかまさにそうかも。。。)。これ対策はアプリケーション側でするべきことでApacheなんかではSendTimeoutなんかいうパラーメータがあるらしい。
socketを設定するpythonサンプル
setsockoptはpythonでも触れるらしいことを知ったのでサンプル。(systemcallなのでcだけかと思ったがそんなことはないらしい。)
import socket | |
def default_buf_size(): | |
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
s_bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF) | |
r_bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) | |
print(f"Default_send: {s_bufsize}") | |
print(f"Default_revc: {r_bufsize}") | |
def modify_buf_size(send_buf_size=1024, recv_buf_size=1024): | |
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
# バッファサイズの変更 | |
sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, send_buf_size) | |
sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, recv_buf_size) | |
bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF) | |
print(send_buf_size, ": ", bufsize) | |
bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) | |
print(recv_buf_size, ": ", bufsize) | |
if __name__ == "__main__": | |
default_buf_size() | |
modify_buf_size() | |
modify_buf_size(send_buf_size=4096, recv_buf_size=4096) | |
modify_buf_size(send_buf_size=1, recv_buf_size=1) | |
modify_buf_size(send_buf_size=1524280422, recv_buf_size=33554433) |
straceすれば当然だがsetsockoptをpythonから読んでいることを確認できる。(この程度のサンプルなら正直cで直接書いても良さそうな気はするけど。)
-bash-4.2# grep setsockopt setsockopt setsockopt(3, SOL_SOCKET, SO_SNDBUF, [1024], 4) = 0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1024], 4) = 0 setsockopt(3, SOL_SOCKET, SO_SNDBUF, [4096], 4) = 0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [4096], 4) = 0 setsockopt(3, SOL_SOCKET, SO_SNDBUF, [1], 4) = 0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1], 4) = 0 setsockopt(3, SOL_SOCKET, SO_SNDBUF, [1524280422], 4) = 0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [33554433], 4) = 0