解説みながらISUCON12予選でSQLiteのままRuby実装で8万点にいけた

isucon.net

2022年に開催されたISUCON12予選を「ISUCON12 予選の解説 (Node.jsでSQLiteのまま10万点行く方法) : ISUCON公式Blog」をみながら試してみて、SQLiteのままRuby実装で86,910点(予選No1水準)までいけました。どのような流れでスコアを高めていけたかと簡単にやったことを紹介します。

ISUCON12予選とは

ISUCONは、「Iikanjini Speed Up Contest」の略でWebアプリケーションのパフォーマンスを向上させてスコアを競う競技です。ISUCON12ということでいままで12回実施されています。2023年もISUCON13が予定されています。

ISUCON12関連の記事はこちらにまとまっています。

また、ISUCON12予選のスコア(参考値)は以下のようになっています。予選突破相当のスコアは24,000点あたり、予選トップ相当のスコアは86,740点になっています。

ISUCON12予選のスコア(参考値)
引用元: ISUCON12 オンライン予選 全てのチームのスコア(参考値) : ISUCON公式Blog

ISUCON12予選でどのような流れでスコアを高めていけたか

大まかな流れとしては、最初は、N+1やインデックスささってない、pumaのworker数の調整など比較的簡単に対応ができ今後のボトルネックになりそうな部分を対応します。次に、ロックの取り方や時間がかるID生成など仕様やコードを確認しつつ対応が必要なことを改善していきます。そして、最後にDBデータの圧縮やレコードを先に作成して読み込み時のレスポンスを高めるなどより手がかかるものをやっていきました。

注意点としては、必ずしも改善したからといってスコアは一辺倒にはあがっていきません。理由としては、API同士の競合が起きていたり、マシンの負荷などのボトルネックがあるため、一辺倒にスコアはあがっていきません。そのため、alptoppt-query-digestなどで計測し、改善した項目が狙い通りに改善されたかを確認するのがよさそうです。

ISUCON12予選でスコアをあげるためにやったこと

まずサーバー環境として、競技マシンは3台あり、それぞれにNginx, Web Application(各言語で実装されて選べる)、MySQLSQLiteがそれぞれ配置されています。ベンチマーカーから1つのサーバにリクエストが送られてくるので、ロードバランスやデータベースの共有などは自分で設定していく必要があります。

私は、ISUCON12 予選の解説 (Node.jsでSQLiteのまま10万点行く方法) : ISUCON公式Blogの内容を上からたどりながら実装していきました。実際のコード例はないのでコードは自分で考えて実装する必要があります。
Rubyの場合の初期スコアは1,921点で、1台でスコアをあげていくことで29,124点(予選突破相当のスコア)になりました。そして、3台構成にすることで 86,910点(予選トップ相当のスコア)を達成できました。

1台構成でスコアをあげていく

  • 初期スコア(1,921点)
  • adminDB visit_history にINDEXをはる(1,665点)
  • Ranking APIが重いのでひとまずループクエリをなくす(1,768点)
  • Score APIの追加のループクエリをなくす(3,713点)
  • pumaのworkerを32、スレッドを1にする(6,681点)
  • アトミック書き込みのためのflockトランザクションに変更する(7,483点)
  • dispenseIDでMySQLを使うのをやめる(7,012点)
  • adminDB visit_historyの初期データをコンパクトにする(6,636点)
  • ログのフラッシュを1秒ごとにする & バイナリログ無効化(6,377点)
  • Finish APIでBillingReportを生成する(14,738点)
  • tenantDB player_scoreにINDEXをはる(20,952点)
  • Ranking APIでランキング集計するのをやめる(26,802点)
  • Nginxワーカーコネクション数を3倍にする(29,124点)

1台から3台構成にする

  • 1台から3台構成にする(77,783点)
    • add tenant APIでtenant dbを作らない、代わりに、connect_to_dbでファイルがなければつくる
    • nginxの+worker_rlimit_nofileを12000に設定。socket() failed (24: Too many open files) while connecting to upstream がでていたから
    • 1台目のnginxでapi配下を2台目と3台目にホストの長さに応じて負荷分散する ※tenant dbがsqlite3なので単純なロードバランシングはできず
    • 2,3台目から1台目のMySQLに接続するようにする。また、1台目のMySQLはローカルのみしかアクセスできないのでフルオープンする
  • ログ出力をおさえ、何回かベンチマーク実行(86,910点)

詳細が気になる方はISUCON12のチューニングメモ(初期 1345 -> 最終スコア 86910) · GitHub にメモを書いていますのでご覧くださいませ。

まとめ

解説をみながら今回のISUCON12予選で、SQLiteのままRuby実装で8万点というスコアを出すことができました。解説みながらでも試行錯誤しつつ12時間ほどかかったので、これを本番の競技で実施できたトップチームの方々はすごいなーと改めて思いました。 しかし、練習でできないことは本番ではできないので、少しずつ過去のISUCONにトライしていきたいです。🪑