ホーム / SRE / 第5部 インフラ・セキュリティ・性能・コスト
目次 まとめ クイズ 面接 ↑ 上へ この章で押さえること
目標を数字で決め、計測でボトルネックを1つに絞り、1つずつ直す流れを説明できるようにする N+1、インデックス、接続プール、キャッシュ、非同期化など、定番の原因と直し方を押さえる パーセンタイルの見方、プロファイリング、負荷試験で「どこまで耐えられるか」を語れるようにする 遅さの原因を計測で見つけ、定番の原因(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 アプリ 250 1000ms 1リクエストの内訳(ms) アプリを2倍速く DB 600 875ms 頑張っても12.5%しか縮まない DBを半分に DB 300 アプリ 250 700ms 同じ「半分」でも30%縮む
アムダールの法則 全体の60%を占めるDBを半分にする方が、25%のアプリを2倍にするより効く。
面接ではこう答える 読み上げ
まず「このAPIのp99を300ms以下」のようにユーザー体験の数字で目標を決めます。次にAPMのトレースで時間の内訳を見て、一番大きい部分をボトルネックとして選びます。仮説を立てて1つだけ変更し、同じ条件で効果を測り、最後に監視や負荷試験に組み込んで回帰を防ぎます。全体に占める割合が小さい部分を直しても効果が小さいので、内訳の大きいところから手をつけます。
SREとの関わり 性能障害の調査、リリース前の性能確認、コストと性能のバランスの判断に直結します。
ひとことで レイテンシは平均ではなく分布で見る。特に遅い側の尻尾(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のレスポンス時間の分布(例) 0ms 250ms 500ms 750ms 1000ms p50 95ms p95 330ms p99 620ms 平均 150ms 100人に1人は620ms以上待っている。重い利用者(データが多い人=よく使う人)ほど尻尾側にいることが多い レイテンシの分布とパーセンタイル 平均150msでも、100人に1人は620ms以上待っている。
呼び出し先が増えるほど、どれか1つが遅い確率は急に上がる 1回のリクエストで並列に呼ぶサービス数と、どれかがp99の遅さに当たる確率 0% 25% 50% 75% 100% 1 25 50 75 100 並列に呼ぶ数 → 1個なら1%、10個で約10% 100個 → 約63% 50個 → 約39% 並列呼び出しによるテールの増幅 依存先が増えるほど、p99の対策が全体の速さを決める。
面接ではこう答える 読み上げ
レイテンシは右に長い尻尾のある分布になるので、平均ではなくp50、p95、p99で見ます。マイクロサービスで多くのサービスを並列に呼ぶと、どれか1つが遅い確率が上がり、全体が一番遅いものに引きずられます。呼び出し数の削減、タイムアウトとフォールバック、ヘッジリクエスト、GCやロックなど遅い原因の除去でテールを縮めます。
SREとの関わり SLOの指標設計、ダッシュボードの作り方、マイクロサービスの設計判断に関わります。
ひとことで 一覧を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句が数万件になるなら分割する JOIN 1回のクエリで結合して取得 1対多だと行が増える。必要な列だけ取る DataLoader GraphQLで、同じリクエスト内の取得をまとめてバッチにする リクエストをまたいでキャッシュしない キャッシュ・非正規化 著者名を記事側にも持つ、集計値を事前計算する 整合性の管理が必要
N+1はDBだけでなく、外部APIやキャッシュ(Redisへの1件ずつのGET)でも起きます。ループの中でネットワーク越しの呼び出しをしていないかを確認し、まとめて取得する(RedisならMGETやパイプライン)ようにします。
1件ずつ取りに行くと、往復の回数ぶん時間がかかる 記事一覧100件と、それぞれの著者を表示する(1往復1msとした目安) N+1(101回) 約106ms SELECT * FROM posts(1回)→ 記事ごとに SELECT * FROM users WHERE id = ?(100回) まとめて取得(2回) 約3ms SELECT * 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へのループ内の呼び出しでも同じことが起きます。
SREとの関わり APMで遅いエンドポイントを見つけたとき、まず疑う原因の1つです。開発チームへの改善提案でもよく使います。
ブラウザで試す(DB): N+1 と JOIN の速度を比べる閉じる ひとことで 遅いクエリは「たくさん読んでいる」か「待っている」かのどちらか。実行計画を見て、読む行を減らす。
手順は、①スロークエリログや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で大量の行を捨てている、などです。突然遅くなった場合は統計情報の変化による実行計画の変化を疑います。
SREとの関わり スロークエリ対応、DBのCPU高騰の調査、インデックス追加のリスク判断に関わります。
ブラウザで試す(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 アプリ プール20 10台 × 20接続 = 最大200接続 オートスケールで20台になれば400接続 DB max_connections 150 超えた分は接続エラー 対策 ・台数の上限 × プールサイズ < DBの上限 になるよう決める。プールは大きすぎても遅くなる ・RDS Proxy や PgBouncer で接続を集約する。Lambdaのように数が急増する実行環境では特に必要 スケールアウト時の接続数の計算 オートスケールの最大台数で計算しておかないと、負荷が高いときにDBが接続を拒否する。
面接ではこう答える 読み上げ
必要な並列数はリトルの法則で、1秒あたりの件数と1件の処理時間の積から見積もれます。接続プールは使い回しのために必須ですが、大きすぎるとDB側の競合で遅くなるので小さめから調整します。スケールアウトすると台数×プールサイズで接続が増えるので、最大台数でもDBの上限を超えないようにし、必要ならRDS Proxyで集約します。
SREとの関わり 「負荷が上がるとDB接続エラー」障害、オートスケールの上限設定、Lambda×RDSの構成に関わります。
ひとことで 同じ結果を何度も計算・取得しているなら、手前に置いて使い回す。効果はヒット率で決まり、最後の数%が大きく効く。
キャッシュの平均レイテンシは「ヒット率×キャッシュの時間+ミス率×元の時間」です。キャッシュ1ms、DB 50msなら、ヒット率90%で平均5.9ms、99%で1.5msと、最後の数%で数倍変わります。ヒット率は必ず監視し、下がったら原因(キーの設計、TTL、メモリ不足による追い出し)を調べます。
何をキャッシュするか 例 注意点 何度も読まれ、あまり変わらないもの マスターデータ、設定、ランキング 更新時の削除を忘れない 計算が重い結果 集計、レコメンド、検索結果 計算中の同時アクセスでスタンピードが起きる 外部APIの結果 為替レート、住所検索 相手の利用規約と鮮度の要件 静的ファイル・公開ページ CDNでキャッシュ 個人のデータを含むページはキャッシュしない
キャッシュ以外にも、事前計算 (夜間バッチで集計しておく、マテリアライズドビュー)や、読み取りレプリカ への分散も、読み取りの負荷を下げる手段です。ただし、キャッシュは原因を隠すだけになりやすいので、遅い原因(N+1やインデックス不足)を直したうえで使うのが基本です。
ヒット率は最後の数%が効く。90%と99%で平均の速さが4倍違う キャッシュ1ms・DB 50msのとき、ヒット率ごとの平均レイテンシ 0ms 10ms 20ms 30ms 40ms 50ms 0% 50% 90% 100% ヒット率 → 50% → 25.5ms 90% → 5.9ms 99% → 1.49ms ヒット率と平均レイテンシ 90%から99%に上げると、平均の速さが4倍になる。
面接ではこう答える 読み上げ
キャッシュの効果はヒット率で決まり、平均レイテンシはヒット率で重みづけした平均になるので、90%と99%で数倍の差が出ます。何度も読まれ変化の少ないデータや計算の重い結果をキャッシュし、ヒット率と追い出しを監視します。ただし根本原因を隠さないよう、まずクエリなどを直したうえで使います。
SREとの関わり キャッシュ導入の効果見積もり、Redisのメモリ設計、ヒット率低下時の調査に関わります。
ひとことで 利用者を待たせる必要のない処理は後回しにし、細かい処理はまとめて行う。応答時間とスループットの両方が改善する。
手法 内容 例 非同期化(キュー) リクエストでは受け付けだけして、重い処理はワーカーで後から行う 画像変換、メール送信、外部連携、レポート作成 まとめて書き込む 1件ずつINSERTせず、複数行を1回で入れる ログやイベントのバルクINSERT まとめて読む ループで1件ずつではなく、まとめて取得 N+1の解消、RedisのMGET 並列化 互いに依存しない呼び出しを同時に行う 複数の外部APIを並列に呼ぶ 事前計算 重い計算を事前にしておき、表示時は読むだけ 夜間の集計、ランキングの更新 分割 大きなバッチを小分けにする 大量UPDATEを数千行ずつ(ロックとレプリカ遅延を防ぐ)
非同期化すると、利用者への応答は速くなりますが、「処理がまだ終わっていない」状態が生まれます。画面での見せ方、失敗時のリトライ、二重実行を防ぐ冪等性、キューの滞留の監視をセットで設計します。
利用者を待たせる必要のない処理は、キューに渡して後でやる 投稿の保存・画像の変換・通知メールの送信(目安) 全部その場で 画像を変換 メール 2.6秒 キューに渡す 0.13秒で応答 ワーカー(裏側) 画像を変換 メール 青い部分:データの保存(0.1秒)。緑:キューに入れる キューの滞留時間を監視し、ワーカーはキューの長さでスケールする。失敗時のリトライと冪等性もセットで 利用者には「処理中」を見せ、終わったら反映する(画面側の設計も必要) 同期処理と非同期処理の比較 利用者の待ち時間は2.6秒から0.13秒に。裏の処理量は変わらないので、ワーカーの能力は別に確保する。
面接ではこう答える 読み上げ
利用者を待たせる必要のない重い処理は、キューに入れてワーカーで非同期に行い、応答を速くします。細かい読み書きはまとめて行い、依存しない呼び出しは並列にし、重い集計は事前計算しておきます。非同期化する場合は、処理中の見せ方、リトライと冪等性、キューの滞留の監視をセットで設計します。
SREとの関わり APIの応答改善、バッチ処理の設計、大量更新によるレプリカ遅延の防止に関わります。
ひとことで CPUやメモリを何に使っているかを、推測ではなくプロファイラで見る。フレームグラフで「幅の広い関数」から直す。
プロファイラ は、プログラムのどの関数がCPU時間やメモリを使っているかを測る道具です。結果はフレームグラフ で見るのが一般的で、横幅が広い関数ほど時間を使っています。本番で常時、低い負荷で取り続ける継続的プロファイリング (Datadog、Pyroscopeなど)を入れておくと、遅くなったときに前後を比べられます。
よくある原因 症状 対策 大量のデータをアプリに持ってくる CPUとメモリが高い、GCが多い 必要な行と列だけ取る、DB側で集計する シリアライズ(JSON変換) 大きなレスポンスでCPUを使う 返す項目を絞る、ページングする GCの停止 p99が周期的に跳ねる ヒープサイズやGC方式の調整、オブジェクト生成を減らす ログの出しすぎ I/OとCPUを使う ログレベルの見直し、非同期出力、サンプリング 正規表現・文字列処理 特定の入力で極端に遅い 正規表現の見直し(バックトラックの爆発) 接続を毎回作る TLSハンドシェイクで遅い HTTPのKeep-Alive、接続プールで使い回す ロック競合 CPUは低いのに遅い ロックの範囲を狭める、共有データを減らす
言語 プロファイラの例 Java async-profiler、JFR Go pprof Python py-spy Ruby rbspy、stackprof Node.js --prof、Clinic.js Linux全般 perf
フレームグラフ:横幅が広い関数ほどCPU時間を使っている 下が呼び出し元、上が呼び出し先。横幅=その関数(と呼び出し先)で使った時間の割合 main handleRequest OrderRepository.find renderTemplate JSON.serialize DBドライバの結果変換(50%) escapeHtml オブジェクト生成・GC この例では、DBから大量の行を取ってアプリ側で変換している部分が半分。SELECT *をやめ、必要な行と列だけ取るのが効く 取り方:Linuxはperf、Javaはasync-profiler、Goはpprof、Pythonはpy-spy、APMの継続的プロファイラ
フレームグラフの読み方 横幅の広い塊を見つけ、その関数がなぜそんなに時間を使うのかを考える。
面接ではこう答える 読み上げ
CPUやメモリの使い方はプロファイラで測り、フレームグラフで横幅の広い関数から直します。よくある原因は、大量のデータをアプリに持ってきて変換している、レスポンスのシリアライズが重い、GCの停止、ログの出しすぎ、接続を毎回作っている、などです。本番で継続的プロファイリングを入れておくと、劣化したときに前後を比較できます。
SREとの関わり 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の上限、オートスケールの設定も確認します。原因を特定せずにお金で解決しないことが大事です。
SREとの関わり キャパシティ計画、インスタンス選定、コスト最適化と性能の両立に関わります。
ひとことで 本番に近い条件で負荷をかけ、どこまで耐えられるか、限界のとき何が先に詰まるかを事前に知る。
試験の種類 目的 負荷試験(ロードテスト) 想定のピーク負荷で、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の接続数の上限を決めます。
SREとの関わり 大型イベント前の準備、キャパシティ計画、オートスケール設定の根拠づくりに直結します。
目標を数字で決め、計測でボトルネックを1つに絞り、1つずつ直す流れを説明できるようにする N+1、インデックス、接続プール、キャッシュ、非同期化など、定番の原因と直し方を押さえる パーセンタイルの見方、プロファイリング、負荷試験で「どこまで耐えられるか」を語れるようにする 面接ではこう聞かれるまず答えを見ずに声に出して答え、そのあと開いて比べる。
すべて開く すべて閉じる
Q1 APIが遅いと報告がありました。どう調べて改善しますか? 模範回答
まず対象のエンドポイントと、平均ではなくp95・p99がどれだけ悪化しているか、いつからかを確認します。直前のデプロイやデータ量の変化も見ます。次にAPMのトレースで1リクエストの時間の内訳を見て、DB、外部API、アプリのどこで時間を使っているかを特定します。DBならクエリの回数(N+1)と実行計画、アプリならプロファイラを見ます。一番大きい部分について仮説を立て、1つだけ変更して同じ条件で効果を測り、最後にダッシュボードやアラートに残して回帰を防ぎます。
深掘りされたら
平均を見てはいけないのはなぜですか? 改善の目標値はどう決めますか? 見られているポイント 計測から始める進め方
Q2 N+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 に接続するときの注意点は? 見られているポイント 掛け算と上限の意識
Q5 p99だけが周期的に跳ねています。原因として何を考えますか? 模範回答
周期的ならGCの停止、定期バッチやcronの実行、キャッシュの一斉失効、ログのローテーション、バックアップやVACUUMなどの定期処理を疑います。トレースで遅いリクエストの時間帯を特定し、そのときのGCログ、DBの負荷、定期処理の実行時刻と突き合わせます。
深掘りされたら
見られているポイント 周期性から原因を絞る
Q6 負荷試験はどうやって行いますか? 模範回答
想定ピークのRPSと許容するp99・エラー率を目標として決め、本番のアクセスの比率を再現したシナリオを作ります。本番に近い構成とデータ量の環境で、負荷を段階的に上げて限界点を探し、そのとき何が先に詰まるか(CPU、DB接続、外部APIの制限など)を特定します。直して再試験し、結果からオートスケールの上限やDBの接続数の上限を決めます。負荷をかける側が先に限界にならないこと、外部サービスに負荷をかけないことにも注意します。
深掘りされたら
見られているポイント 目標・再現性・飽和箇所の特定
Q7 インフラを増強すれば解決するのでは、と言われたらどう答えますか? 模範回答
短期的に影響を止める手段としては有効なので、緊急時はスケールアウトやスケールアップをします。ただし原因が1本のクエリやN+1なら、データが増えれば再発し、コストも増え続けます。どの資源が飽和しているか、時間の内訳がどこかを示したうえで、根本原因の修正とセットで判断することを提案します。
見られているポイント 短期の緩和と根本対策の区別