🏋️

フロントエンドエンジニアのISUCON14奮闘記

2024/12/11に公開

男もすなるISUCONといふものを、女もしてみむとてするなり

ということで、12/8(日)に行われたISUCON14に参戦してきました。以前から存在は認知していたのですが、なんだかんだでこれまで参加する機会がなく...。熱烈に勧めていただいた同僚の @iwashi623メントス に感謝です。2人はその後もレクチャーしてくれたり練習に付き合ってくれたりと、大変お世話になりました。

チームメンバーは、自分が苦手なインフラ周りに明るい人が良いかなと思い、新卒同期の @urban-side@kota13064 を捕まえて3人で挑みました。事前の練習から本番まで新しく学ぶことが多く、めちゃめちゃ楽しかったです。

結果

8,566点で198位でした。後日追試でFAILしたチームが増えていたので、実際はもう少し上位になっていそうです。
事前の目標が10,000点を超えることだったので悔しい結果となりましたが、全体的に例年よりもスコアが伸びづらかったようなので、健闘できた方かなと思っています。

当日終了後のリーダーボード
当日終了後のリーダーボード

https://isucon.net/archives/58837992.html

フロントエンドエンジニアでも楽しめる

普段は主にReactを書いてます。Go / PostgresSQLあたりはたまに触るくらいです。

ISUCONはフロントエンドの人間の出る幕ではないと思っていたのですが、普段BE/インフラやってるチームメンバーも、中々MySQLやNginxを触ったり、ここまでのパフォーマンスチューニングをしたりする機会はないとのことで、全然気負いすることなく臨めました。

ただしレスポンスの構造やJSを変更すると失格になるらしいので、フロントエンドエンジニアとして改善しにいこうとすると普通に人権はないです。

事前練習

キホンを学ぶ

@iwashi623 にISUCON9の予選をベースとして、環境構築、ISUCONの標準的な構成、ログの出し方を叩き込んでもらい、インデックス追加やN+1といった典型的なボトルネック解消のハンズオンまで、面倒をみていただきました。このおかげで何とか戦えるまでに成長できました。圧倒的感謝👹🙏です。

private-isu

チームではまずISUCON本を読み、private-isuに取り組みました。
DBのボトルネックを解消していくと、ボコボコ点数が上がっていったので、爽快感がありました。最終的に30万点くらいまで上げて満足して解散したのですが、後日調べていたら90万点超えの猛者もいて驚きました。

過去問に心を折られる

本番2週間前に、実践練習としてISUCON12予選を解いてみました。問題の詳細は講評に書いてあるので割愛しますが、MySQL移行を頑張っているだけで終わってしまい、全然スコアを上げることができませんでした。
private-isuとは違い、単純にslow-queryを解消するだけでは良くても数千点しか上がらず、無力感と謎の自信を抱えて本番に挑むことになりました。

https://isucon.net/archives/56850281.html

ツール類の準備

色々試しましたが、最終的にはpt-query-digest, pprof, alpあたりの基本的なツールセットに落ち着きました。
pproteinは、手動でのログローテートやdigestが不要になるのがとても魅力的でしたが、競技中にセキュリティグループやVPC内のインスタンス数の変更が発生するので、レギュレーション違反になるリスクを恐れて諦めました。来年はチャレンジしてみたいと思います。

その他、環境構築用にAnsibleのplaybookを用意していました。
過去の優勝チームがplaybookを公開してくれているのでパクろうと思ったのですが、複雑すぎて何も理解できなかったので、部分的に参考にさせてもらい、ツール群のインストールと設定ファイルの配置を行うだけのシンプルなものを用意して行きました。
ただ後述しますが、ansibleは当日使われることはありませんでした...。

使われることはなかった
使われることはなかった

当日

初動、焦る

Cloud Formationでのインスタンス作成手順をミスったり、AWSアカウントのPublic IPが上限に達してしまったりと、序盤から躓きまくってかなり焦りました。ただそのおかげで、初期に発生していたらしいベンチのバグに遭遇することはなかったので、結果オーライかもしれません(?)

その他SSHでpublic keyを置いてこようと思ったらすでに設置されているという肩透かしをくらい(めっちゃ楽ではありました)、そのせいかAnsibleがAccess denied(public key)になり諦めて手動実行、.gitignoreではなく.gigignoreを作成してしまうといったドタバタがありながらも、なんとか初期ベンチ実行し、スタートラインに立つことができました。

.gigignoreを誤って作成してしまう
.gigignoreを誤って作成してしまう

ハイライト

ISUCON12予選を解いていたので、まずはDBがMySQLであることに安心しながら、slow-queryのdigest結果をみながら改善を進めていきました。DB以外も特にインフラ面でトリッキーな構成はなく、アプリケーション側のロジックにボトルネックがあるという面白い問題だったと思います。

雑ですが、以下うまくいった改善ポイントです。

  • 各種index追加
  • ownerGetChairsのクソデカクエリ退治
    • 3sかかっていたのがmsオーダーに改善
    • ただ依然として重いクエリとして残っていた
  • middlewareでのDBアクセスをやめる
    • JWTにid持たせて取り出すだけにする
  • chairGetNotificationのride_statusをキャッシュ
    • memcachedでstatus管理
    • 3000~4000点くらい上がった
    • SSEは知らなかったので勉強します...
  • 近い順からマッチするように変更
    • スコアは微増
    • bulkでやればもっと効果があったらしい
  • notificationのretry intervalを伸ばす
    • 30msから適当に長くしてみたが、これも微増程度

できなかったこと

マッチング処理へのアプローチ

スコアの計算条件などから、ここを最適化することが今回のキーポイントであることは途中からなんとなく気づいてはいたものの、革新的な改善を思いつくことはできませんでした。
チームメンバーの2人がマッチングの距離を調整したり、ユーザーの不満度を下げる試みをしたりしてくれましたが、逆にイスの供給が不足してエラーになってしまいました。

slow-queryの改善

マッチングのロジック改善や、notificationsの改善に苦戦していたりで、slow-queryを潰し切ることができませんでした。status取得のN+1や、chair_locationsの履歴テーブルなどはもう少し時間があれば手が出せた部分だと思うので、自分の実装力不足を痛感しました。
また、途中からCOMMIT;が1st slow-queryになっていたのですが、トランザクションがごちゃごちゃしすぎて、結局特定すらできずに終わってしまいました。

インスタンス3台の活用

DBやアプリケーションサーバーの分割がセオリーとのことですが、練習で一度もやってなかったのでぶっつけ本番ではできなかったです。そもそもアプリケーションのロジックがボトルネックで、あまりCPUがパンパンになることもなかったので優先度は低いと判断しました。
終わったあとに「DB分割によって整合性を取ることが難しくなった」という感想もそこそこ見かけたので、迂闊に手を出さなかったのは正解だった気もしています。

今回は、DBよりもアプリケーション側のロジック改変をしなければならないこと、ベンチが整合性を重視する仕様になっていたらしいことから、実際にデプロイしてベンチを回さないと動作確認ができず、作業が詰まってしまうことが多かったです。
そのため、途中から2台目のインスタンスは @urban-side によるマッチングのロジックの実験場として運用していました。最初から割り切って1人1インスタンスで各々自由に実験していくという戦略もありだったかなと思いました。特に動作確認の方法については、良い方法があればぜひ知りたいです。

動かしてみたものの0点でCloseされるPR
動かしてみたものの0点でCloseされるPR

再起動試験

これもやったほうがいいと聞いてはいたものの、時間がなさすぎてできませんでした。
envcheckは通っていましたが、memcachedが再起動後に起動してくれるかとか、ride_statuseschair_sent_atを適当に埋めてたので整合性大丈夫かとか、過剰にビビっていました。
結果的にはちゃんと翌朝まで点数があったので良かったですが、追試にビビるあまり大胆な改善ができなかった部分もあるので、余裕があればTRYしたかった部分です。

その他技術的な観点以外でも、役割分担やマニュアルの読み合わせ、スコアをハックしにいく動きなど徹底しきれなかった部分が多く、過去問を通して練習しておくべきだったなと思っています。

振り返り

AIが強い

N+1解消や、典型的なクエリの改善などは渡せば大体解決してくれるので、private-isuを解いているときはもうAIを使いこなすバトルになるんじゃないかと思っていました。DBのスキーマを最初に食わせておくという手もあるみたいです。
一方で、今回の問題はスコアの算出条件がトレードオフになっていたり、明確にここを改善すればいいというボトルネックがなかったりと、自分の頭で考えなければいけないポイントが多かった分楽しかったです。作問側も年々難しくなってきているんだろうなと思います。

俺は弱い

特にSQL力の弱さを痛感しました。JOINする/しないでパフォーマンスにどういう影響があるかとか、index1つとっても、何を含めるかとか、逆にパフォーマンスを悪化させることがあるとか、基本的な知識が全く足りないていませんでした。
年末はこのあたりの本を何か買って勉強しようと思います。

https://twitter.com/copinemickmack/status/1842436235631243382

今回キャッシュに選択したmemcachedについても、問題なく動いたので良かったですが、goのインメモリキャッシュやRedisと比較して本当に最適な選定だったかは怪しいです。I/Oのパフォーマンスや揮発性などの挙動を把握して、問題の要件に合わせた選択を息をするように出来るようにならないといけないなと思いました。

まとめ

初参加でしたが、楽しい & 技術的にも学ぶことが多く、参加してよかったです。とはいえ数字を出されると上に行きたいという気持ちも強くなってきてしまい、来年はもう少し過去問をやり込んでから再挑戦したいと思います。

また、12/16まで延長戦があるみたいなので、こちらも今週末に復習がてら再チャレンジしてみます!

運営からのお知らせ
運営からのお知らせ

Discussion