ISUCON12 予選突破しました #isucon
概要
全体指針の検討
最初にざっくりアプリを触ってみて、マルチテナントと沢山のAPIが入り混じっていて大変難しいと思いながら、コードとクエリを確認して明らかに直した方が良さそうな所として、2か所発見しました。
1か所目がMySQL側のvisit_historyで、これは集約して来訪時刻の最小値のみが使用されているため、最小じゃないレコードは不要そうでした。2か所目がSQLite側のplayer_scoreで、スコアの最新値のみが使用されているので、そうじゃないレコードは書き込まなくても良さそうでした。そこで、2か所とも、初期データとアプリの書き込み処理を、どちらも修正する事にしました。
また、SQLiteをMySQLに載せ替えるかという事に関しては、意味深な変換スクリプト (~/webapp/sql/sqlite3-to-sql) が同梱されていた事から、そもそもそういう問題ならこの機能は自力で用意させるはず?とメタ推理し、このルートは大変不審であると考えたので、誘惑に惑わされず、載せ替えは無しで進める事にしました。
visit_historyの改良
visit_historyに関して、初期データを更新 (@k_enokiがいい感じにやってくれました) し、アプリ側でも2件目以降の登録を阻止するために、(tenant_id, competition_id, player_id) でユニークインデックスを設定し、Duplicate Key Errorを無視するコードをアプリ側に追加して、これはスムーズに完成しました。
スコアは初期スコアからほぼ変化なし (4000程度) です。player_scoreの改良
player_scoreに関しては、初期データを更新するため、~/initial_dataのSQLiteの各ファイルに以下のようなクエリ:
delete from player_scorewhere id in (select id from (select id, row_number() over (partition by tenant_id, player_id, competition_idorder by row_num desc) as rnfrom player_score)where rn > 1);vacuum;
を流して内容を更新 (9割程度削減できたはず?) して、アプリ側は、CSVアップロードで全レコード追加した風のレスポンスを返しつつ、DBには最新値のみを追加するという処理に変更しました。
この過程で、ファイルの各行でのループ中で、breakする箇所を見落としていたせいで、rowCountとしてrowNumを流用して返したところ、件数カウントのチェックに違反するバグを仕込んでしまいましたが、件数のカウンターを別途用意することで回避し、手こずりましたが完成しました。
この時点でもスコアは初期スコアからほぼ変化なしです。
テナント排他的ロックの改良
テナント毎に懐かしいflockによる排他的ロックが行われ、それによりダーティーリードを阻止していることは理解したのですが、SQLiteのままトランザクションを使うコードに変更して、ロックを外せるのか、良く分かっていなかったので@kotaroyに調べてもらいました。並行して、ロックの必然性は不明だが、一旦読み取りの場合は排他的ロックは必要ないはずなので、 sync.RWLock に交換しておきました。スコア向上はイマイチでした。
調査の結果、SQLiteのままでトランザクションは対応出来そうという事になり、コードの修正を試みましたが、上手く動かず、未完のままいったん保留しました。
ボトルネック特定から提出版作成
@k_enokiがNew Relic Go AgentをEcho Integrationで導入してくれて、TransactionsのMost time consuming順により、ボトルネックとなっているAPIは以下のように特定
- GET /api/player/competition/:competition_id/ranking
- GET /api/player/player/:player_id
出来たのですが、処理内容が盛り沢山で、まだはっきりどこが遅いのかわからなかったため、処理段階ごとに Instrument Go segments を細かく埋め込んで調べたところ、排他的ロックが問題となっている事がはっきりしました。
こげ茶色がtenant rwlock |
そこで、未完となっていたスコア登録のトランザクション導入ブランチについて、処理ボトルネックだという確信が深まったので、渋々再度修正を試みたところ、17時過ぎについに完成し、ようやくスコアが進捗 (17000程度) しました。
その後、MySQLサーバーを別サーバーに向ける改良を @k_enoki が素早くやってくれて、各種デバッグ情報の出力を止め、再起動試験をし、24000 程度のスコアが出るようになった状態で最終提出版としました。
さいごに
素晴らしい出題と大会運営、スポンサー企業・個人スポンサーの皆様には大変感謝してます。本選でもよろしくお願いします。
コメント
コメントを投稿