ISUCON10に @ebiebievidence(以下ebi) と一緒に「ここにチーム名を入れる」というチームを組んで出場しました。結果は予選敗退、最終スコアは1300、参考値内での順位は108位でした。使用した言語はGolangです1

この記事では、準備したこと、当日やったこと、振り返り、感想などを書きます。

ebiの振り返りブログはこちら: ISUCON10 予選敗退の記録と反省

準備

ISUCON7, 8, 9の過去問を、時間制限を設けて2人で解き、本番で起きそうな問題を洗い出しました。そして、その経験をもとに、以下で述べる準備をしました。

動きを詰めておく

過去問を解いてみたところ、いろいろと詰まってしまったポイントがありました。例えば、僕もebiもGoのWebアプリにプロファイラを仕込んだことがなく、初めて過去問を解くときはセットアップがうまくできませんでした。この対策として、いい感じに動くプロファイラ設定を予め作っておき2、当日はそれをコピペするだけで済むようにしました。ISUCON10 本番でもこの準備が活き、プロファイラの導入はサクッとできました3

また、お互いの作業がお互いの作業をブロックしてしまい、待ちが発生することがありました。例えば、alpでアクセスログの傾向を見たいのにnginxの設定が終わってないから見れない、改修を入れたのでデプロイしたいがデプロイスクリプトがまだできてない、など。この対策として、ブロッカーとなるタスクを洗い出し、どちらがどのタイミングでどのタスクをするのかを調整し、待ち時間の減少を図りました。

似たような問題として、MySQLのアップグレード作業中にアプリを止めてしまい、1時間ほどベンチが回せなくなるという出来事もありました。この対策として、インフラのオペレーションはできる限りGracefulな手順で行うことにしました。例えばMySQLのアップグレードであれば、1) 予め別ホストでMySQLのアップグレード作業をしておき、2) アップグレードが完了したらアプリのDBの向き先を切り替え、3) うまくいかなかったら向き先をもとに戻す、という手順を組みました。事前にこの手順を考えていたことで、本番当日にMySQLのアップグレードをしたときはebiの作業をブロックせずに済んだし、アップグレードが失敗したときに切り戻しが可能なので安心感がありました(実際に何度か切り戻した)。

役割分担

練習したことで互いの得意・不得意が明らかになり、役割分担をすることができました。今回に関しては、MySQLのexplainは僕のほうが読めて、Goはebiのほうが書けるということが明らかになり、お互いに自分の得意分野のチューニングをメインに行うということになりました。担当分野を分割することで、自分の担当部分に集中することができ、個人的にはかなり良かったと思っています。ただし、役割分担をしたことにより、ログを見て全体の戦略を考えるような動きをする人がいなくなってしまったのではないかという印象があります。

メンタル面

過去問を解く中で「思った以上にスコアが伸びずに焦る」という経験を何度かしました。この経験は地味に効いていて、当日スコアが伸びなくても「まあそんなもんだよな」と思って、ある程度冷静に進めることができました。

当日やったこと

ebiと二人でやったことと、僕がやったことについて時系列順に書いていきます。ただし、時刻は結構適当です。 また、ebiがメインで入れた施策については言及していないので注意してください。

リリース戦略としては、

  1. PR出す
  2. ピアレビューをざっとして、問題なさそうならfeatureブランチをデプロイする
  3. ベンチを回してみて、failせず、スコアが下がらなければmasterにマージする

という感じでやってました。

初動(~13:30)

  • 画面共有をしつつ、マニュアルを熟読した。また、マニュアルを見ながら実際にアプリを動かしてみた。
  • スローログを仕込んだり、nginxにalp用のログを仕込んだりした。
  • 第一回ベンチマーク
  • アクセスログからuaをgrepして雑に集計してみたが、botからのアクセスは来ていなかったので、ひとまず何も対策を入れずにおくことにした
  • アクセスログをalpで集計してみたが、突出して遅いエンドポイントは見当たらなかった。30分ぐらい2人で考えたが、よくわからんので手当たりしだい直していくということになった。

MySQLの載せ替え&アップグレードのオペレーション(~14:30)

ベンチマーク実行中のhtopを見たところ、MySQLのCPU使用率がとても高いということがわかった。練習時に、MySQL8に上げちゃってもいいかなという話をしていたこともあり、MySQLのアップグレードを実行することにした。

MySQL8へのアップグレードには1回失敗した。これは、MySQL8のapt repositoryを追加して apt install mysql-server を実行したとき、コマンドの実行に長い時間がかかりハングしたように見えたため、「一旦止めよう」という謎の思考になり、Ctrl-Cを押してしまったことが原因である。結局このホスト上ではその後MySQLが2度と起動しなくなってしまった。反省している。アプリが動いているホストでこのオペレーションをしていたら致命的だったので、Gracefulなアップグレード手順を組んでおいてよかったと思った。

その後、残ったもう一つのホスト上でMySQL8へのアップグレードを試み、成功した。外部ホストからの接続を許可する設定などをやり、アプリのDBの向き先を切り替えて、アプリが正常に動作することを確認した。

しかし、ベンチを回したところ、スコアが1/3ぐらいまで下がってしまい、直し方が全然わからんのでMySQL8にアップグレードする作戦を断念し、アプリのDBの向き先を切り戻した。

相談タイム2 (~15:30)

このあたりはちゃんと記録を取ってなかったので曖昧だが、デプロイコマンドとプロファイラが入ったのでベンチを回し、プロファイラの結果を2人で眺めてたような記憶がある。

Dockerで手元にMySQLを立てる (~16:00)

初動でローカル開発環境を作ることになっていたが、うまくローカルでDBをセットアップできず放置されていたので、セットアップ作業を行った。

DBのセットアップがうまくいかなかったのは、utf8周りの設定がDockerのMySQLイメージとサーバーで異なっていたからだった。適当にmy.cnfを書いてやってDockerに食わせることでDBのセットアップに成功した。そして、以下のようなワンライナーを実行することで、所望のDBが立ち上がるということをebiに共有した。

$ docker run -p 0.0.0.0:3306:3306 --rm -e MYSQL_USER=isucon -e MYSQL_PASSWORD=isucon -v $(pwd)/custom.my.cnf:/etc/mysql/my.cnf -e MYSQL_DATABASE=isuumo -e MYSQL_ROOT_PASSWORD=my-secret-pw mysql:5.7

手元に開発環境を構築する(~16:30)

DBがあるだけではアプリの動作確認ができないので、静的ファイルをアプリから配信できるようにする改修 #4 を入れた。ただし、静的ファイルの中には大量の画像が含まれており、これをコピーしてくるのは無理そうだったので諦めた4

explainを見つつインデックスを貼る(~18:30)

手元にMySQLとテスト環境ができたので、いよいよ本格的にインデックスを貼り始めた。 基本的には、pt-query-digestみる→上位のクエリをExplainする→インデックスをいい感じに貼る→ベンチ回す→pt-query-digestみる→…の繰り返し。

  • #6 (~16:40)
    • SELECT * FROM chair WHERE stock > 0 ORDER BY price ASC, id ASC LIMIT 20;
    • filesortが出てたのでorder byで使ってるカラムにインデックス貼った
    • (price, id, stock)じゃなくて(price)にインデックス貼った理由はあんまり覚えてないけど、たしかそれだとうまくインデックスが効かなかったんじゃないかな
    • Using whereは依然として出てたが、スロークエリからは消えてくれたので次に行った
    • スコアは微増
  • #8 (~17:50)
    • SELECT * FROM estate WHERE (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) ORDER BY popularity ASC, id ASC LIMIT ?
    • 見るからにwhereがヤバそうなクエリだったが、EXPLAINしてみると真にヤバいのはそこではなく、filesortを出してるorder byの方だった
    • (popularity DESC, id ASC)にインデックスを貼りたいのだけど、MySQL5.7では昇順と降順を組み合わせたインデックスは貼れないらしかった
    • そこで、ちょっと工夫してpopularityに負号をつけて保持し、検索クエリをORDER BY popularity ASC, id ASCに書き換えた
    • (popularity, id)にインデックスを貼ったところfilesortが消えた
    • Using whereは依然として出てたが、スロークエリからは消えてくれたので次に行った
    • スコアは微増
  • #11 (~18:30)
    • SELECT * FROM estate ORDER BY rent ASC, id ASC LIMIT 20;
    • インデックスが適切に貼ってあれば20件フェッチするだけで済むが、インデックスがないので全件読んでfilesortしなきゃいけないという激ヤバのクエリ
    • (rent)にセカンダリインデックスを貼って解決した
    • これを入れたら点数はシャキッと上がる様子を見せたが、スコアが上がったところでベンチがfailするようになった
      • ebiが確認したところ別の修正(rentRangeIDで検索できるようにするやつ)のバグとのことだったので直してもらった
    • バグフィックスを入れたらスコアが1000点近くまで伸びた気がする

台数構成の変更 (~19:30)

パッと直せそうなスロークエリはあらかた潰した(位置情報系のやつは全く手が出せそうになかったので諦めた)が、依然としてMySQLのCPU使用率が高くアプリを圧迫してそうだったので、台数構成を変更したい気持ちになる。

他の2台は 1) MySQLアップグレードに失敗し文鎮化 2) MySQL8アップグレードに成功して重くなってる という状況だったので、DBを他に移すという手は取れなかった。詰んだかなと一瞬思ったが、アプリを他のインスタンスに移動すればいけるやんということに気づき、構成変更を試みる。

  • Makefileを改修して、スローログを取りに行くホストを変えたり、文鎮化したMySQLに触らないようにしたり、そもそもバグってたのを直したりした
  • env.shを編集したり、MySQLに外部接続可能なユーザーを作成したりして、別インスタンスのアプリからDBに接続できるようにした

構成変更に成功し、スコアが微増する。

ソートの処理をアプリに寄せる(~20:45)

再びスローログを見ると、以下のようなクエリがトップにいた。

SELECT * FROM estate WHERE latitude <= ? AND latitude >= ? AND longitude <= ? AND longitude >= ? ORDER BY popularity ASC, id ASC

このクエリを使っている場所を見ると、この結果をさらにMySQLに投げてN+1クエリをしているようだった。N+1クエリを直す方法がスパッと思いつかなかったので、とりあえずこのクエリを直してお茶を濁すことにした。

そもそもLIMITがついてないならMySQLでソートする意味はない。MySQLではlatitude,longitudeのインデックスで行をフィルタし、ソートはアプリ側ですればいいじゃんと考え、以下の2つの改修を入れた。

  • #14
    • まずはlatitude, longitudeにインデックスを貼る
  • #15
    • ORDER BYを外した上でGo側でソートする

これでMySQLのCPU使用率が下がってスコア上がらないかな〜と思ったけど、上がらなかった。

歓談(~21:00)

ベンチの調子があんまり良くなさそうだったのでベンチガチャは取りやめ、1300点が出たタイミングで競技終了とした。

二人で感想戦(~22:30)

競技終了直後から2.5時間ほど感想戦をした。感想戦の内容は、「振り返り・感想」で述べる

振り返り・感想

botアクセスを弾けなかった

「当日やったこと」にも書いたとおり、序盤でcat access.log | rg -o 'ua:.............' | sort | uniq -c みたいなワンライナーを書いて、botアクセスがほとんどないことを確認した上で、botアクセス弾きの実装を遅延していた。結果的にbot除外ができずに悔しい思いをしたが、初動としては間違ってはないと思う。

一方で、最適化が進んでスコアが上がったタイミングで、アクセスログを読み直す時間を取らなかったのはよくなかった。競技終了後にアクセスログをちょろっと読んで、botアクセスが激増していることに気づいたのだが、これに20時の段階で気づけていれば…と思い非常に悔しかった。来年は、めぼしいクエリにサクッとインデックスを貼り終わったぐらいのタイミングで、アクセスログをもう一度詳しく見直して、方針を再検討するようにしても良いかもしれない。

また余談だが、cat access.log | rg -o 'ua:.+reqtime' | sort | uniq -c みたいなワンライナーが書けていれば、uaにUUIDが振ってあり、uaから「ユーザー」を識別できることに気づけたかもしれない。rg -o 'ua:.............'だとuaの先頭n文字しか見れず、UUIDが振ってあることに気づかなかったのだった。

タスクの順序

タスクに取り掛かる順序にはもう少し最適化の余地がありそうと思った。例えば今回は、アプリにプロファイラ入れる → デプロイコマンド作る の順番で作業をしたが、プロファイラの導入に時間がかかっており、スキーマにインデックスを貼ってもデプロイできないということがあった気がする。デプロイコマンドの実装を先にやっておけば、インデックス貼るなどの作業がもう少し早い段階でできた可能性がある。そもそもデプロイコマンドがなければ、プロファイラを入れたアプリをデプロイすることができないので、このタスクの順序は逆にしない理由がない、と今になって思った。

また、ローカル開発環境のセットアップは最序盤にやるべきだった。アプリの改修をするにも、EXPLAINを見るにも、インデックスを試し貼りするにも、ローカル開発環境がないとリスクが高いし、作業効率が悪いので、必須である5

MySQLのアップグレード

競技開始時点において我々は、MySQLのアップグレードのことを「30分程度の手間をかけることで、スコアが変わらないか、もしかしたら伸びる」という施策だと認識していた6。つまり、不確実性は存在するが、それは良い方向にしかないと思っていた。しかし実際には、以下のような悪い方向の不確実性があった。

  • MySQLのアップグレードにかかる時間は、30分で済まない可能性がある
  • MySQLのアップグレードは、失敗する可能性があり、またそれは不可逆なものとなる可能性がある
  • MySQLのアップグレードは、スコアを下げる可能性があり、かつそれに対処することは我々のスキルでは難しい

いずれも、過去にISUCONにおいてMySQLのアップグレードを行ったことがあれば明らかになっていたハズのリスクである。逆に言うと、過去にやったことがないものには必ず不確実性が存在する。「過去にやったことがないものには必ず不確実性が存在する」というのは重要な教訓で、言葉としては理解していたが、今回実感することができた。今後に活かしたい。

ミドルウェアのアップグレードに手を出すなら、事前に練習して不確実性を下げる、マイナーバージョンの変更にとどめて不確実性を下げる、ミドルウェアを熟知したインフラエンジニア7を連れてくる、などの対策を事前に打っておく必要がありそうだと思った。

余談だが、実はMySQLのアップグレード自体はebiが事前に練習していたのだが、MySQL8に上げても動くというのを確認しただけで、スコアの変動までは検証できていなかったらしい。「過去問を通しで解くときに投入した」レベルじゃないと「練習した」とは言えないのだなと思った。現実的にはそれでも不確実性は潰しきれないとは思うのだけど、今後新しい施策を練習するときは、なるべく実戦に近い形で練習しようと思った。

台数構成の変更

アプリ・DBが1ホストに相乗りしている状態から、アプリホストとDBホストに分離するという台数構成の変更を行ったが、これは以下のような点で良かったと思う。

  • ぶっつけ本番だったのにうまくできた
    • 裏を返すと、リスキーな施策だったということでもある
    • 今年やってみたことでやり方が大体わかったので、来年以降は不安なく出来るはず
  • gracefulな構成変更ができた
    • 構成変更作業中でもベンチやデプロイが実行できる状態を保つことができた
    • 失敗したときにすぐに切り戻せる状態を保つことができた
  • ある程度の根拠を持って施策に取りかかれた
    • インデックスをある程度貼ったタイミングでtopを見ると、もともと100%に張り付いていたMySQLのCPU使用率が若干下がり、残ったCPU時間をアプリが喰っているように見えた
    • このことから、isuumoはCPUを使う処理がそれなりにあるアプリケーションであり、MySQLがこのホストからいなくなれば、アプリが使えるCPU時間が増え、より多くのリクエストをさばけるはずである、と考えた
    • 実際に、MySQLをアプリホストからどかしたところ、アプリのCPU使用率が80%ぐらいまで上がったため、この推論はあたっていたと思われる
  • スコアが伸びた

マニュアルをちゃんと読めてた

マニュアルをちゃんと読めてたのはよかった。bot弾きについては結局実装できず悔しい思いをしたのだが、マニュアルをちゃんと読んでいなかったらbotのアクセスを弾くという発想はそもそも出てこないので、前提条件的なところが満たせていたのは評価できると思う。もう一歩踏み込んで、「序盤はbotアクセスはきていなかったが、今の状態では来ているかもしれない」という発想ができるともっと良かった。

また、ベンチマーカーは画像等の静的ファイルにアクセスしない、という条件も読めていたので、nginxに無駄なキャッシュ設定を入れて時間を溶かすみたいなこともなかった

インデックス関連

去年ISUCON9に参加したときは、pt-query-digestの読み方も、EXPLAINの読み方も、効果的なインデックスの貼り方もわからないという状態だった。それに比べて今年は、EXPLAINを読み、ある程度効果的なインデックスを貼ることができた。例えば、以下のようなところは、去年の段階であれば全く対応できていなかったところだと思う。

  • スローログに SELECT * FROM estate WHERE (ここに超長い条件文が入る) ORDER BY hoge, fuga LIMIT 10 みたいなクエリが出てたときに、EXPLAINをちゃんと読んだことで、超長い条件文をスルーしてhoge, fuga にインデックスを貼れた
  • BTreeの構造を理解していたことで、 ORDER BY popularity DESC, id ASC みたいなクエリに対して降順インデックスを試みたり、popularityに負号をつけてASCにしたりできた

これらの課題に対応できるようになったのは、この1年間の勉強や仕事の経験が活きていると強く感じた。

rentRangeIDによる直接検索

? < rent AND rent <= ?というクエリをrentRangeIDで直に検索できるようにする、という改修をebiがやっていたが、スコアはそれほど上がらなかった。おそらくこのクエリ自体は、rentにインデックスを貼ることで同程度の改善が見込めたものと思われる。しかし、この改修によって? < rent AND rent <= ?rentRangeID = ?というクエリで等価検索できるようになり、rentRangeIDを含む複合インデックスの性能が向上する、ということが社内での感想戦中にわかった。例えば WHERE ? < rent AND rent <= ? ORDER BY rent, id というクエリはfilesortを避けられないが、 WHERE rentRangeID = ? ORDER BY rent id というクエリは (rentRangeID, rent) にセカンダリインデックスを貼ればfilesortをせずに済む。インデックスは奥が深い。

また、別の話として、この改修はそれなりに複雑なものだったということがある。ぱっとレビューしても正しさを担保できず、ベンチマークを1度回してみて通ったのでOKとした。しかし、後に別の改修を入れてパフォーマンスが改善した際、この修正のバグが原因となってベンチがFailするようになった。このときは結局、ebiにバグを直してもらい事なきを得た。

ここから得られる反省は、ISUCONのような急いでる状況で、複雑な実装をするのはリスクが高いということだ。しかし、上述の通り、これは必要な修正だったと思われ、単に「リスクが高いから実装すべきでない」とも言い難い。また、ベンチマークを回して動作確認してからマージしているわけで、エンバグリスクを可能な限り低減してもいる。つまり、「実装すべきだったし、リスク低減策も打っている」ということで、これを避けることはできなかったと思う。

この対策としては、「バグったときのリスクが減るような実装」を意識するのが1つの手段としてあると思う。例えば、一行コメントアウトしたら元の挙動に戻るようにしたり、リトライで治るようにしたり(これはISUCONでは関係ない)、バグってもユーザー影響が小さいように実装したり(これもISUCONでは関係ない)、など。

秘伝のタレ・インフラ知識

事前にnginx・MySQL・カーネルパラメーターの秘伝のタレを入手していたのだが、当日はebiに無断でこれを入れなかった。相談せずにやってしまったことは反省している。なぜ私が当日秘伝のタレを入れなかったのかというと、理解していない秘伝のタレを導入することに強い抵抗を感じたためである。

抵抗を感じた理由の1つとして、知識不足な状態で秘伝のタレを入れると、秘伝のタレが原因で問題が起きたときに解決が難しくなると感じたことがある。特にMySQL 8のアップグレードに失敗して切り戻した直後だったこともあり、この懸念を強く感じていた。例えば、今回のホストの設定ではSwap領域がなかったため、MySQLのバッファプールを雑に増やすとOOMキラーが出現して詰む、という問題が起きた可能性がある8。しかし、このような問題は、秘伝のタレを入れても入れなくても起き得、どこかのタイミングで対処せざるをえなくなるものだと思う。つまり、根本的には、インフラ周りの知識が我々チームに足りていないという問題を解決する必要がある。

位置情報クエリ

nazotte検索については、位置情報の取り扱いを全然知らなかったので対応不可能と思い、思考停止してしまったのが敗因だったと思う。初見だったST_CONTAINS関数に脳をやられてしまい、クエリを詳しく見るのを避けてしまった。また、アプリ側に処理を移すことも一瞬考えたが、あからさまに競プロ的な問題だったのでこちらも避けてしまった。

しかし、冷静に考えると、nazotteのN+1はちょっと考えれば消せたはずである。また、ST_CONTAINS についても、その場でMySQLのリファレンスを引いて、対策を考えることはできたかもしれない。競プロ的問題であっても、既存のライブラリを導入するなどすれば、競プロ的問題を自前で解かずに対策することは可能だった。つまり、拒否反応をコントロールして冷静になることで、一定のチューニングは行えたのではないか。この辺は「未知の問題に即興で対応する能力」がまだまだ足りておらず、修行が必要なところかもなと思った。

一方で、そもそも、ST_CONTAINS のクエリがボトルネックであるということを、自分がスローログから読み解けただろうかというと、ちょっと怪しいと思う。トップ1のクエリは、「当日の動き」に書いたように、latitudeとlongitudeでSELECTしてくる部分だった。多分、トップ1だけじゃなくて、トップ10ぐらいのスロークエリを全体的に眺めたりすると、この辺の処理がまるっと重いのがわかり、アプリに負荷を逃がすとか、いい感じのインデックスを貼るとかの動きが取れたかもしれない。そもそも、いくつかのクエリが同じぐらい重いという状況であれば、トップ1かトップ5かという細かい順位は意味を持たない。順位だけでなく、各々のスロークエリが「どのぐらい重いのか」「どのように重いのか(回数なのか、time/reqなのか)」「なぜ重いのか」というのを見て、その後で対応方針を考える、としたほうがいいかもしれない。

再起動試験

構成を変更してアプリとDBを別インスタンスにしたことによって、アプリ→DBの順に起動するとアプリが起動しないという問題が発生したと思われる9

一番大きな反省としては、一度も再起動試験をやらなかったということである。開始直後、構成変更時、終了1時間前あたりのタイミングで、再起動試験を行うべきだった。特に今回は、最後のベンチを終えて、終了10分前ぐらいになったタイミングで「そういえば再起動試験してないな」ということに気づいていたので、あのタイミングでおもむろに全インスタンスを再起動して動作確認をしていれば気づけたはずだし、systemdに1行足すぐらいのことはできたと思う10

来年は、上記の通り再起動試験を適当なタイミングでやりつつ、「アプリが起動時にDBにつなぎに行って死ぬ」問題に関しては、systemdにRestart=alwaysを入れることで解決は可能そうなので、コピペで行けるように準備しておきたい。

初手でボトルネックが見つからなかった件

序盤、アプリケーションのボトルネックを特定することにちょっとこだわり過ぎたという反省がある。今回の問題は突出して遅いエンドポイントがなかったので、「まずはここを治そう」みたいな見当がつけられなくて結構悩んだ。結局、地道にスロークエリにインデックスを貼っていくことでスコアを改善することができたのだが、これはつまり、ISUCON10の予選問題には、アプリケーション的なボトルネックが存在しない代わりに、MySQLというボトルネックが存在していたということではないかと思う。

第一回のベンチマークを回したタイミングで、MySQLのCPUがサチってることはわかっていたので、インデックスを張って負荷を下げるというのは、結果的に割と正しい方針だった。来年は、 「ボトルネック」の捉え方を少し変えていこうと思った。

その他

  • sharding思いつけなかった…
    • そんなカジュアルにshardingできるものだと思ってなかったけど、テーブル構造のシンプルさを見て気づくことはできたかもしれない
    • read replica立てるみたいなのが有効になるシーンは今後出てくるかもしれないので覚えておこう(やるとは言ってない)
  • SELECT FOR UPDATE消すやつ
    • スロークエリに出てなかったのであんまり考えてなかった
  • 複雑な検索クエリじゃん -> Elasticsearch入れよ と安易にならなかったのはいい判断だった
    • Explainを見て、そんなに重くなさそうだったのでスルーしたのだった
  • 外部モニタを2枚使って、3画面で臨めたのは地味だがかなり効果があった
    • なお、そのうち一枚は4Kモニタなので画面が広い
    • 画面共有で相談しながら、ブラウザ開きながら、コード書きながらサーバーのhtop見るみたいなのができたのはかなりUX高かった
  • alp -q オプションで、query string込でリクエストを集計できるのを知らなかった
    • 来年は使っていきたい
  • 社内のISUCONに出た人がzoomに集まって感想戦をやったのだが、効果がないと思っていた施策が実は別のところに効いてたのが明らかになったり、他のチームの戦略とかを質問しつつ聞けたりして、非常に学びになったので良かった
  • 今回はGoだったのでプロセスキャッシュがサッと入れられたが、Rubyとかのマルチスレッドが弱い言語だと厳しそうなので、RedisとかMemcachedをサクっと立ててキャッシュにするみたいなのができる必要がありそう。
  • MySQLのクエリキャッシュ全然知らなかった…
  • ローカル動作確認環境を作れたのは良かった
    • インデックスの試し貼りとかがサクッとできた
    • アプリの改修を入れたときも「最低限動く」ぐらいの動作確認ができたのは安心感があるので良い
  • スローログに出てないクエリがアプリ側を律速しているということがあった
    • おそらく、アプリ側を見て行うチューニングと、スロークエリの改善には以下のような特性があるんじゃないかと思った
      • アプリのプロファイラを見て遅い処理を改善すると、 そのエンドポイントの 性能が改善する
      • スロークエリにインデックス貼るとMySQLの負荷が下がり、クエリのレスポンスが全体的に速くなり、 アプリケーション全体の 性能が改善する
  • そもそもスローログってどういうクエリが出力されてたんだっけ、というところを知らないことに気づいた
    • CPU時間を食ってるクエリなのか、実行時間が長いクエリなのか、実行回数が多いクエリなのか、等
    • 調べたところ、実行時間の累計が長い順にソートされているようだった
  • クエリが重い理由から対策方法を考えるみたいなこともできたかも
    • 例えば1クエリあたりの実行は軽いが、回数が多いクエリをアプリ側でキャッシュしたり
  • そういえばロック待ちが頻発してるクエリの特定方法とか知らないな…
    • 来年までに詰めておきたい

まとめ

反省点はたくさんありつつ、特にSQL力に関して、去年に比べて大幅な成長を感じられたISUCONでした。業務は大変だけど、確かに力がついていて、1年間仕事してきてよかった〜と素直に思いました。来年までの1年間で、更に力をつけていきたい。

  1. ちなみに、おそらく再起動試験はFailしたと思われるので、仮にボーダーを超えていたとしても本戦には出場できなかったものと思われます。これについては振り返りで述べる。 

  2. GolangのWebアプリをいい感じにプロファイルするならfgprofを使うと良さそうというメモを書いた 

  3. POST /initializeでキックしようと思ってたのにfunc init()の中でキックしてたみたいな事故はあった 

  4. なお、サーバー上では、静的ファイルへのリクエストはnginxが処理するので、この改修を入れてもベンチマークには影響がない(そもそもベンチマーカーは画像にアクセスしないが)。 

  5. 社内感想戦で聞いた感じだと、3台のホストのうちの1台をテスト用にしてるところもあったようで、賢いなと思った 

  6. MySQL 8.0: MySQL 5.7よりも最大2倍高速 

  7. これは結局、不確実性を「インフラエンジニア」に丸投げしてるだけで、本質的には不確実性は下がっていない気がする 

  8. あくまで一例であり、実際に起きるかは知らない 

  9. 試してはないがほぼ確実に死ぬ 

  10. 予選落ちはほぼ確実だったのでどうでもいいといえばどうでもいいが