ISUCON10予選に参加して不通過でした

Webアプリケーションパフォーマンスチューニングコンテスト ISUCON http://isucon.net/、記念すべき10回大会の予選に参加して、あと3チーム100点弱の差で不通過に終わりました。悔しい!

チームメイトは会社の同僚の @acidlemon (ISUCON 3の出題、ISUCON 4, 7 のチームメイト), @mackee_w (macopy, 実はチームを組んだのは初) です。

序盤から中盤

  • ベンチを回して MySQL が重いねー(いつものことだ) と把握
  • acidlemon
    • estate の範囲検索になっているカラムを = 条件で取れるように 0〜49999 -> 0, 50000〜100000 -> 1 のようにクラスわけ(verify がたまにコケるのを解消できず取り込めず
    • interpolateParams=true (server side prepare を無効)
    • select * をやめて id だけまず取得して where id in
  • macopy
    • featureがLIKE検索で複数条件に一致をやっていたのを別テーブルに逃して縦持ち
    • index 追加
    • geo 検索でポリゴンに座標が含まれているかを MySQL ではなく Go 側で判断 (後回しにしたら試行もできず…
  • fujiwara
    • OS は Ubuntu と予告されていたので各種ツール (alp, percona-toolkit, notify_slack, netdataなど) のセットアップ用 mitamae を流して初期設定
    • MySQL を軽くチューニング
    • nginx.conf で bot に 503 を返すように正規表現
    • Regexp::Assemble でまとめてこんなのを "(I(SUCON(bot((-Mobile)?|-Image\/)|FeedSeeker(Beta)?|Coffee)|supider((-image)?\+)?)|crawler \(https:\/\/isucon\.invalid\/(support\/faq\/|help\/jp\/)|Mediapartners-ISUCON|isubot))"

今回はとにかく検索が重く (単発ではそこまで重くないけど10000〜30000行読む必要がある数十msのクエリが大量に飛ぶ)、MySQL の負荷を index では軽減しきれず、序盤〜中盤まで全くスコアを伸ばせず苦しい展開でした。初期スコアは500程度、いろいろやっても600ぐらい。

大抵の ISUCON では序盤にいくつかの定番の手を打つと上がるみたいなジャブが効く感じがあるのですが、今回は全然ダメージを与えられている気がしないという、アプリケーションとしては比較的単純な作りなのに心を折りに来る良問でしたね…

また途中、自分が用意した deploy.sh がカレントディレクトリからでないと正常に動かないという雑な作りなため、macopy がデプロイしたつもりができてない (のでスコアが何も変わらない) というので revert を連発することになり、1時間ほど消費するというトラブルもありました。

中盤から終盤

時間的にはもう終盤に近く、まったくスコアが伸びないと心が先に折れるので、力押しでスコアを出そうと MySQLレプリケーションを組んで3台構成にしました。

  • isucon1: nginx, app, mysql (writer)
  • isucon2: app, mysql (reader)
  • isucon3: app, mysql (reader)

として、isucon2, 3 にレプリケーションを設定。アプリケーションからは 127.0.0.1 に接続する reader 用の db 接続を用意し、Search 系のクエリはそちらに切り替え。レプリ遅延回避のため多少小ネタを入れます。

  • ベンチの最初に叩かれる /initialize でデータが初期化されるため、その遅延が解消するまで initialize のレスポンスを sleep(10) して粘る
  • /initialize が終わると整合性チェックが User-Agent: isucon-verify で飛んでくるので、このアクセスはすべて 1 台目で処理するように nginx を設定
    • 1台目は reader 接続も 127.0.0.1 (=writer) を向いているので確実に遅延なしで読めます

これで1000を超えるぐらいまでなんとか伸ばして一息入れました。

更に、データの更新はほぼないので MySQL のクエリキャッシュが効きそう、ということで有効化して1300ぐらい? CPU が若干使い切れてなさそうだったので db.SetMaxOpenConns(10)db.SetMaxOpenConns(30) にしたらまた少し。slowlog はもう見ないことにして無効化。

あと残り2時間程度、ここからもう MySQL そのものをどうにかするのは無理筋と判断して、acidlemon と macopy に、それぞれ chair と estate の検索をオンメモリにする実装を並行で取りかかってもらいました。

先に取りこんだ estate の検索は fail したので切り戻し、残り30分を切ったところで chair の検索オンメモリ化の投入になんとか成功して 1800 越え、再起動試験をして残り10分で 2074、競技中の最高スコアをマークしたところで打ち止めとなりました。

終戦

最終的には、一般枠予選通過まで84点差、あと3チーム上回れば…という惜しいところで敗戦しました。

アクセスログみる限り、追試でのベンチ走行では競技中の最終ベンチよりも10%ほど多くレスポンスを返せていたので、あと何度かベンチを回していたらブレで100点程度上がっていたかも知れず、通過の可能性も僅かながらあったはずなので、粘りが足りなかったですね。ISUCON 6 の時は最後の1分を切ったところでベンチを開始して、それが fail せずに通ったことで予選を通過できていたのに…

ISUCON10 オンライン予選 全てのチームのスコア(参考値) : ISUCON公式Blog

検索のオンメモリ化ももう1,2時間早く判断して取りかかっていれば、estate, chair の両方を動かすことができた可能性が高く、これは判断の遅れです。そもそも予選通過ボーダー付近は毎年混み合うので、そこで揉み合っていた時点でベンチのスコアのブレの運要素を排除しきれず、つまりは実力不足でしたね。

おわりに

ISUCONでは予選通過できたのが7まで (8は出題、9, 10 が予選落ち) ということで、2年連続で本選の地を踏めず、悔しい思いです。今年も楽しい問題でした。運営の皆様、トラブルで開始が遅れたにもかかわらず、当日中に結果の発表まで大変お疲れ様でした!