ISUCON13 に参加した

いつものメンバーで参加した。このメンバーで組んだのは ISUCON9 からなので、もう 5 年になるのかー

いつメン

東京、京都、シアトルと、全チームの中でも地理的にはかなり離れているチームなんじゃないかな。

今回は僕がインフラ担当。3 人ともどこでも担当できるのは面白いメンバーだと思う。

最終スコアは 39,560 で、https://isucon.net/archives/57993937.html を見る限りは 40 位っぽい。再起動試験を含まないポータルのランクでは 49 位。

準備

素振りは 2023/9/10 (12予選) と 2023-11-04 (11本選) の 2 回やった。本番と同じ形で、8h 一本勝負。どちらもそこそこの点(本選出場チームの真ん中ぐらい)は取れたので、そのぐらいはいけるんじゃないかという感覚。

素振った結果として、僕の時間の使い方は以下で行こうと決めていた。

  • 10:00
    • ベンチ一発流す
  • 10:00-10:30
    • repository 作って各サーバを git clone したものに入れ替える
  • 10:30-11:00
    • 秘伝のタレを撒く
    • alp/pt-query-digest をいつでも誰でも実行できるように
    • 任意のサーバにデプロイできるスクリプトの共有
  • 11:00-11:30
    • INDEX 直したり
  • 11:30-12:00
    • 複数台構成とか
  • 12:00
    • この辺で多少暇になってそうなので何か手伝えそうな気がする
  • 13:00
    • N+1 も潰し終えてきてそろそろ大玉が何か分かってくる?
  • 16:00
    • この辺で最終形の複数台構成にしたい
    • あとは dstat 見ながら調整
  • 17:30
    • ログ消す
    • ベンチガチャ回しつつ祈る

また、EC2 はとりあえず 6 台立てて好きに使えるサーバを増やしておこうという話をした。

  • 最初から複数台構成にしたい
  • 各自のビルド環境だったり確認環境だったりをローカルに整えるのはちょい面倒

という理由。

やったこと

リポジトリは公開している。https://github.com/onk/isucon13

10:00

リポジトリ作って撒く。rust/target 以下が .gitignore されていなくてイラついたけど、ちょっと待ったら git add できたのでそのままコミット

10:30-11:00

入っているミドルウェア等を把握して、いつもの conf を適用。

あとで分かったけど、ここで静的ファイルにキャッシュ入れてたわ。私が戦犯です。

11:15

alp、pt-query-digest 取得

この時点では「icon を静的化しよう」「statistics はどうにかする必要がありそう」「あとは INDEX 張ってから考える」までしか僕は分かってない。

11:25-11:40

INDEX 足すねー、と言って足す。最速でできたと思う。

やったのは ruby/app.rb から SQL っぽいものを抜き出して、眺めながら WHERE 句と ORDER BY 句に入っているものを愚直に index にしただけです。まだアプリは何も読んでないが INDEX だけなら勘でできる。

例えば

SELECT * FROM livecomments WHERE livestream_id = ? ORDER BY created_at DESC

を見たら

alter table livecomments add index livestream_id_and_created_at (livestream_id, created_at desc)

と書く感じ。この作業は機械でできそう。

11:40

pprof 取得して会話

  • icon 静的化
  • tag は redis にキャッシュ

という話をしたはず?

13:00

ADMIN PREPARE が出ないように interpolateParams=true

12:00-13:15

デプロイ周りの整備したり複数台構成にする準備をしたり

明らかに MySQL が 100% 食っているので、アプリが使うデータストアを分離。

  • s1: nginx, app, powerdns
  • s2: mysql, redis
  • s3: 空き

N+1 倒したら redis 置く隙ぐらいあるだろうと思って適当に。

INDEX 足して 2 台構成になって状況変わったので作戦会議

statistics は redis に sorted set でランキングを持つ、各値も redis に持つ。pt-query-digest も pprof もコレだと言っているので、これはやる。

13:30-16:00

icon 静的化にここまでかかった……。try_files でデフォルトアイコンを出す?ユーザが無いときは 404 にしなきゃいけないんじゃない?みたいなので途中で作戦変更することになった。

結局、初期ユーザは NoImage.jpg を置いておく (initialize でコピーする)、register 時にも NoImage を置く、アイコン変更 (postIconHandler) 時に静的に書き出す、とした。icon はこれで静的に返せるので、あとはまるっと nginx 任せ。

なお If-None-Match の件は僕はマニュアル読んでなくて気づいてない。

16:10

Tag を redis に cache をデプロイ (2h 前に完成していたのでさっさと入れたかったね……)

fillLivestreamResponse の N+1 が潰れる。tags も DB から外して redis にのみ持つようになったはず?

16:20-16:35

pprof に出ていた moderateHandler を潰す。NGWord のループが潰れて、謎の DELETE 文は素直になった。

16:40-17:45

alp に出ていた GET reaction と GET livecomment の N+1 を潰そうとするが解決に時間が掛かる。結局最後に revert。

そしてベンチはひたすら「新たに設定したアイコンが反映されていません」と言ってくる。謎。

17:45

redis 化していた getLivestreamStatisticsHandler を、もうベンチ全然通らないしとエイヤでマージ

18:00

今回 fail で終了だー、と自棄になりつつとにかく enqueue しまくる

18:01

なぜか最終点がベストスコアで出ている

反省

作戦はまぁミスってない、とにかく手が遅い!!!

  • reaction の N+1 の解決こんなに時間掛かってどうすんだ
  • theme や icon hash はさっさと user にカラム追加して一発で取ってきたかった

インフラ的にも酷い感じだった。

  • 結局 3 台構成にできてないのが最悪。N+1 潰しが終わらないのを見に行くとかやっている間に分散しておけば良かった
  • 何気なく入れていた設定の存在を忘れていた。これが今年は一番影響が大きかったね……

あと isudns にインデックス張ってないのを眺めに行ったにも関わらず見逃したのも酷かった。追試した感じこれは大差なかったですが。

2 人で 1 つをやらずに別の N+1 を潰しに行っていたら多分あと 2-3 手はやれたなーと思っていて、30 位は普通に行けましたね。。 matsuu/aws-isucon で追試したら 2 台構成のままでも N+1 2 つ潰しただけで 50,000 点が出たので本当にアプリ側書く手の早さだなーと思う。そして nginx の config は本当にベンチの不安定さに繋がっていたので戦犯。

まったくベンチ通らない様子

よく最終点出たよね。運が良すぎると思う。

反省の残る回だった。来年また来ます。

1 年ぶりに本番に出た感想としては、練習ではベンチを任意のサーバに対して流し放題なのに対して本番では制限があるので、ベンチを流す回数が圧倒的に少なくなるなと思いました。開発のリズムがまったく違うので、本番相当で練習した方がヨサソウです。

matsuu/aws-isucon で複数台で試したい方向け

ベンチを流す前に、

  • env.shISUCON13_POWERDNS_SUBDOMAIN_ADDRESSベンチ対象の IP アドレス に向ける
  • ./webapp/pdns/init_zone.sh して *.u.isucon.localベンチ対象の IP アドレス に向ける

で、ベンチ実行は

$ ./bench run --enable-ssl --nameserver ベンチ対象のIPアドレス --webapp ベンチ対象のIPアドレス

です。