ノイジーネイバー(Noisy Neighbor)問題とは、同じ物理サーバー・ノード・データベースを共有する別の利用者(隣人)が資源を大量に使い、自分のシステムの応答が遅くなったり失敗したりする現象です。「ノイジー(noisy)」は英語で「うるさい・騒がしい」の意味で、日本語では「うるさい隣人問題」とも呼ばれます。
この記事では、問題が起きる理由を押さえたうえで、遅延の原因が「自分の負荷」なのか「隣の負荷」なのかを指標で切り分ける方法と、使う側・提供する側それぞれの対策を扱います。
まとめ:ノイジーネイバー問題の要点
- 原因は共有資源の競合です。全員が同時に最大まで使わない前提で容量を超える枠を割り当てるオーバーコミットは、競合を招く要因の一つです。Microsoftも「完全に回避できない」リスクと位置づけており、目標は発生確率と影響範囲を下げることです。
- 切り分けの指標は、VMならCPUのスチール時間(
topのst)、Linuxなら/proc/pressureのPSI、コンテナならcpu.statのnr_throttled。自分の使用率が平常のまま遅延だけが増えていれば、隣を疑います。 - クラウドやSaaSを使う側は、スロットリングに従う再試行と、専有環境・分離の強いティアへの移行が打ち手です。提供する側は「テナント別の計測 → クォータ・レート制限・クエリ上限 → 分離」の順で進めます。
| レイヤー | 典型的な症状 | 最初に打つ手 |
|---|---|---|
| クラウドVM・仮想化基盤 | st値の上昇、処理時間のばらつき | 専有インスタンス、インスタンス変更 |
| Kubernetes | 同一ノードのPodが遅延・退避 | requests/limits、ResourceQuota |
| マルチテナントSaaS | 特定テナントの集中で全体が遅延 | テナント単位のレート制限・公平キュー |
| 共有データベース | 1社の重い集計で他社の画面が遅延 | クエリの実行時間・件数の上限 |
ノイジーネイバーの意味と、問題が起きる条件
Azure Architecture Centerのアンチパターン解説は、この問題を「別のテナントのアクティビティが原因で 1 つのテナントのパフォーマンスが低下したときに発生」するものと定義しています。
起きる条件は2通りあります。1つは、1テナントが突出して資源を使うケースです。もう1つは見落とされがちで、各テナントの使用量は小さくても、ピーク時間帯が重なって合計が容量を超えるケースです。後者では突出した負荷のテナントがいないため、個別の停止だけに頼らず、合計負荷の抑制や容量増強、ピーク時間帯の分散で対処します。
アプリケーションがシングルテナントでも、物理ホスト・ストレージ・ネットワークを共有していれば、その層で性能干渉が起こり得ます。分離の効果は、どの資源まで専用化するかで決まります。テナントの分け方ごとの特徴はシングルテナントとマルチテナントの違いで比較しています。
共有基盤がオーバーコミットを前提にする理由
共有基盤の多くは、全利用者が同時に上限まで使うことはまれだという前提で、物理資源の量を超える枠を割り当てています(オーバーコミット)。仮想化ではハイパーバイザーが物理CPUより多いvCPUを配り、Kubernetesでも、スケジューラーがノードのCPU・メモリ容量に収まるかを判定するときは、limitsではなくrequestsの合計を使います。Kubernetesの公式ドキュメントも、ノードのlimitsの合計は100%を超えてよい(overcommitted)と明記しています。普段は空きを融通し合えるので費用が下がりますが、複数の利用者が同時に上限近くまで使った瞬間に、割り当てた枠どおりの資源が出てこなくなります。これがノイジーネイバーの土台です。
競合が起きる共有リソースと発生の仕組み
「隣に負ける」資源はCPUだけではありません。どの資源で競合しているかによって、見るべき指標も打てる対策も変わります。
| 共有リソース | 競合の中身 | 確認する指標の例 |
|---|---|---|
| CPU時間 | ハイパーバイザーやカーネルの割り当て待ち | steal、PSI cpu |
| メモリ・キャッシュ | メモリ不足による回収待ち、LLC・メモリ帯域の競合 | 不足:PSI memory/競合:キャッシュミス率・帯域 |
| ディスクI/O | IOPS・帯域の飽和 | PSI io、I/O待ち時間 |
| ネットワーク | NIC・上流帯域の飽和 | 再送、帯域使用率 |
| データベース | 接続枠・ロック・重いクエリ | 接続数、テナント別クエリ時間 |
| キュー・ワーカー | 1テナントの大量ジョブで滞留 | テナント別の待ち時間 |
下の層ほど利用者側からは見えません。クラウドVMの利用者は同じ物理ホストに誰がいるかを知る手段がないため、症状は「同じリクエストが、時間によって成功したり失敗したりする」形で現れます。Azureのガイドも、この不規則さを検出の手がかりに挙げています。
レイヤー別に見る発生パターン
クラウドVM・仮想化基盤:CPUスチール時間
仮想マシンでは、vCPUが実行したいのにハイパーバイザーが物理CPUを他のVMに渡している時間が生じます。Linuxの top の st 欄がこれで、man pageは「time stolen from this vm by the hypervisor」と説明しています。自分のプロセスが同じ量の計算をしているのに処理時間だけ延び、st値が上がっていれば、同じホストに重いVMが同居している可能性が高いと判断できます。
Kubernetes:requests・limits未設定Podによる資源競合
Kubernetesでは、複数のPodが同じノードのCPUとメモリを分け合います。requests・limitsを1つも指定していないPodは BestEffort クラスになり、ノードの空き資源を上限なく使えます。そのPodが急にメモリを使えば、同じノードの別チームのPodが遅くなるか、ノードの圧迫で退避(eviction)されます。退避対象はQoSクラスの名前だけでは決まりません。ノード圧迫による退避の公式ドキュメントによると、kubeletは使用量がrequestsを超えているか、Podの優先度(Priority)、requestsに対する使用量を基に選びます。requestsを超えて使っている BestEffort・Burstable のPodが先に、Guaranteed とrequests以内の Burstable が最後に回ります。
マルチテナントSaaS:1テナントのクエリ・バッチ・ジョブ
SaaSでは、1社の月次バッチや大量CSVインポート、重い集計クエリが共有データベースやジョブキューを占有し、他社の画面操作まで遅くします。キューの場合は、先に投入された大量ジョブが後から来た他テナントのジョブを待たせる「先着順の不公平」が起きます。LayerXのエンジニアブログは、Temporalでのノイジーネイバー対策として、レート制限と2本のタスクキューを組み合わせた再現テストを公開しています。
ノイジーネイバーの検知指標と自分の負荷との切り分け
遅延が起きたとき、最初に判定すべきは「自分の使用量が増えたのか」です。自分の使用率・リクエスト数が平常で、待ち時間の指標だけが増えていれば、ノイジーネイバーを疑う根拠になります。平常時の値を記録していなければ比べられないため、Azureのガイドも利用者側にベースラインの性能指標を残すよう求めています。
ホスト・コンテナで見る指標
| 指標 | 取得元 | 読み方 |
|---|---|---|
| st(スチール時間) | top、vmstat | 上昇=ハイパーバイザーによるCPU実行待ち |
| PSI some / full | /proc/pressure/* | 資源待ちで止まった時間の割合 |
| nr_throttled | cgroup v2 cpu.stat | 自分のCPU上限で止められた回数 |
| p99レイテンシー | APM・メトリクス | 平均が平常でも裾が伸びる |
PSI(Pressure Stall Information)は、CPU・メモリ・I/Oの資源不足でタスクが止まった時間の割合を示すLinuxカーネルの機能です。some 行は少なくとも一部のタスクが待たされた時間、full 行はアイドル状態ではない全タスクが同時に資源待ちで停止した時間の割合を表し、直近10秒・60秒・300秒の平均が出ます。ただし、システム全体のCPUのfullは未定義で、表示されても互換性のために0が出るだけなので、CPU競合の判定には使えません。
# CPUのスチール時間(st)を表示。初回は起動後の平均、以降4回は1秒間隔
vmstat 1 5
# 資源待ちの割合(some/full、avg10は直近10秒の平均)
cat /proc/pressure/cpu
cat /proc/pressure/io
# Linux・cgroup v2環境で実行する例
# 以下のパスは対象コンテナのcgroupがこの位置に見える場合のみ有効
# ホストから読む場合は対象コンテナのcgroupディレクトリ内のcpu.statを指定
# CPUコントローラーが有効な対象cgroupのスロットリング回数と時間
cat /sys/fs/cgroup/cpu.stat | grep -E 'nr_throttled|throttled_usec'
Kubernetesでは、コンテナがCPU上限で止められた回数を、kubeletに組み込まれたcAdvisorが container_cpu_cfs_throttled_periods_total として出します。Prometheusで監視を構築する手順の構成で集める場合は、まずkubeletのcAdvisorメトリクスが収集対象に含まれるか確認します。ノードのsteal・PSIやアプリケーションのp99は取得元が異なるため、それぞれ別に収集設定が必要です。
ノイジーネイバーと誤認しやすい現象
次の2つは、症状が似ていても原因は自分側にあります。隣を疑う前に除外してください。
- バースト可能インスタンスのCPUクレジット枯渇:AWSのT系インスタンスは、ベースラインを超えて使うとクレジットを消費します。Standardモードでは、蓄積したCPUクレジットの残量が少なくなると、CPU使用率がベースラインまで徐々に制限されます。CloudWatchの
CPUCreditBalanceが0付近なら、隣ではなく自分の使いすぎです。 - 自分のCPU limitによるスロットリング:cgroup v2の
cpu.maxは「一定期間(既定は100,000マイクロ秒)にどれだけCPUを使えるか」の上限で、ノードのCPUが空いていても上限に達すれば止められます。nr_throttledが増えているなら、limitの設定値を見直す問題です。
SaaSの検知指標:テナントIDによる負荷の集計
SaaSの場合、全体の平均レイテンシーを見ても「誰が騒いでいるか」は分かりません。Azureのガイドは、Cosmos DBの要求ユニット(RU)を要求ごとに記録し、テレメトリにテナントIDを含めてテナント別に集計する方法を例に挙げています。ログとトレースにはテナントIDを記録し、資源使用量と対応づけます。Prometheusなどのメトリクスにも付ける場合は、テナント数と他のラベルの組み合わせによる時系列数を見積もり、対象指標を絞ります。
クラウドやSaaSを使う側でできる対策
利用者は隣のテナントを止められません。打てる手は、影響を受けたときに被害を広げないことと、影響を受けにくい構成に移ることです。
スロットリングに従う再試行
共有サービスが混み合うと、HTTP 429や503で要求を断られます。すぐに同じ要求を投げ直すと、混雑している基盤にさらに負荷を足すことになります。Amazon Builders’ Libraryの再試行の解説は、再試行を「利己的(selfish)」と表現し、過負荷が原因の失敗では再試行が状況をかなり悪くしうると書いています。Retry-After ヘッダーがあれば従い、無ければ指数バックオフにランダムな揺らぎ(ジッター)を加えます。再試行回数には上限を設け、更新処理では冪等性や重複実行を防ぐ仕組みを確認します。
専有環境・分離の強いティア・予約容量への移行
他のAWSアカウントのVMとホストを共有しないようにするには、AWSのDedicated Instances・Dedicated Hostsで専有ハードウェアを使います。ただし、同一アカウントのVM同士の競合や、別途共有されるストレージ(EBSなど)での競合は残り得ます。マネージドサービスでは、Azureのガイドが利用者側の対策として、Cosmos DBの予約済みスループットの購入、Service BusのPremiumレベルへの移行、ExpressRouteで性能に敏感な環境用に回線を分けることを挙げています。いずれも費用が上がるため、指標で隣の影響を確認してから選びます。
なお、Firecracker(マイクロVM)のような分離技術はセキュリティ境界を強めるもので、物理CPUやメモリを共有する以上、性能面の競合は残ります。AWS上で専有化と費用の線引きを含めて構成を見直す場合は、AWS・Google Cloud・Azureのインフラ構築支援でも相談を受けています。
Kubernetesの対策:QoSクラスとNamespace単位の上限
メモリ圧迫時の退避されにくさを重視するPodでは、負荷に見合うCPU・メモリ量を見積もり、全コンテナの requests と limits を同じ値にして Guaranteed にします。ただし、あらゆる資源の圧迫で最後になる保証はありません。また、CPU limitによるスロットリングは残るため、遅延要件を満たすかは負荷試験で確認します。運用中に nr_throttled が増え続けたら、1.35でStableになったIn-place Pod Resizeで、Podを作り直さずにrequests・limitsを変更できます。Deployment管理下のPodは、テンプレート側も直さないと次の再作成で元の値に戻ります。
apiVersion: v1
kind: Pod
metadata:
name: api
spec:
containers:
- name: api
image: example.com/api:1.4.2 # 例示用。利用可能な自分のイメージに置換
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "500m" # requests と同じ値で Guaranteed になる
memory: "512Mi"
チームや顧客ごとにNamespaceを分けている場合は、Namespace内のrequests・limitsの設定値の合計を ResourceQuota で、コンテナの既定値を LimitRange で制限します。LimitRangeで既定のrequestsを入れておけば、指定を忘れたPodが BestEffort になることは避けられます。ただし、requestsは使用上限ではないため、上限も必要ならlimitsの既定値や最大値を別途設定します。重要度の違うワークロードが混在するなら、PriorityClass で優先度を付けます。高優先のPodが配置先を見つけられないとき、低優先のPodを押し出して(プリエンプション)場所を空けられます。
提供する側の対策:マルチテナントSaaS
テナント単位のレート制限
API全体に1つの上限を設けても、1テナントが上限を使い切れば他テナントが弾かれます。上限はテナントごとに持たせます。次はトークンバケット方式の最小実装で、テナントごとに毎秒5トークンを補充し、最大10トークンを保持します。満杯なら10件を直ちに通せますが、任意の1秒間を必ず5件以下に制限する方式ではありません。
import time
from collections import defaultdict
class TenantTokenBucket:
"""テナントごとに独立したトークンバケット。rate=毎秒の補充数、burst=上限"""
def __init__(self, rate: float, burst: int):
self.rate, self.burst = rate, burst
self.tokens = defaultdict(lambda: float(burst))
self.updated = {} # テナントごとの前回補充時刻
def allow(self, tenant_id: str) -> bool:
now = time.monotonic()
elapsed = now - self.updated.get(tenant_id, now)
self.updated[tenant_id] = now
self.tokens[tenant_id] = min(self.burst, self.tokens[tenant_id] + elapsed * self.rate)
if self.tokens[tenant_id] >= 1:
self.tokens[tenant_id] -= 1
return True
return False # 呼び出し側は HTTP 429 と Retry-After を返す
limiter = TenantTokenBucket(rate=5, burst=10)
heavy = sum(limiter.allow("tenant-a") for _ in range(100))
light = sum(limiter.allow("tenant-b") for _ in range(3))
print(f"tenant-a: {heavy}/100 通過, tenant-b: {light}/3 通過")
# 実行結果: tenant-a: 10/100 通過, tenant-b: 3/3 通過
tenant-aが100件を連打しても通るのは10件だけで、tenant-bの3件はすべて通ります。この例は単一スレッド向けです。本番で複数のAPIサーバーに分散させる場合は、Redisなどの共有ストア上で補充・判定・消費を原子的に実行します。同一プロセス内でも並行呼び出しには同期が必要で、未使用テナントの状態には有効期限を設けます。方式ごとの違いと分散実装はAPIレート制限(レートリミット)とは?で比較しています。制限値はテナントに公開し、429を受けたときの再試行方法も文書化しておきます。
データベースのクエリ実行時間と件数の上限
レート制限は要求の回数しか抑えません。1回で数十秒かかる集計クエリは、回数が少なくても共有データベースのCPUとI/Oを占有します。Azureのガイドは、返す最大レコード数やクエリの時間制限で、テナントが資源を大量に使うクエリを実行できないようにする方法を挙げています。PostgreSQLでは statement_timeout で1文あたりの実行時間に上限を付けられます。次はPostgreSQL 18.6で実行した結果です。
-- バッチ用の接続ロールにだけ上限を付ける(次の接続から有効)
CREATE ROLE batch_worker LOGIN;
ALTER ROLE batch_worker SET statement_timeout = '2s';
-- batch_worker で接続して実行
SELECT pg_sleep(3);
-- ERROR: canceling statement due to statement timeout
-- 1トランザクションだけ短くする(COMMIT・ROLLBACKで元の値に戻る)
BEGIN;
SET LOCAL statement_timeout = '1s';
SELECT pg_sleep(1.5);
-- ERROR: canceling statement due to statement timeout
ROLLBACK;
アプリケーションが全テナントで同じ接続ロールを使う構成では、ロール単位の設定ではテナントを区別できません。要求ごとにトランザクションを張り、料金プランに応じた値を SET LOCAL で入れます。件数のほうは、APIの1ページあたりの最大件数を固定し、全件出力のような重い操作は非同期ジョブに回します。
キューの公平化:SQS fair queuesとTemporal Fairness
非同期ジョブは、キューを先着順にしている限り1テナントの大量投入で他テナントが待たされます。Amazon SQSの標準キューは、送信時に MessageGroupId へテナントIDを入れるだけで公平キュー(fair queues)が自動で働き、コンシューマー側のコード変更は要りません。SQSは、1テナントがキュー全体の処理中メッセージの10%超を占め、かつそのテナント自身の処理中メッセージが30件以上ある場合、または直近のコンシューマー総処理時間の10%超を占める場合に、そのテナントを「ノイジー」と判定します。これらの閾値は近似値であり、境界値ちょうどで判定が切り替わるとは限りません。ノイジーと判定されたメッセージは捨てられず、静かなテナントのメッセージが優先的に配信されます。CloudWatchの ApproximateNumberOfNoisyGroups で判定数を監視できます。
ワークフローエンジンのTemporalにも、タスクキュー内でfairness key(テナント名など)ごとに仮想キューを作り、ラウンドロビンで配信するFairness機能があり、2026年5月5日に一般提供(GA)になりました。同じ優先度階層で各キーに待機タスクがある場合、fairness weightを2.0にしたキーには、1.0のキーの2倍の割合でタスクが配信されます。処理時間やCPU使用量が2倍になる保証ではありません。
シャッフルシャーディングとテナントの配置換え
ワーカーを固定のグループに分ける通常のシャーディングでは、1グループが落ちると、そこに属する全テナントが巻き込まれます。AWSのシャッフルシャーディングは、テナントごとにワーカーの組み合わせをずらして割り当てる方式です。Amazon Builders’ Libraryの例では、8台のワーカーを2台ずつ組むと28通りの組み合わせができ、組み合わせにテナントを均等に割り当て、片方のワーカーが失敗してももう片方へ再試行できる場合、両方のワーカーを失って処理を継続できなくなるテナントの割合は約1/28になります。4グループに分ける通常のシャーディング(1/4)より7倍狭い計算です。Route 53は、2048台の仮想ネームサーバーから4台ずつ組むことで、7,300億通りの組み合わせを作っています。
環境を複数に分けて運用しているなら、テナントの置き場所も対策になります。Azureのガイドは、使用パターンが補い合うテナント同士を同じ環境(スタンプ)に置いてピークをならすことと、急ぎでないバックグラウンド処理をピーク時間外に回すことを挙げています。もう1つの定番は、テナントを料金プランで階層化し、大口・上位プランだけを専用環境(サイロ)に置き、他は共有環境(プール)に集約する方法です。データベース側でテナントの行を分けるには、RLS(Row Level Security)を併用します。
対策を選ぶ順番と、やりすぎの失敗パターン
提供する側の対策は、計測、ガバナンス(クォータ・レート制限・クエリ上限・公平キュー)、分離の順に進めます。テナント別の計測がないまま分離に進むと、どのテナントのどの資源を専用化すべきかを決められません。専用環境の追加は費用と運用負担を増やすため、計測結果と性能要件を基に採否を判断します。
避けたい失敗は3つあります。
- CPU limitを一律に低く設定する:隣の暴走は防げても、ノードが空いている時間にまで自分のPodがスロットリングされ、p99レイテンシーが悪化します。
Guaranteedの採用は退避されにくさとCPU上限による遅延を比較して判断し、limitの値はnr_throttledを見ながら決めます。 - 全テナントをサイロ化する:共有による費用効率というマルチテナントの利点がなくなります。専用環境は、性能SLAや顧客ごとの分離要件があり、追加費用を許容できるテナントを対象にします。
- 制限値をテナントに知らせない:Azureのガイドも、スロットリングやクォータを透明に説明するよう求めています。黙って429を返すと、テナント側の無制限リトライで負荷がかえって増えます。
1テナントの負荷が共有データベース全体の大半を占めるなら、共有を続ける利点は小さいので、そのテナントだけを専用環境へ移すのが妥当です。
ノイジーネイバー問題に関するよくある質問
ノイジーネイバーは日本語で何と言いますか?
直訳すると「うるさい隣人」で、「うるさい隣人問題」と呼ばれることもあります。IT分野では、共有リソース上の他の利用者の負荷で自分の性能が落ちる現象を指します。
ノイジーネイバー問題は完全に防げますか?
資源を共有する限り、完全には防げません。Azure Architecture Centerも、共有には「完全に回避できない」リスクが伴うとしています。現実的な目標は、クォータやレート制限で発生確率を下げ、シャッフルシャーディングなどで影響を受けるテナントの範囲を狭めることです。
AWSではどんな対策がありますか?
インフラ層ではDedicated Instances・Dedicated Hostsによる物理ホストの専有、アプリケーション層ではSQS fair queues(標準キューで MessageGroupId を付けると自動で有効)があります。T系インスタンスの遅延はクレジット枯渇の可能性があるため、CPUCreditBalance を先に確認してください。
Kubernetesでノイジーネイバーを防ぐには?
重要なPodは、実負荷とCPUスロットリングを確認したうえでrequestsとlimitsを同じ値にして Guaranteed にします。Namespaceごとに ResourceQuota で合計を、LimitRange で既定値を設定すれば、指定漏れのPodが BestEffort になるのも防げます。
ノイジーネイバーはセキュリティの問題でもありますか?
多くの場合、原因のテナントに悪意はなく、自分が他に迷惑をかけていることに気づいていません。ただしAzureのガイドは、共有コンポーネントの弱点を突いて意図的に他テナントを妨害する(サービス拒否攻撃)テナントもありうるとしています。原因を問わず、リソースガバナンスの問題としてクォータ・スロットリングで抑えるのが基本です。