Saza, MoririnとISUCON13で学生3位、総合13位(スコアは111625点)を獲得した。チーム名はMONOSで自分は初出場、他二人は前回も出場している。 振り返りがてら時系列順にやったことを書き留めておく。

GitHubレポジトリ: https://github.com/Saza-ku/isucon13

チームメンバーSazaの記事はこちら

前日まで

事前練習は自分一人でprivate-isu・11予選を、チームで12予選・11本戦・12本戦をやっていた。

練習・本番共にSazaが作ってくれたいい感じのテンプレートを使っている。これはセットアップやデプロイ、計測までを簡単に実行できるスクリプトやドキュメントをまとめたものである。非常に便利で練習が捗った。 計測ツールはalp, pt-query-digest, pprof, netdataを使っていて、ベンチマークが終了するとIssueに全ての結果が書き込まれるようになっている。

当日

序盤

初動はSazaがインスタンス・レポジトリのセットアップから初期計測まで、自分とMoririnがマニュアル・コードを読むという感じに分担していた。

セットアップが終わって何度か計測してみると、DNS解決の問題で初期化処理に失敗するようになった。初期設定ではDNS解決で返すIPアドレスの設定(環境変数ISUCON13_POWERDNS_SUBDOMAIN_ADDRESS)が2台目インスタンスのPublic IPになっていたのが原因で、1台目にするとうまく動いた。

この問題やベンチマーカーの不具合などから計測結果がなかなか出ず、自分とMoririnはlivecommentが重要そうという話をしながらエスパーで無駄なSQLクエリの削減を進めていた。Sazaはさっと秘伝のタレ、複数台構成(NGINX+App+PowerDNS, PowerDNSのMySQL, AppのMySQL)をやってくれた。

[11:43] 3300(#14) : 初動(#16)
[12:10] 3864(#21) : ランク改善(#12)
[12:13] 4500(#23) : 複数台構成(#23)
[12:22] 5682(#24) : NGWord検索改善(#2)
[12:37] ?(#29): ランク改善2(#31)
[12:43] 4500(#33) : 複数台構成2(#32)
[12:54] 9000(#35): livestream_tagsにindex(#36)

中盤

ちゃんと計測結果が出た後は以下のように分担した。

  • 自分: icon関係
  • Saza: DNS水責め攻撃
  • Moririn: スロークエリ

自分はiconの画像データを「DBから剥がす」「304で返す」の2つの改善をした。 「DBから剥がす」に関しては今までのISUCONでもよくある改善である。(#41)

「304で返す」に関してはアプリケーションマニュアルの方法に基づく改善である。アプリはUserのレスポンスに画像データのハッシュ値(SHA256)を含めて返している。ベンチマーカーは画像データを取得する際にこのハッシュ値をHTTPヘッダIf-None-Matchにつけて送信するため、ハッシュ値が一致するなら304 Not Modifiedを返して良いというものである。

最初は画像をNGINXで返そうと試みた。具体的には、NGINXでetagの機能をONにし、アプリが返すハッシュ値を単純なSHA256からNGINXが使用する形式(LastModifiedのUNIX時間の16進数-Lengthの16進数)に変更した1。こうすることで、NGINXがいい感じに304を返したりファイルデータを返したりしてくれることを期待していた。しかしベンチマーカーを回した結果整合性チェックに失敗し、ハッシュ値はSHA256から変更できないと判明した。NGINXのetagのアルゴリズムを変更するプラグインもあった2が、うまくいかず断念した。(実装ブランチ)

最終的には、UserNameをキーにしてMemcachedにハッシュ値を保存し、画像の取得時にIf-None-Matchヘッダの値と一致すれば304を返すようにした。iconsテーブルへのSELECTが激減したので大幅改善である。ちなみにIf-None-Matchヘッダの値はハッシュ値の両端に"(ダブルクオーテーション)がついているため、キャッシュがヒットせずデバッグに手こずった。(#75, #90)

[13:15] 16941(#40) : iconをDBから剥がす(#41)
[13:26] 19508(#43) : slot改善(#44)
[15:00] 38091(#49) : livecomment, reaction改善(#50, #53)3
[15:34] 45424(#60) : ng_wordsにindex(#59)
[16:13] 63000(#73) : iconを304で返す(#75)

終盤

icon改善が終わった後、自分はスロークエリを見ながらindexを貼ったりしていた。(#6, #84)

Sazaが担当しているDNS水責め攻撃の対策はかなりの難易度のようであった4。レコードをオンメモリで保存したり、一旦不正サブドメインもAレコードを返してアプリでエラーを返すようにしたりしていたが、なかなかうまくいかずかなり苦しそうであった。

Moririnは地道にSQLクエリ改善をしてくれていた。fillXXXResponseのメソッドが複数エンドポイントにまたがっており、自分の変更とコンフリクトしそうで若干大変であった。

最後40分ほどになってからスロークエリの一番上にあるthemeのSELECTをキャッシュできることに気づいた。ログや計測ツール削除の準備だけしてから爆速で実装しなんとか1発で動作した。(#100)

最後は少しギリギリになったが、ログや計測ツール削除後に再起動し、ベンチマーク実行とブラウザチェックをして終了。 最終スコアは111625点であった。

[16:32] 68000(#79) : livecommentsにindex(#6)
[16:32] 73000(#83) : reactionsにindex(#84)
[17:04] 74000(#89) : IconHashのキャッシュし忘れ(#90)
[17:14] 84529(#93) : search改善(#93)
[17:40] ?(#99) : themeのキャッシュ(#100)
[17:50] 111625(最終) : ログ・計測ツール削除, DB Wait(#95, #98)

できなかった改善

livecomment周りのN+1改善は難しいが、コメントやリアクションをたくさん投稿&表示できると投げ銭やユーザーが増えるというベンチマーカーの挙動を考えると改善効果は大きいかなと思った。

またDNS水責め攻撃の対策ができればPowerDNSのMySQLサーバーのリソース使用量が減るので、2台目インスタンスにアプリを分割したいよねという話もしていた5

終わりに

定石の改善箇所もたくさんありながらDNSサーバーという新たな鬼門も現れ、とても楽しく1日全力を出せた。 入賞できず残念だが来年は良い結果を残したい。


  1. https://www.denzow.me/entry/2017/11/26/221442 ↩︎

  2. https://github.com/kkung/nginx-static-etags/ ↩︎

  3. ここで一瞬2位になった(https://x.com/saza_ku/status/1728447246826635414↩︎

  4. 出題者の方が書いたDNS水責め攻撃の記事を見て、出題の背景やろなぁという話をしていた ↩︎

  5. 感想戦を見ているとDNSサーバーを自作しているチームが複数いて驚いた ↩︎