ISUCON14振り返り
記事の概要・想定読者
- ISUCON や Web パフォーマンスチューニングに興味があるエンジニアの方
- 競技の事前準備に役立つ情報を求めている方
ISUCON とは
ISUCON(Iikanjini Speed Up Contest) は、LINEヤフー株式会社が開催している日本最大級の Web パフォーマンスチューニングの競技大会です。
主な特徴
-
高速化を競う
参加者は与えられた Web サービスを、規定に基づいてどこまで高速化できるかを競い合います。競技はチーム戦で、ベンチマークのスコアによってランキングが決定されます。 -
幅広い技術領域
インフラ・ミドルウェア・データベース・アプリケーションコードなど、多岐にわたる技術分野の知識やノウハウが求められます。
ISUCON で提供されるサーバー構成は簡易的であり、実務と直結しない部分もあります。
しかし、以下のようなスキルを体系的に習得できる貴重な機会でもあります。- パフォーマンスボトルネックの発見と改善
- ログやメトリクスを用いた原因調査
- SQL チューニングやインデックス設計
- キャッシュ戦略
- サーバー構成の分離
こうした経験を通じて得られる システム全体の構造理解 や トラブルシューティング能力 は、長期的に実務にも活かせるエンジニアリング基盤になると考えています。
ISUCON 14 のお題
今回はライドシェアが題材でした。
アプリで呼んだ椅子に座り、自動運転で目的地まで移動できる「ISURIDE」というサービスのバックエンドが課題として出題されました。
「ISURIDE」の詳細は以下から確認できます。
メンバー
チーム 「ハリー・ポッターと賢者のEC2」 として参加しました。普段は勉強会を一緒に開催している、以下の 3 名で構成されています。
名前 |
---|
Harumi Yamashita (筆者) |
asamuzak |
keigo |
ISUCON14の目標
初出場のISUCON 13は練習期間があまりなかったので、上位20%を目指しましたが、320/694 位という結果でした。
それを踏まえ、ISUCON 14では対策に時間をかけました。
■ 目標設定の根拠
過去の ISUCON のスコアと順位のヒストグラムをチーム内で作成し、傾向を分析しました。
ISUCON は回ごとに問題難易度やスコア分布が大きく異なるため、絶対的なスコアより 相対順位 での目標設定が有効だと判断しました。
その結果、ISUCON 14 では「上位 25% 以内」を目指す ことに決定しました。
■ なぜ「上位25%」なのか
過去の大会において、上位 25% 以内に入っているチームは以下のような 基本的なパフォーマンス改善 を完了している傾向がありました。
- N+1 などの明確なスロークエリの改善
- 適切なインデックス設計
- キャッシュの活用
- 静的ファイル配信の最適化
- データベースサーバーの分離
これらの改善は、事前に十分な準備を行えば十分に実現可能だと判断し、無理のない挑戦的ラインとして 上位 25% を目標としました。
ISUCON14への取り組み
目標達成に向けて、以下の対策を実施しました。
■ 過去問への取り組み
- 使用した環境: matsuu/aws-isucon
- 解いた過去問:
private-isu
、ISUCON 11~13 (比較的新しい問題が中心)
取り組み方針
本番環境に近い形での練習を重視し、EC2 上で実行可能な matsuu/aws-isucon を活用させていただきました。
また、出題傾向を把握することを目的に、ISUCON11以降の比較的新しい問題から順に取り組みました。
古い問題ではライブラリやミドルウェアのバージョンに依存するトラブルも報告されているため、最新の課題から着手することで、よりスムーズかつ効果的な学習ができたと感じています。
■ 手順書・ドキュメントの整備
競技開始直後の環境構築で時間を浪費しないように下記のような対策を行いました。
- 開発環境・データベース・各種ミドルウェアのセットアップ手順を前回よりもさらに詳しくドキュメント化しました。
- 以前の便利コマンドを Makefile 化することで、複雑な操作を一括で実行できるようになり、作業の効率が向上しました。
結果として、初動にかかる時間を大幅に短縮でき、パフォーマンスチューニングに集中できました。
■ 各技術分野の属人化の解消
前回は担当外の知識不足が原因で、担当外のタスクに手を出せず作業が止まる場面がありました。また、PRのレビューが甘く手戻りが発生することもありました。
そこで今回は以下の対策を実施しました。
-
全員が問題を個々で解く練習を行い、カバーしていない範囲を減らす
個々の作業中のノウハウをこまめに共有し、定期的な知識共有を行った -
連携を取りやすいようなルール決め
コミュニケーションコストがかかったとしてもメンバーに対して作業の助けやレビューを気軽に求められるルール作りを行った
実際にサーバー分散の作業は担当外のメンバーが成功させるなど、互いにカバーし合うことで手が止まらず、全員が動き続けることができました。
ISUCON14の結果
最終結果は、スコア13,907点で85/834位でした!
上位10.2% 🎉
余裕をもって上位25%目標を達成することができました。
詳細な結果はこちらからご覧いただけます。
測定ツール紹介
「推測するな、計測せよ」という言葉があるように、闇雲にボトルネックらしき部分を触っても効率的ではありません。ISUCON でも計測ツールを積極的に活用し、最適化計画を立て、着実にボトルネックを解消することが重要です。
以下のツールを私たちのチームでは使用しました。
pt-query-digest
alp (Access Log Profiler)
-
用途
Webサーバー のアクセスログを集計し、時間がかかっているエンドポイントを可視化する。 -
参考リンク
alp (tkuchiki/alp) ISUCON Workshopの解説
pprof
-
用途
関数ごとの実行時間を可視化するために使用。
事前準備
ISUCON でスムーズに競技を始めるためには、開発環境・データベース・各種ミドルウェアのセットアップを素早く完了 させることが重要です。
Makefile
Makefileを作成し、競技中によく使う複雑なコマンドや一連の操作を簡素化しました。
.PHONY: help
help: ## コマンド一覧を表示
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
.PHONY: nginx
nginx: ## NGINXのログをリセットして再起動
sudo rm -rf /var/log/nginx/access.log
sudo systemctl restart nginx
.PHONY: mysql
mysql: ## MySQLのログをリセットし再起動
git pull
sudo rm -rf /var/log/mysql/slow-query.log
sudo systemctl restart mysql
.PHONY: go
go: ## Goをビルドして再起動
git pull
go build -o isuride
sudo systemctl restart isuride-go
.PHONY: restart
restart: ## アプリケーションと関連サービスを再起動
make nginx
make go
.PHONY: pt-q
pt-q: ## MySQLのslow-queryログを解析
sudo pt-query-digest /var/log/mysql/slow-query.log --filter '$$event->{db} eq "isupipe"' | less
.PHONY: alp
alp: ## NGINXアクセスログをALPで解析
sudo cat /var/log/nginx/access.log | alp ltsv -m="^/api/app/users$$","^/api/app/payment-methods$$","^/api/app/rides$$","^/api/app/rides/estimated-fare$$","^/api/app/rides/[0-9a-zA-Z]+/evaluation$$","^/api/app/notification$$","^/api/app/nearby-chairs$$","^/api/owner/owners$$","^/api/owner/sales$$","^/api/owner/chairs$$","^/api/chair/chairs$$","^/api/chair/activity$$","^/api/chair/coordinate$$","^/api/chair/notification$$","^/api/chair/rides/[0-9a-zA-Z]+/status$$","^/api/internal/matching$$" --reverse --sort=sum -o count,2xx,3xx,4xx,5xx,method,uri,avg,sum
.PHONY: pprof
pprof: ## pprofを使ったプロファイリング結果を表示
go tool pprof -seconds 60 -http=localhost:1080 http://localhost:6060/debug/pprof/profile
Notionの活用
大会に向けてNotionに専用ページを作成し、情報を集約することで作業効率をあげました。
手順書の整備
セットアップ手順やよく使うコマンド類を「何も考えずに実行できるレベル」まで細かくドキュメント化しました。
知見DB
同じミスに時間を使わないよう、躓いたことやよくやることは一括でテーブルにまとめました。
作業DB
各メンバーの進捗をテーブル形式で管理することで、相互のタスク状況がわかりやすくなり、レビューやヘルプのタイミングを把握しやすくなりました。
当日のタイムスケジュール
競技前 (8:30 ~ 10:00)
メンバーの自宅に前日から集まり、オフラインで準備しました。オフライン参加によって意思疎通や判断がスムーズになり、競技前の緊張感も一体感に変えられました。
メンバーの奥様が用意してくれた朝食で英気を養いつつ、モチベーションを高めました。
AWS に Web サービス環境を構築 (10:00 ~ 10:15)
Amazon Web Services (AWS) の CloudFormation を用いて Web サービスの環境を構築し、ベンチマークを実行して初期スコアを計測しました。
ドキュメント読み込み (10:00 ~ 10:15)
AWS 構築を担当しないメンバーは、同時に ISUCON 14 のお題ドキュメントを熟読し、サービス概要や競技ルールを把握しました。
今回はパフォーマンスだけでなく「ユーザー満足度」にも重点が置かれていたため、仕様や要件の理解が非常に重要でした。
各種セットアップ (10:15 ~ 10:50)
事前の役割分担とドキュメント化のおかげで、昨年より大幅に時間を短縮して以下の作業を完了できました。
- GitHub リポジトリ作成
- MySQL の初期設定
-
my.cnf
への各種パフォーマンス向けオプション追加 - プリペアドステートメントの解消 (サーバー側に無駄なステートメントを残さないよう、アプリケーション側でパラメータをバインド)
- コネクション数やプール (
MaxOpenConns
/MaxIdleConns
) の調整
-
- NGINX の初期設定
-
alp
の導入
-
- Memcached の設定
- pprof の設定
チューニング開始
以下では主なタイムラインと、その時点のスコアや実施内容についてまとめています。
10:50 ~ 11:30 [2647 点]
最初にベンチマークを回し、ボトルネックの洗い出しを行いました。
例年の ISUCON ではデータベース (DB) が最初のボトルネックになりやすいため、まずは MySQL のインデックス を貼ることから着手しました。
- 一人がインデックス追加を進める
- 残りの二人がサーバー分散やアプリケーション側の N+1 解消を同時に実施
インデックス追加
pt-query-digest
でスロークエリを分析し、必要なインデックスを検討・適用しました。
サーバー分散 (DB・アプリケーション分離)
アプリケーションサーバーと DB サーバーを分離しようとしましたが、ベンチマークの整合性チェックでエラーが発生しました。
原因は後ほど判明しますが、この時点では深追いせず一旦保留としました。
N+1 解消
alp
や pprof
を使ってボトルネックとなっているエンドポイントや関数を探しましたが、当初はこれといった “致命的” な N+1 がなく苦戦しました。
例年であれば序盤に明確な N+1 が見つかるケースが多いため、少し想定外の展開でした。
11:30 ~ 13:30 [2828 点]
N+1 など分かりやすいボトルネックを順次改善し、計測ツール上では確実に負荷が下がっていることを確認しましたが、なかなかスコアが伸びず焦りを感じていました。
例年は「明らかなボトルネックを潰す」アプローチが有効でしたが、今回は全体的に処理が少しずつ重い構成で、1 つの改善あたりのスコア上昇が小さく、戦略に苦戦しました。
トリガー利用の検討
-
背景
getOwnerChair
関数が長大なクエリとなっており、合計移動距離を取得するときに複雑な計算をしていたため、実行時間が長くなっていました。 -
実施内容
移動タイミングで合計距離を更新するようにトリガーを設定し、クエリ負荷を軽減しようと試みました。 -
結果
初期データ投入時にロックがかかっていたなどの問題でトリガーが動作せず、合計移動距離が初期化されない問題が発生。結局、本番中には十分な検証時間を確保できず、キャッシュ へ切り替えて対処しました。
SQL ではなく Go で現在時刻を取得
-
背景
appGetNearbyChairsResponse
で現在時刻を取得する際、DB からNOW()
を呼び出していた。 -
実施内容
Go 言語の標準ライブラリを使い、アプリケーションコード側で現在時刻を取得するように変更。 -
効果
不要なクエリ呼び出しを削減し、DB 負荷を軽減。
GET /api/app/nearby-chairs
の N+1 改善
-
背景
appGetNearbyChairs
内でループしながら SQL を叩いている箇所があった。 -
実施内容
バルクフェッチ (一括取得) に変更し、ループ内でのクエリ実行を回避。 -
効果
DB へのアクセス回数が大幅に減り、応答速度が向上。
13:30 ~ 17:00 [8246 点]
この時間帯では、N+1 問題の解消、インデックス最適化、キャッシュ導入、DB 構成の改善 などをまとめて実施し、8246 点 までスコアを大幅に伸ばすことができました。
インデックスの最適化
-
背景
パフォーマンス改善が進むに連れて、新たに重くなったクエリがpt-query-digest
の上位に現れてきた。 -
実施内容
インデックスの貼り直しや最適化を継続的に実施。
キャッシュ導入
-
背景
appGetNearbyChairsResponse
の周辺椅子検索クエリが重かった。 -
実施内容
周辺椅子データをキャッシュ (Memcached) し、重複計算を削減。
GET /api/app/nearby-chairs
の N+1 解消(JOIN 対応)
-
背景
以前の修正で N+1 を減らしきれず、一部が残っていた。 -
実施内容
最新位置情報をLEFT JOIN
で一度に取得するように変更し、DB アクセス回数を削減。
/api/app/ride
の N+1 解消
-
背景
/api/app/ride
エンドポイント内で取得したデータをループしながらさらに絞り込む処理があり、DB アクセスやループ回数が増大していた。 -
実施内容
一括取得やフィルタリングの変更を行い、無駄なループを削減。
DB サーバー分散の再挑戦
-
背景
競技開始直後に DB 分散を試みて失敗していたが、課題を解決できそうな見込みが出てきた。 -
実施内容
いったんアプリケーションとisuride-matcher
(systemd で定期的に椅子とユーザーをマッチングするサービス)を停止し、DB サーバーの分散を再度実行。 -
結果
スコアが 8246 点 まで一気に跳ね上がる結果となり、チームの士気が高まりました。
17:00 ~ 17:30 [13,907 点]
isuride-matcher
のマッチング間隔調整
-
背景
当初は 500ms 間隔でマッチングしていましたが、ユーザーが椅子を待つ時間が長く、満足度が下がっていると推測されました。 -
実施内容
マッチング間隔を 10ms に短縮し、椅子が来るまでの待ち時間を削減。 -
結果
待ち時間短縮に伴ってユーザー満足度が向上し、スコアが 13,907 点 に大きく伸びました。
マッチングロジックの改善検討
-
背景
ユーザーと遠方の椅子をマッチングしてしまう非効率があり、さらに待ち時間を短縮できる可能性があった。 -
実施内容
椅子とユーザーの距離を考慮したマッチングアルゴリズムを検討。 -
結果
実装段階でライドステータス整合性エラーが発生し、残り時間との兼ね合いもあり、最終的に導入を見送りました。
17:30 ~ 18:00 [13,907 点]
最後に競技環境の整合性チェックや最終ベンチマークの実行を行い、少し余裕をもって作業を終了しました。
反省点
セットアップの自動化
高順位チームの事例を見ると、インフラやアプリケーションのセットアップ自動化 (Ansible 等) に力を入れている印象を受けました。
私たちのチームでも以下の項目を自動化して、さらに初動と環境変更の効率化を図りたいと考えています。
- インフラ設定の自動化 (Ansible)
- Git 管理の自動化
- ISUCON ベンチマーク結果の自動収集と可視化
- 各種ログ (MySQL・NGINX・Go) の自動収集と可視化
アプリケーション仕様の読み込みの重要性
これまでのIsuconではボトルネックを順に改善していくのがセオリーでしたが、今回はボトルネックが平坦に分散していたため、スコアに直結する要素を早期に見極める必要がありました。
しかしアプリケーション仕様の把握が不十分だったことで、「ユーザー満足度」を高める施策が後手に回ってしまいました。
今後はパフォーマンスチューニングに加え、アプリケーション仕様をどこまで深く理解し、活かせるかをチームで再度検討したいと考えています。
トリガーの使用
トリガーを使って合計移動距離を計算しようとした際、不整合に苦しみました。
再利用性を狙うあまり複雑化してしまった面もあるので、ISUCON のような短期集中型の競技では、より単純化したカラム追加などで対応する戦略も検討するべきだと感じました。
コードレビュー体制
後半になると時間が足りず、最低限のコードレビューもままならない状態でした。
AIコードレビューのCIを導入するなど、タイポや明らかな不具合を自動検出できる仕組みを用意しておくのも有効かもしれません。
今後の方針
ISUCON で勝つための Issue 分析
次回は30 位入賞を目標に、ISUCON の勉強を継続しつつ、ソフトウェアエンジニアとしての総合力も高めていきたいと考えています。
主な改善の方向性として、以下の 4 つのメイン Issue に注力する予定です。
パフォーマンス改善の知識や戦略
ISUCON で大きくスコアを伸ばすためには、限られた時間内でどれだけ効率よくパフォーマンスを最適化できるかが重要です。
そのためには、パフォーマンス改善の手札を増やす必要があります。
特に、ベンチマーカーの仕様よく用いられるチューニング手法、 Go・SQL の最適化について知識を深めていこうと思いました。
実装力
ISUCONでは、限られた時間内で正確に実装し、エラーにも迅速に対応する力が求められます。
そのため、言語スキル・設計力 といった総合的な実装力を高めることが不可欠だと考えました。
-
知見共有
勉強会で知見共有タイムを設け、メンバーが得意な分野を発表することでチーム全体の理解度を底上げしたいです。 -
AIコーディング
AIコーディングによってエンジニアの開発プロセスは大きく変わります。個人の実装力だけでなく、AIコーディングを活用するための技術もキャッチアップしていきたいと考えています。
ツール/環境の充実
ISUCON13 ではコマンドの操作手順をドキュメント化し、ISUCON14 では Makefile を用いてコマンド実行を一元化しました。
今後は Ansible を活用することで、初期設定や構成変更・デプロイをワンコマンドで管理し、より効率的かつ安定した環境を整備します。
チーム連携
今回オフラインで横並びに作業しました。
それにより、コミュニケーションを円滑に進められたと感じています。また、同じ空間でスコアの伸びを分かち合えたことが楽しかったです。
次回のISUCONもオフラインで挑みたいです。
まとめ
ISUCON 14 はベンチマーカーが非常に安定しており、待ち時間なく繰り返し検証できた点が大きかったです。
前回 はベンチマーカーの実行待ちが発生し、気軽に試せませんでしたが、今年は実行待ちが無く、改善サイクルを高速に回すことができました。
また、DB の分散が成功して一気にスコアが伸びたときは、全員立ち上がって喜びました。
ISUCON ならではの「改善する楽しみ」を存分に味わうことができて嬉しかったです。
来年の ISUCON ではさらに上位を目指して挑戦していきたいと思います。
Discussion