🪑

ISUCON14に参加しました(全体214位)

2024/12/20に公開

本記事はフラー株式会社 Advent Calendar 2024 の20日目の記事です。

はじめに

今年もISUCON14に参加しました。去年に引き続き2回目の参加です。

今回は社内でISUCON参加者を募り、フルメンバーの3人で参加しました。1人は初参加の新卒メンバーです。

チーム名は決めるのが面倒だったため、ULIDで生成した文字列をチーム名にしました。

読み上げるのが困難というデメリットがありますが、パッと見で自分のチームだと把握できるというメリットがあります。

(レギュレーションには違反していないはず...)

結果

使用言語はGo、順位は214/831位(6,748点)でした。

残念ながら100位以内には入ることができませんでした。

やったこと

計測ツール導入

事前準備していたPlaybookでツールのインストールを行いました。

滞りなく30分ほどで環境をセットアップすることができたため、準備した甲斐があったかなと思っています。

インデックスを作成する

スローログクエリを解析し、SELECTで参照されていそうなカラムにインデックスを作成しました。

この作業は今年初参加の新卒メンバーにやってもらいました。マジ感謝。

  • ride_statusesテーブルに、ride_idcreated_atの複合インデックス
  • ridesテーブルに、chair_idupdated_atの複合インデックス
  • ridesテーブルに、user_idcreated_atの複合インデックス
  • chair_locationsテーブルに、chair_idcreated_atの複合インデックス
  • chairsテーブルに、access_tokenのインデックス
  • couponsテーブルに、used_byのインデックス

移動距離を管理するテーブルを作成する

スロークエリログを解析したところ、以下のクエリがボトルネックになっていることがわかりました。

MySQLのイスごとの今までの移動距離をMySQLのウィンドウ関数で集計しているクエリです。

SELECT id,
       owner_id,
       name,
       access_token,
       model,
       is_active,
       created_at,
       updated_at,
       IFNULL(total_distance, 0) AS total_distance,
       total_distance_updated_at
FROM chairs
       LEFT JOIN (SELECT chair_id,
                          SUM(IFNULL(distance, 0)) AS total_distance,
                          MAX(created_at)          AS total_distance_updated_at
                   FROM (SELECT chair_id,
                                created_at,
                                ABS(latitude - LAG(latitude) OVER (PARTITION BY chair_id ORDER BY created_at)) +
                                ABS(longitude - LAG(longitude) OVER (PARTITION BY chair_id ORDER BY created_at)) AS distance
                         FROM chair_locations) tmp
                   GROUP BY chair_id) distance_table ON distance_table.chair_id = chairs.id
WHERE owner_id = ?

データ読み込みのたびにchars_locationsテーブルから移動距離を集計するのは非効率であるため、移動距離を管理するテーブルを作成しました。

CREATE TABLE chair_distance (
  chair_id VARCHAR(26) NOT NULL COMMENT '椅子ID',
  total_distance INTEGER NOT NULL COMMENT '移動距離',
  total_distance_updated_at DATETIME (6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '更新日時',
  PRIMARY KEY (chair_id)
);

このテーブルをchair_locationsにレコードが追加されるたびに、更新することで移動距離の集計を効率化しました。

これはchair_distanceにレコードが無ければINSERT、あればUPDATEするように実装しましたが、後からUPSERTでやればシュッと実装できたな〜と気づきました。

ベンチマークがなぜかコケる

アプリケーションサーバーとDBサーバーを分離したあと、競技終了時刻から1時間を切ったため、再起動試験を行いました。が、ここで問題発生!

再起動後、ベンチマークがコケるようになりました。
設定ファイルの確認、プロセスの再起動などを行いましたが、原因はわかりませんでした。

どうすることもできなくなったため、競技終了20分前ぐらいにCloudFormationのスタックを吹っ飛ばすことにしました。

「間に合うかな...」と思いつつ再構築を始めたところ、事前準備で作成していたPlaybookがめちゃくちゃ役に立ち、競技時間内に再びベンチマークが通るようになりました。got事なき。

反省

アプリケーションの仕様を理解していなかった

インデックスの作成や、N+1問題の解消などを行なっても大きなスコア上昇には寄与しなかったため、アプリケーションの仕様を理解した改善をする必要がある問題だと感じました。

特に、マッチングアルゴリズムがめちゃくちゃな実装になっていることに気づけなかったのが痛かったです。

アプリケーションの仕様を大局的に把握する力も養う必要があるなと感じました。

来年はアプリケーションの仕様を理解した上で、効果的な改善を行うことができるようになりたいです。

判断が遅い

再起動試験でコケまくったときに、さっさとスタックを吹っ飛ばすべきだったと反省しています。

復旧が完了した時点で時間がギリギリだったので、アクセスログやスロークエリログなどをOFFせずに終了してしまいました。

来年は思い切った判断をすることができるようになりたいです。

その他

特筆するほどのことではないですが、強いてあげるならば以下のことがあります。

  • sqlxに慣れていなかったので、ランタイムエラーを多発させた
  • systemdの理解が浅い
  • pprofを導入する
  • Playbookの改善
  • 計測結果を見やすくする

感想

楽しかったです!毎年、このような凝った問題を作成されている運営の方々には頭が上がりません。また来年も参加したいです。

今年も「『壁』を超えたな...」みたいな気持ちにはならずとても悔しかったですが、来年こそは超えられるように精進したいと思います。

ISUCON運営、参加者のみなさま、ありがとうございました!

明日は@9rotamaさんの記事です。お楽しみに!

Discussion