かみぽわーる

kamipo's blog

ISUCON4予選に参加してきた

ISUCON4予選お疲れさまでした。

すこし時間が経ってしまったけど、当日うまくいかなかったことの復習をしたので備忘としてここに記します。

今回のチームメンバーは@さんと@ちゃんでした。ギリギリのオファーにも関わらず一緒に参加してくれてありがとう!

チームメンバーの参加エントリはコチラ

当日うまくいかなかったこと

役割分担で僕が目指していたのは、セットアップや開発基盤をすばやく整えて、負荷やアクセスログを分析して根拠をもってなにをすべきかを明らかにすることで、メンバーそれぞれが力を発揮して問題に取り組めるようにできればいいなと思ってた。いわゆるファシリテータというやつなんですかね。

結果からいって自己評価は、そのほとんどがうまくいかなかったと思っていて、その部分がとても心苦しかったです。

うまくいかなかったと思うことが大きく2つあって

ひとつが、isuconユーザのパーミッション問題にハマって(結局終わってみるまでパーミッションの問題だとすら気づけなかった)、それ絡みかどうか解決できてないのでわからないけど、僕がサーバ側で git push できない状態を解決できなかったので Initial commit の git push に30分以上ぐらい掛かってしまったこと(ローカルに rsync してきて git push した)。

もうひとつが、詳細はやっぽさんのエントリ参照ですが、//mypageはアプリ通さずnginx側で返せるようにできるよねってことで、じゃあちょっとnginxの様子見てくる!って張り切って出ていったきり、それを実現できずにずっとハマってたこと。

それで、アクセス数多いからせめて/だけでも静的に返そうってことで全員で nginx.conf いじりながら/で静的ファイル返す方法を模索することになってしまった。

最終的にやっぽさんが気合いで解決してくれて、その状態で提出となりました。

/を静的に返せるようになったアクセスログを集計したのが以下。

*** HTTP requests total: 202764 ***
 
*** HTTP requests stats order by time ***
 
count: 18433, total: 188.261999999996, mean: 0.0102133130798023
path: /login
 
count: 3483, total: 8.70599999999991, mean: 0.00249956933677861
path: /mypage
 
count: 1, total: 0.397, mean: 0.397
path: /report
 
count: 36866, total: 0, mean: 0
path: /images/isucon-bank.png
 
count: 36866, total: 0, mean: 0
path: /stylesheets/bootstrap.min.css
 
count: 36866, total: 0, mean: 0
path: /stylesheets/bootflat.min.css
 
count: 36866, total: 0, mean: 0
path: /stylesheets/isucon-bank.css
 
count: 18433, total: 0, mean: 0
path: /
 
count: 11654, total: 0, mean: 0
path: /?error=not_found
 
count: 2091, total: 0, mean: 0
path: /?error=banned
 
count: 1205, total: 0, mean: 0
path: /?error=locked
 
*** HTTP methods stats ***
GET:  184331
POST: 18433
 
*** HTTP statuses stats ***
200: 184331
302: 18433

/を1ms未満で返せるようになったけどスコアはそんなに伸びなかったので、/loginが10msぐらいなのを根本的に解決しないと上位は難しそう、という集計結果を出すことができなかった。

/loginがもっと速くないとダメだってことなら、じゃあこれ以上速くするならusersは不変なマスターデータだから起動時にプロセスのメモリに持とうとか、tsvから読み込んだら生パスワード分かってるからpassword_hash計算しなくていいよねとか、そういうことに根拠をもって取り掛かれたはず。

ハマって時間に余裕ない状態じゃなければ、やっぽさんはbenchmarkerのバイナリ見て、このDOMチェックしてるから人間が見たときの見た目変えずに最小のDOM返せばよくね?って言ってて、いや、それってありなん…みたいなのも余裕があったら試してみることもできたと思う。

ただ今回は、予選特有の環境における攻略法が知られていたからか、それだけではボーダーを超えるのが難しい予選だったので結果はどうなったか分からなかったわけですが、それでもやりたいことはやりきったよねと思える状態まで行きたかったなというのが正直なところ。

やっぽさんが気合いで解決してくれたことでなにでハマってたか分かったので、復習してみて//mypageをnginx側で返すことができた。/のときと同様/mypageも1ms未満で返せるようになったが、スコアの伸びは1割程度だった。どうすればよかったかのミニマムな設定を以下に示す。

http {
    upstream app {
        server 127.0.0.1:8080;
    }

    map $arg_error $index {
        default   index.html;
        locked    err_locked.html;
        banned    err_banned.html;
        not_found err_not_found.html;
        not_login err_not_login.html;
    }

    server {
        root /home/isucon/webapp/public;
        index $index;

        location /mypage {
            rewrite /mypage /mypage.html;
        }

        location /mypage.html {
            ssi on;
            set $login   $cookie_login;
            set $last_ip $cookie_last_ip;
            set $last_at $cookie_last_at;
            # 2014-10-02 03:51:08 <- 2014-10-02%2003%3A51%3A08
            if ( $last_at ~ (.*)%20(.*) ) {
                set $last_at "$1 $2";
            }
            if ( $last_at ~ (.*)\s(.*)%3A(.*)%3A(.*) ) {
                set $last_at "$1 $2:$3:$4";
            }
        }

        location ~ ^/(login|report) {
            proxy_pass http://app;
        }
    }
}

ログイン情報を、クエリストリングに入れて渡すとスペースが%20に、cookieに入れて渡すと加えて:%3Aエンコードされてくるのをデコードする方法が分からなかったのでそこだけがんばったけど、それ以外は仕組みが分かればなんということはなかった。

ハマってたのは location のマッチに関する挙動をちゃんと理解していなくて、ちょっとした書き方の差でうまく動いてないのが、全部location /に吸われていってるせいだということに全然気づけてなかった。

まずassets系をnginxで返すのに

        location ^~ /(stylesheets|images) {
            root /home/isucon/webapp/public;
        }

        location / {
            proxy_pass http://app;
        }

と書いてたけど、これが全部location /にマッチして静的ファイルがちゃんと返せてないのに気づいて試行錯誤して

        location /images {
            alias /home/isucon/webapp/public/images;
        }

        location /stylesheets {
            alias /home/isucon/webapp/public/stylesheets;
        }

こう書いてなんとか返せるようになったけど、^~を使ってたのがよくなくて

        location ~ ^/(stylesheets|images) {
            root /home/isucon/webapp/public;
        }

こうすればマッチした。

あと/をエラーメッセージで分岐して静的ファイルを返すのに

        map $arg_error $index {
            default   index.html;
            locked    err_locked.html;
            banned    err_banned.html;
            not_found err_not_found.html;
            not_login err_not_login.html;
        }

        location = / {
            root /home/isucon/webapp/public;
            index $index;
        }

        location / {
            proxy_pass http://app;
        }

という感じに書いていたけど、これはindexディレクティブによってrewriteされた結果location /にマッチするらしく、これも期待した動作をしなかった。

SSIを試そうとしたときも、最終的にlocation /にマッチしてしまってバックエンドが静的ファイルを返してしまっていたからSSIのタグが置換されずにそのまま出てしまっていた。

もし一度でもlocation /を消したミニマムなケースで試していたらすぐに気づけたかもしれないけど、そもそも基本的なことを理解していなかったことが要因なので実に不甲斐なかった。

おわりに

ダメだったとこ復習して反省したらだいぶスッキリしたので、今度チームでおいしい日本酒飲みに行きましょう!