Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ISUCON研修おかわり会 講義スライド

ISUCON研修おかわり会 講義スライド

2025/6/26に開催したCTO協会新卒合同研修非公式アフターイベントの講義資料です

Avatar for Yuki Yata

Yuki Yata

July 06, 2025
Tweet

More Decks by Yuki Yata

Other Decks in Programming

Transcript

  1. 本日の流れ(アジェンダ) 1. (5 分) オープニング 2. (15 分) Part 0:

    ISUCON の基本 3. (20 分) Part 1: 改善 ① データベース (インデックス) 4. (15 分) Part 2: 改善 ② Web サーバー (静的ファイル) 5. (25 分) Part 3: 改善 ③ アプリケーション (N+1 問題) 6. (10 分) まとめと Q&A 2
  2. 自己紹介 kazu-gor(@kazu-gor2) 株式会社 エブリー SWE AI 研究 → コンテンツ Mgr→

    デー タエンジニア → PO→ BE(今) ISUCON は最下位でした コーヒーが好きです 4
  3. 本日のゴール MUST ISUCON の基本的な流れ 計測 → 特定 → 改善 のサイクルを理解する

    private-isu を題材に、自力で 1 つ以上 の改善を体験する BETTER ログを読み解き、適切な改善アクションを 自分で考えて実行できる ための基 礎を築く 6
  4. 進め方 ① ISUCON 研修と同じ private-isu というアプリケーションを使います コースごとの進め方 初級:講義 → ハンズオン

    → グループワーク 中級:無限にグループワーク コード 初級:スタートの状態(ログの設定やライブラリのダウンロードはすでにされ ている) 中級:ある程度チューニングされた状態(インデックス、N+1,静的ファイルの 返却、Prepared Statement の抑制、memcache の活用、connection の設定、 外部ライブラリの削除) 7
  5. ISUCON とは? Iikanjini Speed Up CONtest お題となる Web サービスを限界まで高速化を図るチューニングバトル DB

    やアプリケーション、OS などをガチャガチャする 仕様さえ守っていれば何でもあり!とにかく速く! バックエンド、インフラの知識を総動員して立ち向かう競技 ベンチマーカーが負荷試験を行って、捌けたアクセス数に応じてスコアが上がっ ていく 12
  6. 基本的な2つのボトルネック データベース データの取得、挿入がボトルネックになりがち 一つ一つは軽くても蓄積するとかなりのボトルネックに 0.1 秒で処理できるクエリも 10000 回叩かれたら 10 秒かかる

    アプリケーション データを無駄に取得していたり、何回も取得しているとレスポンスが遅くな る 実際は、実質データベースのボトルネックだったりもする 14
  7. [ハンズオン] ベンチマーカーを実行して CPU の使用量を見る ベンチマーカーの実行 ターミナルで make benchmark を実行 top

    コマンドで CPU 使用量を見る 現在の CPU の使用量をモニタリングできるコマンド ベンチマーカーの実行中に別ターミナルで top コマンドを叩いてみましょう CPU%みたいなところを見る 何が 上位に来ていましたか?(誰かに聞いてみる) 18
  8. 詳細な分析用の計測ツール2選 本日は代表的な 2 つのツールを使います。(make コマンドで隠蔽されているけど) 1. アクセスログ解析: alp どのエンドポイント(URL)が 遅いか

    / たくさん呼ばれているか を教えてく れる。 アプリケーションのパフォーマンスを測定するときに使う 2. スロークエリログ解析: pt-query-digest どの SQL クエリが 遅い(スロー)か を教えてくれる。 DB のパフォーマンスを測定するときに使う 22
  9. 出力例 +-------+-----+-----+-----+-----+-----+-------+--------+------------------+ | COUNT | 1% | 50% | 99%

    | AVG | MAX | SUM | METHOD | URI | +-------+-----+-----+-----+-----+-----+-------+--------+------------------+ | 1800 | ... | ... | ... | 0.82| 1.51| 1476.0| GET | / | | 1200 | ... | ... | ... | 0.55| 1.10| 660.0| GET | /posts?offset=10 | | 600 | ... | ... | ... | 0.30| 0.80| 180.0| POST | /login | | ... | ... | ... | ... | ... | ... | ... | ... | ... | +-------+-----+-----+-----+-----+-----+-------+--------+------------------+ SUM (合計時間) や AVG (平均時間) が大きいところが怪しい! 24
  10. 出力例 (一部抜粋): # Query 1: 0.01 QPS, 0.01x concurrency, ID

    0x... # This item is included in the report because its total time is ... # Scores: V/M = 0.01 # Time range: ... to ... # Attribute pct total min max avg ... # ============ === ======= ======= ======= ======= ======= # Count 50 1800 # **Exec time** 63 5225s 2s 4s 3s ... # ... SELECT `posts`.* FROM `posts` ORDER BY `created_at` DESC LIMIT 10 OFFSET 10; # Query 2: ... SELECT `comments`.* FROM `comments` WHERE `post_id` = ? ORDER BY `created_at` DESC; Exec time (合計実行時間) が大きいクエリが怪しい! 26
  11. [グループ] ボトルネックはどこだ? (5 分) alp の結果と pt-query-digest の結果を見て、今一番解決するべきものはどれでしょう か? グループで議論して、仮説を立ててみてください。

    議論のヒント: まずそもそももっと修正すべきなのは DB?アプリ? DB だとしたらどのクエリが一番重そう? アプリケーションだとしたらどのエンドポイントが一番重そう? 28
  12. 想定解 DB の SELECT * FROM comments WHERE post_id =

    〇〇 を改善するの が一番効果が高そう! CPU の使用量は DB が圧倒的に多い → こっちが問題そう その DB の中で最も実行時間が大きいものが comments を取得するクエリ 30
  13. インデックス (Index) とは? データベースの検索を高速にするための 「索引」 です。 例:分厚い ISUCON の技術書 インデックスなし:

    "N+1 問題" のページを探すのに、全ページをめくる必要が ある ( = フルスキャン ) インデックスあり: 索引(あいうえお順)で "N+1 問題" を引くだけで見つけられ る ( = インデックススキャン ) データベース君が見る行数が減るから速くなる! WHERE 句や ORDER BY 句で頻繁に使われるカラムにインデックスを貼ると効果的! 32
  14. EXPLAIN で実行計画を見る EXPLAIN SELECT * FROM comments WHERE post_id =

    12345; 注目ポイント type: ALL: ヤバい! フルテーブルスキャン。インデックスが効いていない。 ref, range, index: インデックスが使われている。良い。 rows: スキャンすると予測される行数。この数が少ないほど良い。 33
  15. [ハンズオン] インデックスを追加しよう MySQL にログイン mysql -u isuconp -p isuconp #

    パスワードもisuconp インデックスを追加する SQL を実行 alter table comments add index post_id_idx(post_id); さっきの EXPLAIN をもう一度実行して結果が変わっているか確認! ついでにベンチマーカーももう一度実行してスコアが変わっているか確認しましょ う! 34
  16. 静的ファイル vs 動的ファイル 静的ファイル (Static Files) 誰がいつアクセスしても 内容が変わらない ファイル。 例:

    CSS, JavaScript, 画像ファイル (JPG, PNG) 動的ファイル (Dynamic Content) アクセスごとにプログラムが実行され、内容が変わる ファイル。 例: ユーザーの投稿一覧、ログイン後のマイページ 41
  17. [ハンズオン] アプリケーションのパフォーマンス改善 設定ファイルを開き、isucon.conf の設定ファイルを参考にして、 静的ファイルの配信を設定してみましょう。 # 設定ファイルを開く sudo vi /etc/nginx/sites-available/isucon.conf

    # /lecture/part3/static_file.confを参考にファイルを書き換え # 設定ファイルの文法チェック sudo nginx -t # nginxのreload make reload-nginx ベンチーマーカーを実行してスコアやエンドポイントのレスポンスがどうなってるか確 認! 48
  18. N+1 問題ってなに? 「1 つのデータを取るために、1 回+ N 回もデータベースに問い合わせちゃう問題」 ファミレスで例えると... あなたはファミレスの店員です。5 人のお客さんが来店しました。

    N+1 問題が起きている状態: i. 「1 人目は何を注文した?」→ 厨房に伝えに行く(1 回目) ii. 「2 人目は何を注文した?」→ 厨房に伝えに行く(2 回目) iii. 「3 人目は.. 合計 5 回も厨房を往復!疲れる!時間がかかる!非効率! 53
  19. コードで見る N+1 問題 // N+1問題が発生するコード // 1回目のクエリ posts, _ :=

    db.Query("SELECT * FROM posts") for posts.Next() { var post Post posts.Scan(&post.ID, &post.Title) // ループの中でクエリが発生!(N回) comments, _ := db.Query("SELECT * FROM comments WHERE post_id = ?", post.ID) count := 0 for comments.Next() { count++ } fmt.Printf("%s has %d comments\n", post.Title, count) } // → 投稿が100件あったら、101回もデータベースと通信! for 文の中で何度も DB に問い合わせをしている。 55
  20. N+1 問題はわかったけどなにがいけないの? ループ処理をするたびに DB に問い合わせする → 10 件ぐらいなら毎回探索してもそこまで負荷にはならなさそう → 100,000

    件あったら何回も 100,000 件に対して欲しいデータを探しにいく? 想像できる通り、DB のパフォーマンスが悪化して以下のような事態に。 ユーザ: 「なんかこのサイト重くない?」 開発者: 「なんか DB のリソースめっちゃ使ってない?」 これは ISUCON に限った話ではありません。 実際の開発でも気を抜くと起こり得ます。 56
  21. [グループ] private-isu の N+1 を探せ! (5 分) alp の結果で遅かった /

    (トップページ) の処理を見てみましょう。 ソースコードは /home/isucon/private_isu/webapp/golang/app.go or python/app.py にあります。 // /home/isucon/private_isu/webapp/golang/app.go // トップページのハンドラー func getIndex(w http.ResponseWriter, r *http.Request) { // 投稿を取得 results := []Post{} err := db.Select(&results, "SELECT * FROM `posts` ORDER BY `created_at` DESC LIMIT ?", postsPerPage) for i := 0; i < len(results); i++ { // この中で何をしている? err := db.Get(&results[i].User, "SELECT * FROM `users` WHERE `id` = ?", results[i].UserID) // コメント数を取得 var count int db.Get(&count, "SELECT COUNT(*) FROM `comments` WHERE `post_id` = ?", results[i].ID) results[i].CommentCount = count // コメントを取得 comments := []Comment{} db.Select(&comments, "SELECT * FROM `comments` WHERE `post_id` = ? ORDER BY `created_at` DESC LIMIT 3", results[i].ID) for j := 0; j < len(comments); j++ { db.Get(&comments[j].User, "SELECT * FROM `users` WHERE `id` = ?", comments[j].UserID) } results[i].Comments = comments 60
  22. [ハンズオン] N+1 問題を修正しよう トップページの処理を改善します。 コメントやユーザー情報をまとめて取得し、ループ内での SQL 発行をなくしましょ う。 修正方針: 1.

    取得した posts の id と user_id を集める。 2. IN 句を使って、comments と users を一括で取得する。 3. Map を使って、Post に Comment や User を効率的に紐付ける。 // 例: IN句を使った一括取得 postIDs := []int{1, 2, 3, 4, 5} query := "SELECT * FROM comments WHERE post_id IN (?, ?, ?, ?, ?)" db.Select(&comments, query, postIDs...) // MapでO(1)アクセスを実現 61
  23. [ハンズオン] デプロイと再計測 アプリケーションを再起動 # app.goを開く vim -n /private_isu/webapp/golang/app.go # Go

    アプリケーションを再起動 make build-app ベンチマークを再実行 ブラウザでベンチマークを実行し、スコアの変化を確認します。 ログを再度 alp や pt-query-digest で見てみましょう。 SELECT * FROM comments WHERE post_id = ? のようなクエリが減っているはずです。 スコアは上がりましたか? 62
  24. スコアの Before / After 改善内容 スコア(目安) 備考 初期状態 ~ 3,000

    インデックス追加後 ~ 25,000 DB のボトルネックも解消 静的ファイル配信後 ~ 80,000 アプリの負荷を下げて安定化 N+1 問題修正後 ~ 180,000+ アプリケーションの改善が大きく効く! ※ スコアは環境やタイミングで変動します。 重要なのは、計測 → 特定 → 改善のサイクルを回すことです! 64
  25. 本日の振り返り 計測 alp でアクセスログを見て、遅いエンドポイントを特定した。 pt-query-digest でスロークエリログを見て、遅い SQL を特定した。 特定と改善 インデックス:

    EXPLAIN で実行計画を確認し、 CREATE INDEX で改善した。 静的ファイル: Nginx から直接配信するように設定し、アプリの負荷を軽減し た。 N+1 問題: ループ内の SQL 発行をやめ、Eager Loading で改善した。 このサイクルを、素早く正確に回すことが ISUCON 攻略の鍵です! 65
  26. ネクストステップ 他の改善手法を試してみる アプリケーションのプロファイリング DB のコネクション数調整 Nginx, MySQL のパラメータチューニング 複数台構成 (DB

    サーバーを分けるなど) ISUCON の過去問に挑戦する isucon.net に過去のすべての問題と解説ブログへのリンクがあります。 コミュニティに参加する 勉強会や Discord などで情報交換するのもおすすめです。 66
  27. 68