この記事は羅針盤 アドベントカレンダー 2024の16日目の記事です。
qiita.com
15日目の記事は エンジニアレスでできるサイト改善!ノーコードツール活用術 - 羅針盤 技術航海日誌 でした。
今回は縁あってゲストの方に寄稿していただきました。
普段はAPEXでマスターランクを目指しつつ、隙間の時間でLINEで生理日予測を共有できるペアケアというサービスを運営している方です。
paircare.jp
はじめに
こんにちは。株式会社Entaleの後藤です。別会社1であるにもかかわらず、羅針盤さんのアドベントカレンダーに寄稿する機会をいただきましたので、精一杯書いていきます。
この記事では、今年参加したISUCON14の挑戦を振り返りながら、事前準備や当日の取り組み、反省点などをお話しします。
事前準備
ここ数年、ISUCONには1〜2人で参加することが多かったのですが、今年は3人チームでの挑戦となりました。本番1週間前にメンバーが集まり、初動のみ各自の役割を以下のように決めて準備を進めました。
アプリケーション担当
- 提供されたドキュメントを読み込み、仕様を理解する
- アプリケーションを実際に触って動作を把握する
デプロイツール担当
- Git管理出来るようにする
- Web アプリケーション、Nginx、MySQLを全サーバーに反映するスクリプトを作成する
計測ツール担当
- pproteinを導入する
事前の練習環境として、「ISUNARABE」を活用しました。このツールは非常に使いやすく、初動確認にも最適でした。
pproteinとは
pproteinを利用すると、アプリケーションとデータベースのボトルネックを可視化することが出来ます。ISUCONは計測が最も重要なので、事前準備で話して導入を決めました2。
これまでは alp, pt-query-digest, pprofをターミナルで見て結果をチャットに貼り付けていましたが、全員が好きなタイミングで全てのログを見ることが出来るようになるので、より改善に集中することが出来ます。

本番当日
【9:40】公式配信視聴
オフラインで集合し、ISUCON公式ライブ配信を観覧。配信の最後に公開されるお題の動画は毎年クオリティが高くなっている気がします。テーマはライドシェアサービスということで、今年も非常にワクワクする内容でした。
【10:00】初動開始
事前準備通りに役割分担を進めます。私は計測ツール担当として、pprotein の導入を開始しました。 以下は、pprotein 用ハンドラーの実装例です。
import ( "github.com/kaz/pprotein/integration" ) mux.Handle("/debug/*", integration.NewDebugHandler())
【10:30】初回ベンチマーク実施
計測ツールとデプロイツールが整ったので、初回のベンチマークを実行します。この時点でのスコアは約1000点です。1台目のサーバーにログインし、top コマンドで負荷の高いプロセスを確認しました。
【11:00】インデックス対応
マニュアルやアプリケーションの確認を進める予定でしたが、データベースの負荷が非常に高かったため、まずはインデックス対応を最優先することにしました。pprotein に出力されたスロークエリを参考に、インデックスを追加していきました。
インデックス設計の際には、ChatGPTも活用しました。
質問: テーブルにこんなクエリがたくさん投げられてるんだけど、どんなインデックスを貼るべき? # テーブル CREATE TABLE chair_locations ( id VARCHAR(26) NOT NULL, chair_id VARCHAR(26) NOT NULL COMMENT '椅子ID', latitude INTEGER NOT NULL COMMENT '経度', longitude INTEGER NOT NULL COMMENT '緯度', created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '登録日時', PRIMARY KEY (id) ) # クエリ SELECT * FROM chair_locations WHERE chair_id = 'S' ORDER BY created_at DESC LIMIT N 回答: この場合、クエリの効率化には以下のインデックスを設計すると良いでしょう。 CREATE INDEX idx_chair_id_created_at ON chair_locations(chair_id, created_at);
結果、スコアは約4000点になりました。
【11:30】データベースのサーバー分割
依然としてデータベース負荷が高いため、サーバーをアプリケーションとデータベースの分割対応を先に実施することにしました。以下の手順で対応を進めました。
- mysqld.cnfの修正
# bind-address=127.0.0.1 # mysqlx-bind-address=127.0.0.1
- ユーザー権限付与
CREATE USER `isucon`@`192.168.%` IDENTIFIED BY 'isucon'; GRANT ALL PRIVILEGES ON `isuride`.* TO `isucon`@`192.168.%`;
- pproteinのslowlog設定を修正
[ { "Type": "slowlog", "Label": "mysql", "URL": "http://192.168.0.12:19000/debug/log/slowlog", "Duration": 60 } ]
- 環境変数を更新してDBの接続先を変更
ISUCON_DB_HOST="192.168.0.12"
サーバー分割後にベンチマークを回したところ、通知関連のクリティカルエラーが発生しました。アプリケーションを理解しなければ修正不可能と判断して、一度切り戻し、全員でマニュアルを確認しました。
【12:00】アプリケーション仕様の把握
メンバーからアプリケーションの仕様を教わりながら、Goのコードを読み解きました。その結果、以下のポイントが改善点として浮上しました。
- 椅子とライドのマッチング処理
- 椅子・ライドに関連する通知
【12:30】マッチング処理の改善に挑戦
非同期バッチで動作するマッチング処理を同期処理に変えられないか模索しました。しかし、何をやってもベンチマークが通らない問題に直面します。ここに多くの時間を費やしましたが、成果が得られませんでした。
【14:30】データベースのサーバー分割(再)
データベースのサーバー分割に一度戻り、クリティカルエラーの原因が通知とマッチングの負荷であることを特定します。ポーリング間隔を調整して負荷を軽減した結果、スコアは約10,000点に到達しました。
【16:30】データベース調整
負荷の高いクエリ数を削減するため、sync.Map を利用したメモリキャッシュを実装していきました。また、データベースコネクションの設定を調整し、スコアは約15,000点に向上しました。
const maxConnsInt = 25 db.SetMaxOpenConns(maxConnsInt) db.SetMaxIdleConns(maxConnsInt * 2)
【17:30】最終確認
pproteinやログの削除をしてから、設定ファイルでMySQL・Nginxの最適化を試みましたが、得点の大幅向上には至りませんでした。
振り返り
無事に再起動試験は突破して、最終スコアは14,947点となりました。
今年の問題も非常に良く設計されており、キャッシュやデータベースの単純な最適化ではなく、アプリケーションのシナリオに沿った改善が求められました。この点を足踏みしてしまいインフラの力技に頼ってしまったのが反省です。
来年はさらに改善のサイクルを回し、優勝を目指します。
- 株式会社Entaleと株式会社羅針盤は関連会社やグループ会社といった関係ではございません。中の人と仲が良いだけです。↩
- pproteinの導入はpprotein でボトルネックを探して ISUCON で優勝するを参考にさせていただきました。↩