Mirrativ Tech Blog

株式会社ミラティブの開発者(バックエンド,iOS,Android,Unity,機械学習,インフラ, etc.)によるブログです

ISUCON11予選に参加して10位で予選突破しました

こんにちは、バックエンドエンジニアのmakinoです。先日行われたISUCON11予選に参加し、10位で予選突破することができました。

isucon.net

私が所属している「カレーおじさん」チームは、前職の同僚の @sugaret, @lazydg と組んでいるチームで、ISUCON参加は3回目、予選突破は去年に続いて2回目になります。

それでは、さっそくISUCON11予選を振り返っていきたいと思います。

使用したツール

alpはaccess log解析、pt-query-digestはslow query解析で毎度使っているものです。

今年はCloud Profilerを新たに使ってみることにしました。いつもはベンチマーク実行時にpprofを手で叩いており面倒だったのですが、Cloud Profilerは継続的にプロファイリングを実行し収集してくれ、GCPコンソール上でさくっとプロファイル結果を見ることができるので便利でした。

f:id:tatsumack:20210823153221p:plain
初回ベンチマーク実行時の様子

予選の問題

椅子(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_uuidisu_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_uuidisu_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するようになってしまいました。 getIsuConditionsFromDBLIMITの絞り込みを入れた際に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が好きな方の応募をお待ちしております!

www.mirrativ.co.jp

speakerdeck.com