こんにちは、バックエンドエンジニアのmakinoです。先日行われたISUCON11予選に参加し、10位で予選突破することができました。
私が所属している「カレーおじさん」チームは、前職の同僚の @sugaret, @lazydg と組んでいるチームで、ISUCON参加は3回目、予選突破は去年に続いて2回目になります。
それでは、さっそくISUCON11予選を振り返っていきたいと思います。
使用したツール
alpはaccess log解析、pt-query-digestはslow query解析で毎度使っているものです。
今年はCloud Profilerを新たに使ってみることにしました。いつもはベンチマーク実行時にpprofを手で叩いており面倒だったのですが、Cloud Profilerは継続的にプロファイリングを実行し収集してくれ、GCPコンソール上でさくっとプロファイル結果を見ることができるので便利でした。
予選の問題
椅子(ISU)からコンディションが送られてきて、そのコンディションをグラフ等で可視化するIoTサービスでした。
ISUCONDITION アプリケーションマニュアル · GitHub
あなたの大事なパートナーである ISU が教えてくれるコンディションから、そのメッセージやコンディションレベルを知ることで、大事な ISU と長く付き合っていくためのサービスです。
YouTube配信での事前説明では、世界観が独特でどんなサービスなのかイメージしにくかったのですが、マニュアルが丁寧に書かれていたので競技が始まればスッと理解することができました。 また、今回はスコア計算のロジックが複雑で、読み解くのに時間がかかりました。
当日やったことの振り返り
レポジトリはこちらです。使用言語はGoです。 github.com
indexの追加
計測ツールを仕込んでベンチマークを実行すると、DBの負荷が高かったので、まずはクエリのチューニングをすることにしました。
1番時間がかかっていたクエリがこちらです。
SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = 'e1512b1d-07ba-4dad-8104-22f236521f82' ORDER BY `timestamp` DESC LIMIT 1\G
適切なindexがなくfilesortになっていたので、jia_isu_uuid
, timestamp
にindexを追加しました。
isucondition add index · tatsumack/isu11q@a48f4a8 · GitHub
クライアントサイドPrepareを有効にする
slow queryを見るとPrepare
が大量に発行されていました。sql.Open()
に渡すdsnにinterpolateParams=true
を追加することで、クライアントサイドでPrepare
を完結させることができ、ラウンドトリップの削減とDBの負荷を下げることができます。*1
dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?parseTime=true&loc=Asia%%2FTokyo&interpolateParams=true", mc.User, mc.Password, mc.Host, mc.Port, mc.DBName)
client prepare · tatsumack/isu11q@61292fa · GitHub
echoのdebugモードを無効にし、ログを切る
Cloud Profilerを見たところ、ログの書き込みに時間がかかっていました。今回、Goではechoというフレームワークが使われていたのですが、debugモードが有効になっていると整形されたjsonをログ出力し高負荷になってしまうというのを前回のisuconで学んでいたので、debugモードを無効にしログも切ってしまいました。
echo debug log off · tatsumack/isu11q@d1ad819 · GitHub
icon画像をDBから剥がす
icon画像のbinaryがDBに格納されていたので、ファイルに書き出すようにしました。
change icon db to file · tatsumack/isu11q@ae2da92 · GitHub
また、ベンチマーカーがConditional Getをサポートしているとのことだったので、レスポンスにCache-Controlヘッダを設定するようにしました。
c.Response().Header().Set("Cache-Control", "max-age=3600")
cache control · tatsumack/isu11q@4460858 · GitHub
isu_condition
をbulk insertする
isu_condition
テーブルへのinsert回数が多く、実装を見たところ1件ずつinsertするような処理になっていたので、bulk insertするようにしました。
condition bulk insert · tatsumack/isu11q@db31058 · GitHub
アプリとDBを別のサーバーに分ける
複数台構成の最終形として、アプリとDBは別のサーバーに分けることは確実だろうということで、アプリとDBを別のサーバーに分ける対応を行いました。
getIsuConditionsFromDBにLIMITの絞り込みを入れる
中盤になるとgetIsuConditionsFromDB
の処理が支配的になってきました。実装を見ると、指定されたjia_isu_uuid
のisu_condition
を全件取得するようになっており、LIMIT
で絞り込むようにしました。が、実はこのときの対応が不十分だったことに後ほど気付きます。
add limit · tatsumack/isu11q@6c5adeb · GitHub
POST /api/condition/
drop割合の調整
アプリとDBの負荷が下がってきたので、 POST /api/condition/:jia_isu_uuid
で一定確率でリクエストを落とす処理の調整を行いました。
最終的にはdrop割合を0にしています。
GET /api/isu/:jia_isu_uuid/graph
timestampで絞り込み
グラフの表示の際にも、指定されたjia_isu_uuid
のisu_condition
を全件取得するような実装になっていました。指定された日のデータだけで十分だったので、timestamp
で範囲を絞り込むようにしました。
fix graph date · tatsumack/isu11q@21c582e · GitHub
1時間あたりのpost数を制限
短時間に大量のデータを送ってくるクライアントがあり、マニュアルを読むと1時間あたり20件のデータがあれば十分だということが分かったので、20件を超えるデータに関しては無視する処理を入れました。
drop per hour · tatsumack/isu11q@e80a112 · GitHub
getTrend の絞り込み
終盤は GET /api/trend
が支配的になってきていました。
実装を見ると、こちらも指定されたjia_isu_uuidに対して全件取得するような処理になっており、最新のisu_condition
を取得すればいいだけなのでLIMIT 1
を追加しました。
trend limit 1 · tatsumack/isu11q@1ddfb74 · GitHub
condition levelの正規化
上記のGET /api/trend
の改修を入れたことによって、GET /api/condition/:jia_isu_uuid
で不整合が発生しFAILするようになってしまいました。
getIsuConditionsFromDB
にLIMIT
の絞り込みを入れた際にcondition levelの考慮が抜け漏れており、condition levelで絞り込む際に正しくデータを取得できない状況になっていたのが原因でした。GET /api/trend
の改修によってユーザーが増え、さらにisu_condition
テーブルのレコードが増えたことによってこの問題が顕在化したのだと思われます。
isu_condition
テーブルに level
カラムを追加し、condition levelの絞り込みが正しく動くように改修をしました。
level · tatsumack/isu11q@45a149d · GitHub
上記の修正を入れた時点で、スコアは28万点になりました。 まだ2台しか使っていない状態でしたが、残り時間が20分しかないのと、このスコアであれば予選突破ラインは超えているだろうと判断し、手を止めることにしました。
感想
今年も中盤くらいから予選突破のボーダーラインをウロウロしていて、良い緊張感をもって競技を楽しむことができました。
今回の問題はあからさまにダメな処理が各所に仕込まれており、目についたところから手を付けているとあっという間に時間がなくなっていたと思います。 「推測するな計測せよ」の格言通り、計測によってボトルネックを特定し、改修の優先度を見極めながら取り組むことが大事だということを再認識させられる良い問題でした。
また、ベンチマーカーも終盤のアクシデント以外は安定しており不可解なエラーなどもなく、とても快適に競技に取り組むことができました。 運営のみなさま、ありがとうございました。
去年の本戦ではFailしてしまったので、今年は正のスコアを取れるように頑張ります。
最後に
ミラティブは今年のISUCON11に協賛しており、社内にもISUCON部ができました。
エンジニアを積極採用中なので、ISUCONが好きな方の応募をお待ちしております!