isucon
isucon8

ISUCON8の予選突破できました

ISUCON8で「†漆黒のすぐちむ†」という名前で出場しました.

チーム構成

漆黒のいしかわ:自分です.普段はバックエンドのエンジニアをしています.(ISUCON出場は3回目)
漆黒のはせべ:同じグループ会社のインフラエンジニアです.(ISUCON初出場)
ISUCON8 に初参加してみました(長谷部さんの記事)
四国のなかじん:自分と同じ会社のバックエンドのエンジニアです.(ISUCON出場は2回目)
ISUCON8で予選突破しました(中陳の記事)
自分と中陳がプログラムの修正を主に担当して、インフラ周りを長谷部さんに任せる作戦でした.

使用言語

  • PHPで決めていました
    • 理由は、普段から使い慣れている言語を使おうぐらいの理由でした

コード修正以外のやったこと

Githubにソースコードをアップしたので,そちらを参照していただけばソースコードは理解して頂けるかと思いますのでご参照下さい.
https://github.com/YasunoriISHIKAWA/isucon8
※当日のコミット履歴やコメントやコードなども整形せずに上げてます.

  1. 開始と同時にベンチマークを回す
  2. Perlで動いていたものをPHPに修正
    1. このあたりは長谷部さんが対応してくれました
  3. 実際に課題となっているサイトを回遊してみる
    1. サインアップして実際にチケットを予約したり,キャンセルしたり,管理画面を見てサービス把握に努めました
    2. 個人的に非常に収穫がありました.後に関数(主にget_eventやget_events)で必要のない処理がある(改善の余地がある)という手がかりになりました.
      1. 余談ですが,映画のタイトルのシュールさで盛り上がってました
  4. アクセスログを見る
    1. ベンチマークテストを見てアクセスログなどを見ていました
    2. 個人的に大きな収穫は得られませんでした
    3. 何よりh2o全然知らないので困ったなというのが正直な所でした
  5. データベースのスローログを見る
    1. 0.01秒以上かかっているSQLのログを監視していました
      1. 流石に0.01秒だとなんでも引っ掛かるなということで後に0.1秒に引き上げました
  6. サーバの台数とスペック把握

    1. 同じサーバスペックのサーバが3台
      1. CPU2コア
      2. メモリ1GB(SWAP2GB)
    2. 潤沢なリソースではないねぐらいのチームの認識でした
    3. サーバにはそれぞれisu-1,isu-2,isu-3と名前をつけていました
      1. 後述しますが,最終的には以下のような構成になりました. スクリーンショット 2018-09-20 18.47.57.png
  7. レギュレーションの確認

    1. チケットの予約やキャンセルに対してのポイントが高いぐらいの認識でした
      1. 結論:予約・キャンセルをいっぱい出来るように他に邪魔になる処理を軽くすれば良いというチーム認識でした
    2. このあたりは、長谷部さんがレギュレーションを熟読してくれていたので非常に助かりました

やろうとして出来なかったこと(しなかったこと)

  1. h2oをnginxに変える
    1. admin画面のcsvダウンロードの部分がどうしても通りませんでした
  2. h2oで特定のpathを振り分ける
    1. adminはアクセスが少ないもののwebサーバへの負担が重そうな処理があったので振り分けて速度を計測したかったです
    2. 誰もh2o触ったことがなかったので基本デフォルトの設定で行きました
    3. アクセスログに関しては、長谷部さんが修正してくれました
  3. DBをMySQL8に変更
    1. 長谷部さんが単独で検証してくれていましたが、一部動かない機能があり断念しました
  4. Redis or memcachedの導入
    1. 更新データ系をRedisに移すのは時間的にハイリスクに思えたのでなし
    2. 更新されないデータ系はインストール作業などを考えるとハードコーディングの方が費用対効果が良さそうなのでなし

コード修正でやったこと

13:30時点までサービスの把握や状態確認などで時間を潰してしまいコード修正などは手付かずでした.
特に手がかりもなく一旦気分を変える意味もこめてランチ休憩を取りました.
14:00になりそろそろコードに手を入れようとなり下記の対応を取ることにしました

  1. 石川:get_eventが様々なアクションから呼ばれていて何がボトルネックかが分からなかったので,呼び出し元ごとに専用のget_eventメソッドを(例えば,get_event_for_deleteのように)作成していく
  2. 中陳:気になる処理の時間計測とループ処理の改善
  3. 石川:データの更新のない情報(sheets)をハードコード化
    1. sheetsは以下のような特性がありました
      1. sheetsはeventsによって変化しない
      2. sheetごとのpriceはrankによって変化する
      3. sheetのidは1から1000までの連番かつrank内のnumも連番
        1. DBに入れなくても計算でidからrankとnumを取得可能
    2. 残席数の取得方法変更
      1. 元々の実装はsheetsをループさせて予約されているかをチェック
      2. 各ランクごとのsheetの最大数(Sランクなら50)から予約されているsheetの数を引く
  4. 長谷部:isu8-2にデータベースを移行
    1. このあたりで1台で構成していたサーバー内でphp-fpmとMariaDBがCPUを奪い合い始めてました
  5. 中陳:/api/users/{id}のget_event周りを改修
  6. 石川:スロークエリが出ている部分の改修
    1. reservationsのindexの見直し
  7. 石川:/admin/api/reports/events/{id}/salesの改修
    1. スロークエリを出していたので取り敢えず,sheetsをDBを利用しないように修正
    2. eventsテーブルとのJOINは意味がないことが分かったのでJOINを削除
  8. 中陳:get_eventを改修
    1. sheets分SQLが発行されていたのを予約されているsheetsを先に取得することでSQLの問い合わせを削減
    2. reservationsのインデックス修正
  9. 石川:スロークエリが出ている部分の改修
    1. /admin/api/reports/salesの改修
    2. 基本的には/admin/api/reports/saleと同じ
    3. eventsの取得を別に切り分けて1回のSQLで取得しておく
  10. 石川:reservationsのFOR UPDATEを削除
    1. テーブルロックが発生していたので対応
  11. 中陳:計測ログを削除
  12. 長谷部:スロークエリの出力を停止
  13. 長谷部:h2oのログをもとに戻す
  14. 長谷部:isu8-1,2を再起動

上記までが17:30までの流れでした.
最後の再起動は,30分前までで一回開発終了して再起動のチェックに当てようというのを長谷部さんから提案されていたのでこの辺は,半分決まりみたいなところあるので自分も中陳もその時間に合わせて進めていました.

個人の感想

最初はここがボトルネックというポイントが明確にあると手をつけやすいのですが,怪しいところは色々あるけど修正しても改善される自信がないというのが正直なところでした.
get_eventの役割を見直し始めてからスコアが少しずつ上っていったように思います.
一度改善されると次にボトルネックが出てきて改善して・・・を繰り返した結果が今回の結果に繋がったのかなと思います.
あとは,ベンチマークテストの結果が全てだったので基本的には振る舞いは変更せずに内部を改修する,要はリファクタリングに特化しようと努めました.
この辺りは,普段の業務であればユーザビリティが変わらなければ,多少の仕様変更は許されることがありますが,ISUCONはそういう世界ではないので割り切って対応していました.
これが勝因ですという事が明言出来るといいのですが,正直なところは分かりませんが,最初の取っ掛かりが良かったのかなといった感じです.

最後に

運営の皆さん毎年楽しい大会を提供してくださりありがとうございます!
毎年この大会を楽しんでいます!(自分のポイントは毎年無残な感じでしたが)
本選は初参加なので楽しみながら頑張りたいと思います!