hideden.hatenablog.com

はてなぶろぐー。URLなげー。

isuconお遊びチーム(事前社内β組)の設定あれこれ

ISUCONに行ってきました。社内での事前βテストに参加して問題を知っていたので出場はせず。社内β参加を持ちかけられたときは、正直「めんどくせーなw」が素直な感想だったんですが、実際にやってみるとスコアがリアルタイムにわかる&ちょっとずつ自分のスコアが上がっていくってのは楽しくて、わりと本気でチューニングしてしまいました。

さて、本戦でも14時頃からお遊び用としてサーバー一式が解放されたので、大人げも無くそこで112500req/minをたたき出して参加者のやる気を削いだ(・・と懇親会で言われました。色々すいません!)構成について。

今回の課題の場合のチューニングの戦略は何パターンかあると思いますが、普段livedoorBlogでも取っている

  • frontでなるべくcache
  • 更新時はappへ
  • DBへアクセスが行ったら負け

の戦略でいきました。他であまり見かけなかったところとして、cacheの効率を上げるためにfrontのnginxではSSIを使ってます。今回の課題の肝は全ページのサイドバーにあたる「recent_commented_articles」ですが、ページ内にこの部分をレンダリング済みでページ全体をcacheするとコメントpostのたびに全ページのcacheを破棄せねばならず、効率がぐっと落ちます。テンプレートの該当部分を

<!--#include virtual="/recent_commented_articles" -->

とすることでこの部分を分離しています。これにより、コメントpost時のcache更新影響範囲を

  • comment post先articleページ
  • recent_commented_articles部分

だけに限定することが出来ます。frontに使っているサーバーは違うものの、SSIでのページの結合はlivedoorBlogのサイドバー部分を含めたいくつかの箇所で行っています。(本来ならjsでもいいはずなんですが、SEO云々とか色々言われるのでこういう事になってます)

今回のBenchmarkではpostにかかる時間は点数に影響が無いので、article post/comment post時にDB記録とともに更新がある範囲HTML(上記の2ページ分)をレンダリングし、応答を返す前にreverse proxyサーバーに同居しているmemcachedへsetしています。

その他のページに関しては、cache missしたものに関してはオンザフライで生成し、cacheにsetしつつ返しています。レギュレーション的にはサーバー再起動後に1分間の猶予があったので、そこでcacheをウォームアップするものを書けばもっと高速化も可能かもしれませんが、時間が足りなかったのでやっていません。

これにより、app/DBの負荷はぐっと下がってしまうのでapp/DBをチューニングしてもあまり効果は見込めないのですが、最初それに気付くまでに時間がかかり、序盤はappの最適化に時間を費やしてしまいました。今回の課題でPerlではこれのためだけに作られたKossyという簡単なWAFが使われています。これが結構他でも使えそうなくらいよく出来ているんですが、その汎用性の高さのせいであんまり速くないと考え、SCGIというFastCGIのもうちょっとシンプル版のようなものを使ったものに書き直しました。

SCGIを採用した理由としては

  • nginxの公式moduleにngx_http_scgiがあった
  • 使ったことが無かったので使ってみたかったw
  • perlFastCGIは過去(といっても5年以上前)にいい思い出が無かった

こんなところです。他と比較する時間までは無かったので、PSGIと比べてどれくらい差があるのかは不明ですが、CPANモジュールも2006年に更新されたっきりのようなので、、、仕事で使うには色々アレかもしれません。結果的にはこのアプリのrewriteはほとんどパフォーマンスには影響を与えませんでした。社内ircでKossyにいちゃもんつけてすいませんでした!


その他のチューニング項目は

  • mysqlの良くある設定変更
    • innodb_buffer_pool_size系のメモリ設定を増やす
    • skip-name-resolve
    • innodb_flush_log_at_trx_commit = 0
  • 各サーバーのkernelの設定値変更
    • echo 1000000 > /proc/sys/fs/file-max
    • echo 1024 65000 > /proc/sys/net/ipv4/ip_local_port_range
    • echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
    • echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle

くらいです。社内βは個人戦(っというか誰も手伝ってくれなかっただけ)なのでこの辺に割く時間はあまりなく、mysqlのテーブル構造やindex、アプリからのクエリに関してはいじれて居ません。



nginxの設定は汚いんですが以下に。

worker_processes  4;

events {
    worker_connections  10000;
}

http {
    include       mime.types;
    default_type  text/html;
    sendfile        on;
    keepalive_timeout  1;

    upstream scgi_app {
        server xxx.xxx.xxx.xxx:20000;
        server xxx.xxx.xxx.yyy:20000;
        (略)
        keepalive 1000000;
    }

    upstream memcached {
        server 127.0.0.1:11211;
        keepalive 1000000;
    }

    server {
        listen       80;
        server_name  localhost;

        location /images {
            alias /home/isucon/isucon/webapp/staticfiles/images;
        }
        location /css {
            alias /home/isucon/isucon/webapp/staticfiles/css;
        }
        location /js {
            alias /home/isucon/isucon/webapp/staticfiles/js;
        }
        location = /favicon.ico {
            alias /home/isucon/isucon/webapp/staticfiles/favicon.ico;
        }

        location /recent_commented_articles {
            set $memcached_key "/recent_commented_articles";
            memcached_pass memcached;
            default_type text/html;
            error_page 404 =200 @scgi_recent;
        }

        location @scgi_recent {
            scgi_param REQUEST_METHOD "GET";
            scgi_param REQUEST_URI    "/recent_commented_articles";
            scgi_param DOCUMENT_URI   "/recent_commented_articles";
            scgi_param SCGI           1;
            scgi_pass scgi_app;
        }

        location / {
            if ($request_method = POST) {
                return 302;
            }
            ssi on;
            ssi_silent_errors on;
            set $memcached_key $uri;
            memcached_pass memcached;
            default_type text/html;
            error_page 404 =200 @scgi;
            error_page 302 =302 @scgi;
        }

        location @scgi {
            ssi on;
            ssi_silent_errors on;
            scgi_param REQUEST_METHOD $request_method;
            scgi_param REQUEST_URI    $request_uri;
            scgi_param DOCUMENT_URI   $document_uri;
            scgi_param SCGI 1;
            scgi_pass scgi_app;
        }
    }
}

fujiwaraさんも d:id:sfujiwara:20110827:1314460582 で書いておられますが、標準状態ではnginx->memcached間はつど接続/切断されてしまうので、ngx_http_upstream_keepaliveを使って接続を維持しています。おまけでSCGIへの接続もつなげたままにしています。これでだいぶ速度が変わります。

memcached側にpostリクエストを投げるとエラーになってしまうので、scgi側に302statusでfallbackさせています(今回だとpost後には必ず302になるので)。また、404 fallback時に=200がないと404でresponseが返ってしまうのでその辺もいじってます。

/recent_commented_articlesだけ別なのは、SSIでのinclude時のサブリクエスト中は$uri変数が元のリクエストのままになってしまっており、$memcached_keyがおかしくなってしまうのでやっつけ対応です。多分使う変数がまずいんですが、調べる時間もなかったのでこんな事になってます。

普段の業務で使うには色々アレなところも多いですが、今回はとりあえず速度が出れば後は何でもOK!なものという事もあり、適当です。真っ先にiptables -Fした時にはid:tagomorisなどには突っ込まれましたが、まぁそんなもんです。


ちょっと課題が普段blogサービスをやってる人に有利かな?って感じがあり、recent_commented_articlesの実装を見てすぐfrontでcache+SSI戦略を立てることができたので今回の課題ではうまく行きましたが、懇親会などで聞かせていただいた他の参加者の正統なアプリチューニングの話はとても参考になりました。

企画・運営・参加者のかたがたお疲れ様でした!