#きっかけ
この記事を投稿しようと思ったきっかけですが、nginx+gnicorn+Djangoで開発をする機会がありまして、開発も終盤にさしかかりパフォーマンスチェックをしている際に問題が起きました。
abコマンドでベンチマークを取っていたんですが、各設定は
#nginx
worker_processes 2;
events {
worker_connections 1024;
}
#gunicorn
bind = 'unix:/tmp/gunicorn.sock'
backlog = 2048
workers = 2
worker_class = 'sync'
abはこんな感じです
ab -n 5000 -c 200 http://domain.com/
これでベンチを取っていると、abでLengthエラー発生!!
原因を探ってみると、nginxのログに
connect() to unix:/tmp/gunicorn.sock failed (11: Resource temporarily unavailable)
問題のこれが発生。
connectエラーですね。
最初に疑ったのがファイルオープンの上限。
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7812
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 1024
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
これのopen filesですね。
ただ、よくよく考えると
そもそもsocketをコールして正常にソケットディスクリプタができているなら、これはないなということ。
今考えると少し恥ずかしいです。
次にバックログ。
#man listen
LISTEN(2) Linux Programmer’s Manual LISTEN(2)
名前
listen - ソケット(socket)上の接続を待つ
書式
#include <sys/types.h> /* 「注意」参照 */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
説明
listen() は sockfd が参照するソケットを接続待ちソケット (passive socket) として印を
つける。接続待ちソケットとは、 accept(2) を使って到着した接続要求を受け付けるのに使
用されるソケットである。
sockfd 引き数は、 SOCK_STREAM 型か SOCK_SEQPACKET 型のソケットを参照するファイルデ
ィスクリプタである。
backlog 引き数は、 sockfd についての保留中の接続のキューの最大長を指定する。キュ ー
が いっぱいの状態で接続要求が到着すると、クライアントは ECONNREFUSED というエラーを
受け取る。下位層のプロトコルが再送信をサポートしていれば、要求は無視され、これ以 降
の接続要求の再送信が成功するかもしれない。
manコマンドです。
これのバックログが少ないのかなと。
ただ、gunicornのconfigで2048と設定しているのになぜ?という感じでした。
同じabコマンドでgunicornをlocalhostで起動したベンチではこのエラーが発生しませんでした。
#原因
ここまで書くと気づいている方が多数だと思いますが、
$ sysctl net.core.somaxconn
net.core.somaxconn = 128
これです。
ここがデフォルトのままでした。
かなり勉強不足を露呈しているようで恥ずかしいですが、この値でbacklogが上書きされるみたいです。
ここを変更すればエラーが出ることなくベンチとれました。
この値の変更方法は、置いといて...
#とりあえずシステムコールの中身を見てみる
これを教訓にソケット周りのシステムコールを見ることに決めました。
ちなみにソースは、今更?linux kernel 2.6です。
ここからはソースをのせるだけになりますが、
実際にデフォルト値を設定しているのは、
static __net_init int sysctl_core_net_init(struct net *net)
{
struct ctl_table *tbl;
net->core.sysctl_somaxconn = SOMAXCONN;
tbl = netns_core_table;
if (net != &init_net) {
tbl = kmemdup(tbl, sizeof(netns_core_table), GFP_KERNEL);
if (tbl == NULL)
goto err_dup;
tbl[0].data = &net->core.sysctl_somaxconn;
}
net->core.sysctl_hdr = register_net_sysctl_table(net,
net_core_path, tbl);
if (net->core.sysctl_hdr == NULL)
goto err_reg;
return 0;
err_reg:
if (tbl != netns_core_table)
kfree(tbl);
err_dup:
return -ENOMEM;
}
#socket.h
/* Maximum queue length specifiable by listen. */
#define SOMAXCONN 128
この辺ですかね。
思いっきし128って書いてますね。
そして肝心の上書き
SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
struct socket *sock;
int err, fput_needed;
int somaxconn;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
if ((unsigned)backlog > somaxconn)
backlog = somaxconn;
err = security_socket_listen(sock, backlog);
if (!err)
err = sock->ops->listen(sock, backlog);
fput_light(sock->file, fput_needed);
}
return err;
}
if ((unsigned)backlog > somaxconn)
backlog = somaxconn;
ここで上書きされてますね。
色々勉強になりました。
もう少し掘り下げてみたいのですが、ここまでにしておきます。
ちなみに
sock->ops->listen(sock, backlog);
ここで各AF_系のlisten処理を行います。
例えば
//AF_INET ipv4
int inet_listen(struct socket *sock, int backlog);
//AF_UNIX unix domain
static int unix_listen(struct socket *sock, int backlog);
こんな感じだと思います。
この辺もっと詳しく掘り下げてみたいです。
ざっと調べただけなので間違っている箇所もあると思います。
何か気づいたら指摘してください。