ISUCON7の予選を学生枠で通過しました

ISUCON7の予選に @brook_bach さん,@mayoko_ さんと「座るだけのコンテストってな〜んだ?」で参加しました。

@brook_bach さんの記事

brookbach.com

@mayoko_ さんの記事

mayokoex.hatenablog.com

チーム結成の経緯

準備

3人とも別の大学ということもありミーティングはすべてSlackで行いました。

事前準備として以下のようなことをやりました。

お役立ち情報や秘伝のタレをGoogle Docsにまとめた

3人とも普段からWebプログラミングをやっているわけではないので,MySQLのログイン方法やsystemdの使い方などから復習してGoogle Docsにまとめました。MySQLのデータのバックアップとリストアは一度は練習しておかないと本番で詰む可能性があります。プロファイルのために使用するツールとして mysqldumpslow, dstat, kataribe, wsgi_lineprof を使用することも事前に決めておきました。nginx.confmy.cnf は過去のISUCONの参加者のブログから設定項目を寄せ集めて作りました。設定ファイルの更新とミドルウェアの再起動を行うスクリプトも事前準備しておいたので非常に捗りました。

作業フローを整備した

去年のISUCON6に参加したときには本番サーバーでデバッグをしていて非常に効率が悪かったので,今年は改めようと思い,手元の開発環境でデバッグGitHubにpush → 本番サーバーでpullして sudo ./reload.sh という作業フローにしました。

GitHubではプライベートリポジトリを用意しなければならないので普通は課金が必要になってしまいますが,学生特権でプライベートリポジトリが使い放題なので活用させていただきました。

サーバーからプライベートリポジトリにアクセスする方法は以下のページを参考にさせていただきました。

azriton.github.io

Pixiv ISUCONを使って練習した

注意:Pixiv ISUCONのネタバレがあります。将来的に解く予定がある人はスキップ推奨!

Pixiv ISUCON を解いて練習しました。本番ではPythonの実装を使用する予定だったので methaneさんのPython実装 を利用させていただきました。

このPixiv ISUCONの内容が今回のISUCON7と非常に深く関わっていて,ソースコードの流用などで非常に助けられました。

私はAWSのアカウントを持っていないしGCPはクレジットカードの登録をしたことがないインフラ弱者だったのですが @brook_bach さんがよろしくやってくれました。

f:id:kujira16:20171024005814p:plain

f:id:kujira16:20171024005823p:plain

f:id:kujira16:20171024005621p:plain

f:id:kujira16:20171024010854p:plain

f:id:kujira16:20171024010933p:plain

f:id:kujira16:20171024010923p:plain

f:id:kujira16:20171024010944p:plain

f:id:kujira16:20171024011704p:plain

Ansible playbookを準備した

@brook_bach さんが全てよろしくやってくれました。

本番開始直前まで

@brook_bach さんの勘が冴えまくりでした。正直なところ,私は「1台構成だったらAnsibleとか別に要らなくない〜?」と思っていたのですが,用意してくれていたので助けられました。

f:id:kujira16:20171024011745p:plain

f:id:kujira16:20171024011838p:plain

f:id:kujira16:20171024012116p:plain

本番

13:00 〜 14:00

  • サーバーが3台あるという事実が判明する
  • ansibleでツールのインストールやデータのバックアップや公開鍵の登録などが動いている間にレギュレーションを読む
  • アプリで遊んでみるとAjaxが動作しているっぽい雰囲気だったのでChrome DevToolsでネットワークの動きを見てみる
    • GET /fetch でチャンネルごとの未読件数を取得して,未読があったら GET /messagechannel_idlast_message_id をパラメータにして問い合わせるとメッセージが取得できるという仕組みらしい
  • 手元のPCの ~/.ssh/config の設定を書いて ssh app1 とかでアクセスできるようにする
  • 初期構成でベンチマーク → app1だけだと4232点,app1 + app2だと6187点

14:00 〜 15:00

  • git pull && sudo ./reload.sh を3台に適用するのが面倒だという話になる → @brook_bach さんがtmuxのsynchronize-panesという機能で3台同時に操作できるようにしてくれる
  • MySQLの設定を @brook_bach さん,Nginxの設定を私が担当する
    • 静的ファイルの配信の設定もこのとき行った
    • 初期状態の nginx.confuser www-data; を消すと /var/lib/nginx/proxy にアクセスできないというエラーが出て,user www-data; を付けると /home/isucon/isubata/webapp/public にアクセスできないという問題が起こり,30分くらい時間を溶かす
    • sudo gpasswd -a www-data isucon で解決した
    • /var/lib/nginx の役割や,Nginxがどのユーザーで動いているのかについて復習が必要。真っ当な方法を誰か教えてください…
  • /icons がとても遅く,Pixiv ISUCONと同様にMySQLに画像データが入っていたので,Pixiv ISUCONをやったときと同じ方針で解決を試みる
  • @brook_bach さんと @mayoko_ さんにはそれ以外の箇所の高速化をしてもらう

f:id:kujira16:20171024014623p:plain

15:00 〜 17:00

  • /icons の高速化に取り組む
    • /icons を静的ファイルとしてNginxに配信させるためには,アイコン画像が投稿された時に全てのアプリサーバーにアイコン画像を配る必要がある
    • WebDAVのようなオシャレなものを使うという発想がなかったので POST /saveicon というAPIを生やして POST /profile が呼ばれた時に全てのアプリサーバーの POST /saveicon を呼び出すという方法で配ることにした
    • デッドロックする可能性があるので本当は良くない
  • @brook_bach さんと @mayoko_ さんが画像配信以外の箇所を高速化してくれる
    • 私は全く見ていないのでよく分からないが,チャンネルの未読件数を(総数−最後に読んだときの総数)で計算できるようにしてくれたらしい
  • いろいろトラブルがありながらもアプリの修正が完了して31067点になる。この時点では学生2位

f:id:kujira16:20171024015530p:plain

17:00 〜 19:30

f:id:kujira16:20171024015947p:plain

  • 若干マシにはなったが /icons がまだ遅い
    • Pixiv ISUCONで作った秘伝のタレで expires 24h; を設定しているにも関わらず,3分の1くらいのリクエストしか 304 で返せていない
    • ベンチマーク中にdstatを見てみると,ほぼ100Mbps使われていてグローバル向けの帯域を使い切っている
    • この時点で28万点くらい出しているチームがいたので,帯域を使い切らない方法があるはず
  • 画像ファイルを捏造して軽いファイルを返してみたり,JavaScriptを書き換えて画像をLocal Storageに保存してみようとしてみる → 静的ファイルの書き換えはベンチマークで弾かれました
  • とりあえず帯域を稼ぐためにDBサーバーでもNginxを動かしてみる → 34882点(なぜスコアが上がらないのかよく考えるべきだった)
  • 画像以外の箇所でN+1を消したりしていたら52186点が得られた
  • "nginx image cache" でググるCache-Control "public" を付けろという記事 が出てきたので付けてみる → スコア変わらず

19:30 〜 21:00

  • Gunicornやカーネルパラメータの設定をする
  • 再起動テストをする → サービスが立ち上がるまでにアクセスすると502が返る以外には特に問題なかった
  • ガチャを引きまくって終了。ベストスコアの55931点は再現しなかった

敗因と反省

  • Cache-Control "public" を付けて,かつファイルのタイムスタンプを合わせる(レスポンスヘッダのLast-Modifiedを合わせる)とCDNがキャッシュしてくれて304で返せるという環境を想定したベンチマークだったそうです
  • Cache-Control "public" についての情報に行き当たった時に,もう少し詳細に調査をしていれば気づいたかもしれません
  • 一般枠と比べるとかなり低いスコアでの学生枠通過ですが,CDNの設定経験がある学生なんて普通いないので大目に見てくださいw
  • とはいえ,Conditional RequestsはRFCで標準化されている範囲だと言われると何も言い返せません…

感想

  • 学生枠で参加できる最後の年だったので予選を通過できて嬉しいです
  • アプリの実装が凝っていて(Pythonでさえ400行くらい)準備がすごく大変だったと思います。運営チームの皆様ありがとうございました

GitHub リポジトリ

github.com

ほしいものリスト

以下のURLから私たちのチームに書籍を購入していただけると私たちのチームを支援することができます。

http://amzn.asia/jdJ1NRV