Squidがアクセス集中時に遅くなったときに行った対策
始業時や昼休みなど、アクセスが集中する時間帯になるとLinuxのSquidプロキシサーバのレスポンスが悪くなって、「Webアクセスが遅い!!」とあちこちから苦情を言われてしまっていたのですが、次のような対策を行ったところ、無事解決できたっぽいのでメモることにします。
現状の確認
CPUやメモリの使用率を確認
まずはお決まりのtopコマンドをアクセス集中時に実行(topコマンド自体が高負荷時にはサーバに更に追い打ちをかけそうですが、原因の切り分けのためにはやむを得ないと思います)。やはりsquidプロセスが一番CPUもメモリも喰っていましたが、load averageは低く、CPUやメモリに負荷がかかっているようには見えない状況でした。
ネットワークの状況を確認
次に同じくアクセス集中時にnetstatコマンドを実行。すると状態がESTABLISHEDのコネクションよりもTIME_WAITやSYN_RECVのものが多く、ネットワークの待ち行列がボトルネックになっている状況が見えてきました。
ネットワーク周りのカーネルパラメータのチューニング
そこで、カーネル周りについて、次のようなチューニングを実施しました。基本線は、OSで受け付けられるコネクションを増やし、不要になったクライアントのコネクションを早く次のクライアントのコネクションに回せるようにします。
なお、全て /etc/sysctl.conf に追記し、sysctl -pで設定を反映させています。
tcp_fin_timeout
ソケットを強制的にクローズする前に、 最後の FIN パケットを待つ時間を秒単位で指定する。 Man page of TCP
tcp_fin_timeoutを短くすれば、通信が終わって不要になったコネクションを解放する時間も早くなるのでは?と考え、30秒に設定(元は60秒でした)。
net.ipv4.tcp_fin_timeout = 30
tcp_max_syn_backlog
接続してきているクライアントから ack を受信していない状態の接続リクエストをキューに置ける最大数。 この数値を越えると、カーネルはリクエストを捨て始める。
この値が少ないと、アクセス集中時に接続してきたクライアントのコネクションを切ってしまう→クライアントのリクエストはタイムアウト、となっていたと思われるので、元の設定(1024でした)よりも大きめに設定。
net.ipv4.tcp_max_syn_backlog = 4096
somaxconn
TCP ソケットでの backlog 引き数の振る舞いは Linux 2.2 で変更された。 現在ではこの引き数は、 受け付けられるのを待っている、 完全に 確立されたソケットのキューの長さを指定する。 以前は不完全な接続要求の数であったが、これを置き換えた。 不完全なソケットのキューの最大長は /proc/sys/net/ipv4/tcp_max_syn_backlog を用いて設定できる。 syncookie が有効になっている場合、 論理的な最大長は存在せず、この設定は無視される。
backlog 引き数が /proc/sys/net/core/somaxconn の値よりも大きければ、 backlog の値は暗黙のうちにこの値に切り詰められる。 このファイルのデフォルト値は 128 である。 バージョン 2.4.5 以前のカーネルでは、この上限値は コード埋め込みの固定値 SOMAXCONN であり、その値は 128 であった。
tcp_max_syn_backlogの値を上げても、somaxconnの値が低ければこちらに引っ張られてしまうとのこと(参考→DSAS開発者の部屋:高負荷サイトのボトルネックを見つけるには)。そこでsomaxconnもtcp_max_syn_backlogと同じ値に設定。
net.core.somaxconn = 4096
netdev_max_backlog
Sets the maximum number of packets allowed to queue when a particular interface receives packets faster than the kernel can process them. The default value for this file is 300.
こちらもバックログ関係の設定。同じく4096に設定。
net.core.netdev_max_backlog = 4096
tcp_max_tw_buckets
システムが許容する TIME_WAIT 状態にあるソケットの最大数。 この制限が存在するのは、 単純な使用不能 (denial-of-service) 攻撃を防ぐために過ぎない。 デフォルト値は NR_FILE*2 で、システムのメモリに応じて調整される。 この数値を越えると、そのようなソケットはクローズされ、警告が表示される。
TIME_WAITで待てる数を増やすため、元よりも大きな値に設定。
net.ipv4.tcp_max_tw_buckets = 360000
tcp_tw_reuse
プロトコルの面から見て問題ない場合に新規コネクションに TIME_WAIT 状態のソケットを再利用することを許可する。
待ちコネクションの回転率を上げるために有効に設定。
net.ipv4.tcp_tw_reuse = 1
カーネルチューニングの効果
再びアクセス集中時にnetstatコマンドを実行。すると状態がTIME_WAITやSYN_RECVのコネクションが格段に減少しました。チューニングの効果は確かにありました。これでSquidのパフォーマンスも上がるはず……が、ほとんどWebアクセスのレスポンスは変わりませんでした。
再び現状の調査
OSの資源は枯渇していない、ネットワークのボトルネックも解消した、それでもSquidが遅いとなると、Squid自身の問題を疑うしかありません。access.logを眺めても異常は見つからず。悩みながらふとcache.logを見てみると、Your cache is running out of filedescriptorsという見慣れないログが。grepすると出るわ出るわ……それもちょうどアクセスが集中する時間帯にばかり多発しているようでした。これで原因はSquidキャッシュのオープンがファイルディスクリプタの上限を超えてしまっていることに絞られました。
Squidのファイルディスクリプタの上限値を変更
squidのコンフィグ変更
/etc/squid/squid.conf のmax_filedescの値を4096に増やし、コメントアウトを解除します(元の設定にない場合は追記)。
$ sudo sed -i -e 's/^# max_filedesc 1024/max_filedesc 4096/' /etc/squid/squid.conf
もしかしたら次項のulimitの設定だけでいいのかも。ドキュメントにはulimitの設定を引き継ぐならこの設定を消しなさいと書いてあるように読めます。
Reduce the maximum number of filedescriptors supported below the usual operating system defaults.
Remove from squid.conf to inherit the current ulimit setting.
Squid実行ユーザーの上限値変更
rootや一般ユーザーとは違い、デーモンを実行するユーザー(squidやapacheなど)は、/etc/security/limits.conf に設定してもulimitの設定は反映されないそうです(知らなかった)。
ファイルディスクリプタ数の上限変更とlimits.confの罠 (ゆめ技:ゆめみスタッフブログ)に詳しく解説されていました。以下引用します。
daemon系プロセスのファイルディスクリプタ数上限を設定する際、/etc/security/limits.conf は使えません。状況によっては一見設定されたように見えますが、大きな落とし穴にはまることになります。
面倒ですが、必要なプロセス毎にulimitを用いて適切に設定しましょう。
そこで、squidの起動スクリプト /etc/rc.d/init.d/squid に次を追記します。これにより、Squidの実行ユーザー(squid)がスクリプト実行した際にulimitの設定が行われます。
ulimit -HSn 4096
追記後はSquidを再起動すれば設定が反映されます。
結果
無事にアクセスが集中する時間帯でもファイルディスクリプタのエラーは起きなくなったみたいです。Webアクセスのレスポンスも一気に改善しました。
反省点は、切り分けの順番として先にSquidの全部のログを調べるべきだったなというところです。いずれにしてもネットワーク周りのチューニングも必要だったでしょうけど。