本文へ移動

ホーム / SRE / 第5部 インフラ・セキュリティ・性能・コスト

第5部 インフラ・セキュリティ・性能・コスト・第12章

パフォーマンスチューニング

★★☆ 重要10トピック・面接 7問

遅さの原因を計測で見つけ、定番の原因(N+1、インデックス、接続プール、キャッシュ、同期処理、ランタイム、インフラ)を順に潰していく流れと、負荷試験で限界を知る方法をまとめます。

ひとことで

まず目標を数字で決め、計測して一番時間を使っている場所を見つけ、1つずつ直して効果を測る。勘で直さない。

パフォーマンスチューニングで一番多い失敗は、計測せずに「たぶんここが遅い」と思った場所を直すことです。効果がなかったり、別の場所を悪化させたりします。大事なのは次の3つです。①目標をユーザー体験の数字(「注文APIのp99を300ms以下」など)で決める。②計測して、時間の大半を使っている場所(ボトルネック)を1つに絞る。③1回に1つだけ変えて、同じ条件で前後を比べる。

アムダールの法則のとおり、全体の時間のうち小さな割合しか占めない部分をいくら速くしても、全体はほとんど速くなりません。APMのトレースで「1リクエストの時間がどこに使われているか」の内訳を見て、一番大きいところから手をつけます。

見る順番見るものツール
1 どのリクエストが遅いかエンドポイントごとのp95・p99、件数APM、ALBのログ、SLOダッシュボード
2 時間の内訳アプリ・DB・外部API・キャッシュのどこで使っているか分散トレース
3 DB遅いクエリ、回数(N+1)、ロック待ち、実行計画スロークエリログ、Performance Insights、EXPLAIN
4 アプリCPUを使っている関数、GC、メモリプロファイラ、フレームグラフ、GCログ
5 インフラCPU・メモリ・ディスク・ネットワークの飽和USE法(使用率・飽和・エラー)、CloudWatch
6 フロントページ表示、画像、JSの大きさCore Web Vitals、ブラウザの開発者ツール
よくある落とし穴なぜダメか
平均値だけで判断する遅い利用者が平均に埋もれる。p95・p99を見る
まとめて何か所も変えるどれが効いたか、どれが悪化させたか分からない
開発環境のデータ量で測るN+1やインデックスの問題はデータが多いときだけ表面化する
いきなりスケールアップ原因が1本のクエリならお金で解決しても再発する。まず原因を特定
キャッシュで隠す根本原因が残り、キャッシュが外れた瞬間に障害になる
改善後に監視しない次のリリースで元に戻る(回帰)。ダッシュボードとアラートに残す
推測せず計測する。1回に変えるのは1つだけこのサイクルを、目標に届くまで回す1 目標を決める「p99を300ms以下」などユーザー体験の数字で2 計測するAPM・メトリクス・プロファイラで現状把握3 ボトルネック特定時間の大半を使っている場所を1つ選ぶ4 仮説を立てて変更原因の仮説を1つ立て変更は1つだけ5 効果を測る同じ条件で比較悪化していないかも6 回帰を防ぐ監視・アラート・負荷試験に組み込む
チューニングのサイクル

6の「回帰を防ぐ」まで終わらせて、初めて改善が定着する。

全体の時間の大部分を占める場所を直さないと、全体は速くならない青:DB 緑:アプリ 黄:外部API 灰:その他改善前DB 600アプリ 2501000ms1リクエストの内訳(ms)アプリを2倍速くDB 600875ms頑張っても12.5%しか縮まないDBを半分にDB 300アプリ 250700ms同じ「半分」でも30%縮む
アムダールの法則

全体の60%を占めるDBを半分にする方が、25%のアプリを2倍にするより効く。

面接ではこう答える

まず「このAPIのp99を300ms以下」のようにユーザー体験の数字で目標を決めます。次にAPMのトレースで時間の内訳を見て、一番大きい部分をボトルネックとして選びます。仮説を立てて1つだけ変更し、同じ条件で効果を測り、最後に監視や負荷試験に組み込んで回帰を防ぎます。全体に占める割合が小さい部分を直しても効果が小さいので、内訳の大きいところから手をつけます。

ひとことで

レイテンシは平均ではなく分布で見る。特に遅い側の尻尾(p95・p99)が、利用者の不満と障害の芽になる。

レスポンス時間は、ほとんどが速く、一部がとても遅い、右に長い尻尾のある分布になります。平均値はこの尻尾に引っ張られつつも隠してしまうので、p50(中央値・普段の体験)、p95、p99(遅い側の体験)を見ます。p99が1秒なら、100回に1回は1秒以上待たされています。1人が1日に何十回もAPIを呼ぶなら、ほぼ全員が毎日その遅さを体験している計算です。

マイクロサービスで1回のリクエストが多くのサービスを並列に呼ぶと、テールレイテンシの増幅が起きます。それぞれのサービスでp99の遅さに当たる確率は1%でも、100個呼べばどれか1つに当たる確率は約63%になり、全体のレイテンシは一番遅いものに引きずられます。

テールを縮める方法内容
呼び出しの数を減らすまとめて取得する、不要な呼び出しをやめる
タイムアウトとフォールバック遅い依存先を待ち続けず、既定値やキャッシュで返す
ヘッジリクエスト一定時間返ってこなければ別のサーバーにも同じ要求を送り、早い方を使う(冪等な読み取りのみ)
遅い原因の除去GCの停止、ロック待ち、コールドスタート、キャッシュミス時の重い処理
重い利用者の対策データが多いアカウントほど遅くなりがち。ページングや上限を設ける

計測の注意:平均値を何本も平均しても正しいp99は出ません。パーセンタイルはヒストグラムから計算します。また、負荷試験ツールが「前のリクエストが返ってから次を送る」作りだと、遅い時間帯のリクエストが少なく数えられ、実際より良い結果に見えることがあります(coordinated omission)。

平均は「誰の体験でもない」。遅い側の尻尾(p95・p99)を見るあるAPIのレスポンス時間の分布(例)0ms250ms500ms750ms1000msp50 95msp95 330msp99 620ms平均 150ms100人に1人は620ms以上待っている。重い利用者(データが多い人=よく使う人)ほど尻尾側にいることが多い
レイテンシの分布とパーセンタイル

平均150msでも、100人に1人は620ms以上待っている。

呼び出し先が増えるほど、どれか1つが遅い確率は急に上がる1回のリクエストで並列に呼ぶサービス数と、どれかがp99の遅さに当たる確率0%25%50%75%100%1255075100並列に呼ぶ数 →1個なら1%、10個で約10%100個 → 約63%50個 → 約39%
並列呼び出しによるテールの増幅

依存先が増えるほど、p99の対策が全体の速さを決める。

面接ではこう答える

レイテンシは右に長い尻尾のある分布になるので、平均ではなくp50、p95、p99で見ます。マイクロサービスで多くのサービスを並列に呼ぶと、どれか1つが遅い確率が上がり、全体が一番遅いものに引きずられます。呼び出し数の削減、タイムアウトとフォールバック、ヘッジリクエスト、GCやロックなど遅い原因の除去でテールを縮めます。

12.3N+1問題

用語★★☆ 重要
ひとことで

一覧を1回のクエリで取ったあと、各行の関連データを1件ずつ取りに行くため、クエリが「1+件数(N)」回発生する問題。

ORM(ActiveRecord、Django ORM、Hibernateなど)で関連データを使うと、意識しないうちに起きます。たとえば記事一覧を表示し、それぞれの記事の著者名を出すと、記事の数だけ著者を取りに行くクエリが流れます。1回1msでも、往復が100回あれば100msです。開発環境ではデータが少なくDBも同じマシンにあるので気づかず、本番で件数が増えてから表面化します。

# N+1:記事の数だけ users へのクエリが流れる(Rails)
posts = Post.limit(100)
posts.each { |post| puts post.user.name }

# 改善:関連をまとめて取得(2回のクエリで済む)
posts = Post.includes(:user).limit(100)
posts.each { |post| puts post.user.name }
検知の方法内容
APMのトレース1リクエストの中で同じ形のクエリが何十回も並んでいる
1リクエストあたりのクエリ数メトリクスとして出し、急に増えたら気づけるようにする
開発時の検知ツールRailsのBullet、Djangoのdjango-debug-toolbar、テストでクエリ数を上限チェック
DB側の集計pt-query-digest、pg_stat_statementsで、実行回数がとても多い単純なクエリを探す
直し方例注意点
関連の先読み(Eager Loading)Railsのincludes/preload、Djangoのselect_related/prefetch_related使わない関連まで読むと、かえってメモリを使う
IN句でまとめて取得WHERE id IN (…) で一度に取るIN句が数万件になるなら分割する
JOIN1回のクエリで結合して取得1対多だと行が増える。必要な列だけ取る
DataLoaderGraphQLで、同じリクエスト内の取得をまとめてバッチにするリクエストをまたいでキャッシュしない
キャッシュ・非正規化著者名を記事側にも持つ、集計値を事前計算する整合性の管理が必要

N+1はDBだけでなく、外部APIやキャッシュ(Redisへの1件ずつのGET)でも起きます。ループの中でネットワーク越しの呼び出しをしていないかを確認し、まとめて取得する(RedisならMGETやパイプライン)ようにします。

1件ずつ取りに行くと、往復の回数ぶん時間がかかる記事一覧100件と、それぞれの著者を表示する(1往復1msとした目安)N+1(101回)約106msSELECT * FROM posts(1回)→ 記事ごとに SELECT * FROM users WHERE id = ?(100回)まとめて取得(2回)約3msSELECT * FROM posts → SELECT * FROM users WHERE id IN (1, 2, 3, …)(またはJOIN)開発環境では気づかない理由データが10件なら11回で済み速い。本番で件数が増え、DBが別サーバー(往復が長い)になると一気に表面化する
N+1とまとめて取得の比較

クエリ1回は速くても、回数が多ければ往復の時間が積み重なる。

第7章「データベース」:アプリケーションとDB負荷

面接ではこう答える

N+1は、一覧を1回で取ったあと、各行の関連データを1件ずつ取りに行くため、クエリが1+N回発生する問題です。1回は速くても往復が積み重なり、データが増える本番で表面化します。APMのトレースで同じクエリの繰り返しを見つけ、ORMの先読みやIN句でまとめて取得して2回程度に減らします。外部APIやRedisへのループ内の呼び出しでも同じことが起きます。

別のページで開く →

DBラボを読み込んでいます…

ひとことで

遅いクエリは「たくさん読んでいる」か「待っている」かのどちらか。実行計画を見て、読む行を減らす。

手順は、①スロークエリログやPerformance Insights、pg_stat_statementsで「合計時間(1回の時間×回数)」が大きいクエリを見つける、②EXPLAIN(実際に実行して測るならEXPLAIN ANALYZE)で実行計画を見る、③読む行数を減らすようにインデックスやクエリを変える、④同じデータ量で前後を比べる、の順です。1回は遅くないが回数がとても多いクエリが、合計では一番重いことがよくあります。

よくある遅いパターン直し方
SELECT * で大量の列を取る必要な列だけ取る。カバリングインデックスが使えるようになる
WHERE句で列に関数や計算をかけるWHERE DATE(created_at) = … ではなく、範囲条件 created_at >= … AND < … にする
型の違う値で比べる文字列の列に数値で比べると暗黙の変換でインデックスが効かない。型をそろえる
LIKE '%キーワード%'前方一致にする。部分一致が必要なら全文検索(OpenSearchなど)
ORDER BY … LIMIT が遅い並べ替えの列をインデックスに含め、ソートをなくす(Using filesortを消す)
深いOFFSETキーセット(前ページの最後のIDから)方式にする
COUNT(*) を毎回数える概算で足りるなら統計情報を使う。正確さが必要なら集計値を別に持つ
ORで複数の列をまたぐUNIONに分けて、それぞれでインデックスを使う
インデックスが多すぎる書き込みが遅くなる。使われていないインデックスを削除する
ある日突然遅くなった統計情報の変化で実行計画が変わった。ANALYZEで統計を更新、実行計画を比較
-- 遅い:日付に関数をかけているのでインデックスが使えない
SELECT id, total FROM orders WHERE DATE(created_at) = '2026-11-01';

-- 速い:範囲条件にすると created_at のインデックスが使える
SELECT id, total FROM orders
 WHERE created_at >= '2026-11-01' AND created_at < '2026-11-02';

本番の大きなテーブルにインデックスを追加するときは、作成中のロックと負荷に注意します。MySQLのオンラインDDLやPostgreSQLのCREATE INDEX CONCURRENTLYを使い、負荷の低い時間帯に実施します。

OFFSETは「捨てる行」も全部読む。深いページほど遅くなるLIMIT 20 OFFSET 100000先頭から10万行読んで捨て、次の20行を返す読んで捨てる(ページが深いほど増える)返すWHERE id > 100000 ORDER BY id LIMIT 20前のページの最後のIDから、インデックスで直接始めるインデックスで一気に飛ぶキーセット(カーソル)方式のトレードオフ・どのページでも速さが一定。無限スクロールやAPIの「次へ」に向く・「38ページ目に直接飛ぶ」はできない。並び順の列にインデックスと一意性が必要
OFFSETとキーセット方式の比較

管理画面の深いページや、全件をページングで処理するバッチで効果が大きい。

面接ではこう答える

遅いクエリは、合計時間の大きいものからEXPLAINで実行計画を見て、読む行数を減らします。典型は、列に関数をかけてインデックスが効かない、SELECT *で余計な列を取る、ORDER BYでソートが発生する、深いOFFSETで大量の行を捨てている、などです。突然遅くなった場合は統計情報の変化による実行計画の変化を疑います。

別のページで開く →

DBラボを読み込んでいます…

ひとことで

同時にさばける数は、アプリのスレッド数、DBの接続数、各所の上限のうち一番小さいもので決まる。増やせば速くなるわけではない。

必要な並列数はリトルの法則で見積もれます。「同時に処理中の数 = 1秒あたりの件数 × 1件あたりの時間」です。たとえば毎秒200リクエストを平均50msで処理するなら、同時に処理中なのは約10件です。ここから、必要なスレッド数やDB接続数の目安が立ちます。処理が遅くなると同時に処理中の数が増え、スレッドや接続が足りなくなって、待ち行列がさらに伸びるという悪循環になります。

接続プールは、DBへの接続をあらかじめ作って使い回す仕組みです。接続を毎回作るのは重いので必須ですが、大きくしすぎるとDB側でコンテキストスイッチやロック競合が増えて、かえって遅くなります。HikariCPの資料では「コア数×2+ディスク数」程度が出発点として紹介されています。スケールアウトすると「台数×プールサイズ」でDBへの接続数が増えるので、DBの上限を超えないように計算しておきます。

設定考え方
プールサイズ小さめから始め、プールの待ち時間を見て増やす。台数の上限×サイズ<DBの上限
接続取得のタイムアウトプールが空かないときに無限に待たない(数百ms〜数秒)
アプリのスレッド・ワーカー数I/O待ちが多いなら多め、CPUを使う処理ならコア数程度
接続の集約RDS ProxyやPgBouncerで、アプリからの多数の接続を少ないDB接続にまとめる
監視プールの使用中・待ち・タイムアウト件数、DBの接続数
スケールアウトすると、DBへの接続数も掛け算で増えるアプリプール20アプリプール20アプリプール20アプリプール20アプリプール20アプリプール20アプリプール20アプリプール20アプリプール20アプリプール2010台 × 20接続 = 最大200接続オートスケールで20台になれば400接続DBmax_connections 150超えた分は接続エラー対策・台数の上限 × プールサイズ < DBの上限 になるよう決める。プールは大きすぎても遅くなる・RDS Proxy や PgBouncer で接続を集約する。Lambdaのように数が急増する実行環境では特に必要
スケールアウト時の接続数の計算

オートスケールの最大台数で計算しておかないと、負荷が高いときにDBが接続を拒否する。

面接ではこう答える

必要な並列数はリトルの法則で、1秒あたりの件数と1件の処理時間の積から見積もれます。接続プールは使い回しのために必須ですが、大きすぎるとDB側の競合で遅くなるので小さめから調整します。スケールアウトすると台数×プールサイズで接続が増えるので、最大台数でもDBの上限を超えないようにし、必要ならRDS Proxyで集約します。

ひとことで

同じ結果を何度も計算・取得しているなら、手前に置いて使い回す。効果はヒット率で決まり、最後の数%が大きく効く。

キャッシュの平均レイテンシは「ヒット率×キャッシュの時間+ミス率×元の時間」です。キャッシュ1ms、DB 50msなら、ヒット率90%で平均5.9ms、99%で1.5msと、最後の数%で数倍変わります。ヒット率は必ず監視し、下がったら原因(キーの設計、TTL、メモリ不足による追い出し)を調べます。

何をキャッシュするか例注意点
何度も読まれ、あまり変わらないものマスターデータ、設定、ランキング更新時の削除を忘れない
計算が重い結果集計、レコメンド、検索結果計算中の同時アクセスでスタンピードが起きる
外部APIの結果為替レート、住所検索相手の利用規約と鮮度の要件
静的ファイル・公開ページCDNでキャッシュ個人のデータを含むページはキャッシュしない

キャッシュ以外にも、事前計算(夜間バッチで集計しておく、マテリアライズドビュー)や、読み取りレプリカへの分散も、読み取りの負荷を下げる手段です。ただし、キャッシュは原因を隠すだけになりやすいので、遅い原因(N+1やインデックス不足)を直したうえで使うのが基本です。

ヒット率は最後の数%が効く。90%と99%で平均の速さが4倍違うキャッシュ1ms・DB 50msのとき、ヒット率ごとの平均レイテンシ0ms10ms20ms30ms40ms50ms0%50%90%100%ヒット率 →50% → 25.5ms90% → 5.9ms99% → 1.49ms
ヒット率と平均レイテンシ

90%から99%に上げると、平均の速さが4倍になる。

面接ではこう答える

キャッシュの効果はヒット率で決まり、平均レイテンシはヒット率で重みづけした平均になるので、90%と99%で数倍の差が出ます。何度も読まれ変化の少ないデータや計算の重い結果をキャッシュし、ヒット率と追い出しを監視します。ただし根本原因を隠さないよう、まずクエリなどを直したうえで使います。

ひとことで

利用者を待たせる必要のない処理は後回しにし、細かい処理はまとめて行う。応答時間とスループットの両方が改善する。

手法内容例
非同期化(キュー)リクエストでは受け付けだけして、重い処理はワーカーで後から行う画像変換、メール送信、外部連携、レポート作成
まとめて書き込む1件ずつINSERTせず、複数行を1回で入れるログやイベントのバルクINSERT
まとめて読むループで1件ずつではなく、まとめて取得N+1の解消、RedisのMGET
並列化互いに依存しない呼び出しを同時に行う複数の外部APIを並列に呼ぶ
事前計算重い計算を事前にしておき、表示時は読むだけ夜間の集計、ランキングの更新
分割大きなバッチを小分けにする大量UPDATEを数千行ずつ(ロックとレプリカ遅延を防ぐ)

非同期化すると、利用者への応答は速くなりますが、「処理がまだ終わっていない」状態が生まれます。画面での見せ方、失敗時のリトライ、二重実行を防ぐ冪等性、キューの滞留の監視をセットで設計します。

利用者を待たせる必要のない処理は、キューに渡して後でやる投稿の保存・画像の変換・通知メールの送信(目安)全部その場で画像を変換メール2.6秒キューに渡す0.13秒で応答ワーカー(裏側)画像を変換メール青い部分:データの保存(0.1秒)。緑:キューに入れるキューの滞留時間を監視し、ワーカーはキューの長さでスケールする。失敗時のリトライと冪等性もセットで利用者には「処理中」を見せ、終わったら反映する(画面側の設計も必要)
同期処理と非同期処理の比較

利用者の待ち時間は2.6秒から0.13秒に。裏の処理量は変わらないので、ワーカーの能力は別に確保する。

面接ではこう答える

利用者を待たせる必要のない重い処理は、キューに入れてワーカーで非同期に行い、応答を速くします。細かい読み書きはまとめて行い、依存しない呼び出しは並列にし、重い集計は事前計算しておきます。非同期化する場合は、処理中の見せ方、リトライと冪等性、キューの滞留の監視をセットで設計します。

ひとことで

CPUやメモリを何に使っているかを、推測ではなくプロファイラで見る。フレームグラフで「幅の広い関数」から直す。

プロファイラは、プログラムのどの関数がCPU時間やメモリを使っているかを測る道具です。結果はフレームグラフで見るのが一般的で、横幅が広い関数ほど時間を使っています。本番で常時、低い負荷で取り続ける継続的プロファイリング(Datadog、Pyroscopeなど)を入れておくと、遅くなったときに前後を比べられます。

よくある原因症状対策
大量のデータをアプリに持ってくるCPUとメモリが高い、GCが多い必要な行と列だけ取る、DB側で集計する
シリアライズ(JSON変換)大きなレスポンスでCPUを使う返す項目を絞る、ページングする
GCの停止p99が周期的に跳ねるヒープサイズやGC方式の調整、オブジェクト生成を減らす
ログの出しすぎI/OとCPUを使うログレベルの見直し、非同期出力、サンプリング
正規表現・文字列処理特定の入力で極端に遅い正規表現の見直し(バックトラックの爆発)
接続を毎回作るTLSハンドシェイクで遅いHTTPのKeep-Alive、接続プールで使い回す
ロック競合CPUは低いのに遅いロックの範囲を狭める、共有データを減らす
言語プロファイラの例
Javaasync-profiler、JFR
Gopprof
Pythonpy-spy
Rubyrbspy、stackprof
Node.js--prof、Clinic.js
Linux全般perf
フレームグラフ:横幅が広い関数ほどCPU時間を使っている下が呼び出し元、上が呼び出し先。横幅=その関数(と呼び出し先)で使った時間の割合mainhandleRequestOrderRepository.findrenderTemplateJSON.serializeDBドライバの結果変換(50%)escapeHtmlオブジェクト生成・GCこの例では、DBから大量の行を取ってアプリ側で変換している部分が半分。SELECT *をやめ、必要な行と列だけ取るのが効く取り方:Linuxはperf、Javaはasync-profiler、Goはpprof、Pythonはpy-spy、APMの継続的プロファイラ
フレームグラフの読み方

横幅の広い塊を見つけ、その関数がなぜそんなに時間を使うのかを考える。

面接ではこう答える

CPUやメモリの使い方はプロファイラで測り、フレームグラフで横幅の広い関数から直します。よくある原因は、大量のデータをアプリに持ってきて変換している、レスポンスのシリアライズが重い、GCの停止、ログの出しすぎ、接続を毎回作っている、などです。本番で継続的プロファイリングを入れておくと、劣化したときに前後を比較できます。

ひとことで

アプリとDBを直したうえで、足りない資源を足し、無駄な経路を減らす。どの資源が飽和しているかを見てから選ぶ。

手段効くとき注意点
スケールアウト(台数を増やす)アプリがCPUやスレッドで飽和DBの接続数や下流の能力も増える負荷に耐えられるか
スケールアップ(大きくする)DBなど分散しにくいもの上限がある。まず原因のクエリを直す
インスタンス種別の見直しCPU型・メモリ型・ネットワーク型の偏りGravitonで同性能を安く、T系のクレジット切れに注意
ストレージの性能ディスクI/O待ちが多いgp3のIOPS・スループットを追加、インスタンス側の帯域も確認
CDN・圧縮静的ファイルや遠い利用者gzip・Brotliで転送量を減らす。キャッシュの設定ミスに注意
距離を縮めるリージョン間やAZ間の往復が多い依存し合うサービスとDBを近くに置く
OS・ミドルウェアの上限接続数が多いサーバーファイルディスクリプタ(ulimit)、listenのバックログ(somaxconn)、Nginxのworker設定
オートスケールの調整スパイクに追いつかない指標の選び方、起動の速さ、最小台数、事前スケール

インフラで解決するのは最後の手段ではありませんが、原因を特定する前にお金で解決すると、根本原因が残り、コストも増えます。「どの資源が飽和しているか」をUSE法(使用率・飽和・エラー)で確認してから選びます。

第13章「コスト管理(FinOps)」:SREとコスト(FinOps)

面接ではこう答える

インフラ側では、飽和している資源を確認してから手段を選びます。アプリがCPUで飽和していればスケールアウト、DBなど分散しにくいものはスケールアップ、ディスクI/O待ちならストレージの性能、遠い利用者にはCDNと圧縮です。ファイルディスクリプタやバックログなどOSの上限、オートスケールの設定も確認します。原因を特定せずにお金で解決しないことが大事です。

ひとことで

本番に近い条件で負荷をかけ、どこまで耐えられるか、限界のとき何が先に詰まるかを事前に知る。

試験の種類目的
負荷試験(ロードテスト)想定のピーク負荷で、SLOを満たせるか
ストレステスト限界まで負荷を上げ、どこで何が壊れるか、壊れ方は安全か
スパイクテスト急激な増加(キャンペーン、TV放映)にオートスケールが追いつくか
耐久テスト(ソーク)長時間かけ続けて、メモリリークや接続の漏れがないか
手順内容
1 目標を決める想定ピークのRPS、許容するp99とエラー率
2 シナリオを作る本番のアクセスの比率(閲覧・検索・購入など)を再現する
3 環境とデータをそろえる本番に近い構成とデータ量。小さいデータではN+1やインデックスの問題が出ない
4 段階的に負荷を上げるいきなり最大にせず、限界点を探す
5 飽和箇所を見るCPU、DB接続、キュー、外部APIの制限など、何が先に詰まったか
6 直して再試験限界を上げ、結果を記録して次回と比較する

ツールはk6、Locust、JMeter、Gatlingなどです。負荷をかける側のマシンが先に限界になっていないか、キャッシュが温まった状態か冷えた状態か、外部サービスに負荷をかけてしまわないか(モックにするか、相手と調整するか)にも注意します。結果からリトルの法則で必要な台数を見積もり、オートスケールの最大台数とDBの接続数の上限を決めます。

限界点を超えると、処理量は増えずに待ち時間だけが伸びる同時アクセス数を増やしたときのスループットとレイテンシ(イメージ)限界点同時アクセス数 →スループット(処理数/秒)レイテンシ負荷試験では、この限界点がどこにあり、そのとき何(CPU・DB・接続数)が飽和しているかを見つける
スループットとレイテンシの関係

限界点の手前で運用し、そこに近づいたら増やす。限界点の位置は負荷試験でしか分からない。

面接ではこう答える

負荷試験は、想定ピークでSLOを満たせるかを確かめ、限界点と、そのとき何が先に詰まるかを見つけるために行います。本番に近いシナリオとデータ量を用意し、負荷を段階的に上げて、CPU、DB接続、外部APIの制限など飽和箇所を特定します。結果から必要な台数を見積もり、オートスケールの上限やDBの接続数の上限を決めます。

この章のまとめ

🖨 まとめシート(印刷・PDF)
  • 目標を数字で決め、計測でボトルネックを1つに絞り、1つずつ直す流れを説明できるようにする
  • N+1、インデックス、接続プール、キャッシュ、非同期化など、定番の原因と直し方を押さえる
  • パーセンタイルの見方、プロファイリング、負荷試験で「どこまで耐えられるか」を語れるようにする

この章のクイズ

面接ではこう聞かれる

まず答えを見ずに声に出して答え、そのあと開いて比べる。

Q1APIが遅いと報告がありました。どう調べて改善しますか?

模範回答

まず対象のエンドポイントと、平均ではなくp95・p99がどれだけ悪化しているか、いつからかを確認します。直前のデプロイやデータ量の変化も見ます。次にAPMのトレースで1リクエストの時間の内訳を見て、DB、外部API、アプリのどこで時間を使っているかを特定します。DBならクエリの回数(N+1)と実行計画、アプリならプロファイラを見ます。一番大きい部分について仮説を立て、1つだけ変更して同じ条件で効果を測り、最後にダッシュボードやアラートに残して回帰を防ぎます。

深掘りされたら

  • 平均を見てはいけないのはなぜですか?
  • 改善の目標値はどう決めますか?

見られているポイント計測から始める進め方

Q2N+1問題とは何ですか?どう見つけて、どう直しますか?

模範回答

一覧を1回のクエリで取ったあと、各行の関連データを1件ずつ取りに行くことで、クエリが1+N回発生する問題です。1回は速くても往復が積み重なり、データが増える本番で遅くなります。APMのトレースで同じ形のクエリが大量に並んでいることや、1リクエストあたりのクエリ数で見つけます。ORMの先読み(Railsのincludesなど)やIN句でまとめて取得して、2回程度に減らします。開発時はBulletのような検知ツールや、テストでクエリ数の上限をチェックして防ぎます。

深掘りされたら

  • 先読みしすぎると何が問題になりますか?
  • 外部APIでも起きますか?

見られているポイント原因・検知・修正・予防の4点

Q3ある日突然、特定のクエリが遅くなりました。何を疑いますか?

模範回答

コードの変更がないなら、データ量の増加か、統計情報の変化で実行計画が変わったことを疑います。EXPLAINで今の実行計画を確認し、以前と比べて使うインデックスや結合の順番が変わっていないかを見ます。統計情報をANALYZEで更新すると戻ることがあります。ほかに、ロック待ちが増えていないか、同時に重いバッチが走っていないか、ストレージのバースト切れでI/Oが遅くなっていないかも確認します。

深掘りされたら

  • 実行計画が揺れないようにするには?

見られているポイント実行計画の変化という視点

Q4接続プールのサイズはどう決めますか?

模範回答

リトルの法則で、1秒あたりの件数と1件あたりのDB利用時間から必要な同時接続数を見積もり、小さめから始めて、プールの待ち時間を見ながら調整します。大きすぎるとDB側で競合が増えて遅くなります。オートスケールの最大台数×プールサイズがDBの最大接続数を超えないようにし、超えるならRDS Proxyなどで集約します。接続取得のタイムアウトも設定して、無限に待たないようにします。

深掘りされたら

  • Lambdaから RDS に接続するときの注意点は?

見られているポイント掛け算と上限の意識

Q5p99だけが周期的に跳ねています。原因として何を考えますか?

模範回答

周期的ならGCの停止、定期バッチやcronの実行、キャッシュの一斉失効、ログのローテーション、バックアップやVACUUMなどの定期処理を疑います。トレースで遅いリクエストの時間帯を特定し、そのときのGCログ、DBの負荷、定期処理の実行時刻と突き合わせます。

深掘りされたら

  • GCが原因だと分かったら、どう対処しますか?

見られているポイント周期性から原因を絞る

Q6負荷試験はどうやって行いますか?

模範回答

想定ピークのRPSと許容するp99・エラー率を目標として決め、本番のアクセスの比率を再現したシナリオを作ります。本番に近い構成とデータ量の環境で、負荷を段階的に上げて限界点を探し、そのとき何が先に詰まるか(CPU、DB接続、外部APIの制限など)を特定します。直して再試験し、結果からオートスケールの上限やDBの接続数の上限を決めます。負荷をかける側が先に限界にならないこと、外部サービスに負荷をかけないことにも注意します。

深掘りされたら

  • 本番で負荷試験をすることはありますか?

見られているポイント目標・再現性・飽和箇所の特定

Q7インフラを増強すれば解決するのでは、と言われたらどう答えますか?

模範回答

短期的に影響を止める手段としては有効なので、緊急時はスケールアウトやスケールアップをします。ただし原因が1本のクエリやN+1なら、データが増えれば再発し、コストも増え続けます。どの資源が飽和しているか、時間の内訳がどこかを示したうえで、根本原因の修正とセットで判断することを提案します。

見られているポイント短期の緩和と根本対策の区別