L7ロードバランサの内部とアルゴリズム
どのサーバーに振るかで尾の遅延が決まる——round-robin から power-of-two-choices まで、負荷分散アルゴリズムの長所と落とし穴、ヘルスチェックとコネクションドレインの原理を押さえて偏りなく捌けます。
- 1.round-robin は実装が単純だが処理時間の差を無視するため遅いサーバーに負荷が溜まる。least-connection は実行中接続数の最小を選び、リクエスト長が不均一な L7 で偏りに強い。
- 2.EWMA は各サーバーの応答遅延を指数移動平均で追い、遅いバックエンドを動的に避ける。power-of-two-choices は全候補を見ずランダム2台の負荷を比較するだけで、最悪偏りを劇的に下げる。
- 3.ヘルスチェックはアクティブ(定期プローブ)とパッシブ(実トラフィックの失敗観測)の両輪。コネクションドレインは取り外すサーバーへ新規を止めつつ既存接続を完了まで待ち、無停止で入れ替える。
L7ロードバランサは「どこへ振るか」を毎リクエスト決める
L4 ロードバランサが TCP/UDP のコネクション単位で振り分けるのに対し、L7(アプリケーション層)ロードバランサ は HTTP リクエストを終端し、ヘッダやパスを見たうえで リクエストごと にバックエンドを選びます。HTTP/2 や gRPC では1本の TCP コネクションに多数のリクエストが多重化されるため、コネクション単位の L4 分散ではストリームが特定サーバーに固定されて偏ります。L7 はリクエスト単位で配れるので、この多重化に正しく対応できます。
問題は どのバックエンドを選ぶか です。選択アルゴリズムの良し悪しが、平均ではなく 尾の遅延(テールレイテンシ) を決めます。バックエンドの処理時間はリクエストごとにばらつき、たまたま重いリクエストを抱えたサーバーに次々と振ると、そのサーバーのキューだけが伸びて p99 が跳ねます。待ち行列の原理(/devops/queueing-theory-tail-latency/)どおり、稼働率が飽和に近づくと待ち時間は急激に発散するからです。
静的アルゴリズム:round-robin と重み付け
最も素朴なのが round-robin です。バックエンドを順番に1台ずつ巡回して割り当てます。状態をほとんど持たず実装が単純で、各サーバーの能力が等しくリクエストの処理時間も均一なら、これで十分均等に配れます。
弱点は 処理時間の差をまったく見ない ことです。あるリクエストが他の100倍重くても round-robin は知らずに次を回し、重い処理を抱えたサーバーにさらに新規を積みます。サーバーの性能が不揃いな場合は 加重ラウンドロビン(weighted round-robin) で能力比に応じた重みを与えますが、これも「いま忙しいか」という動的な状態は無視します。
round-robin は到着順に均等配分するだけで、各サーバーが現に何本の接続を抱えているかを見ません。リクエスト長が不均一だと、配った数は同じでも実際の負荷は偏ります。クライアントが長命なコネクションを張る L7 では、配分カウントの均等と負荷の均等は一致しないのが原則です。
ハッシュ系(送信元 IP や URL のハッシュでサーバーを決める)は、同じキーを常に同じサーバーへ送れるので セッションアフィニティ やキャッシュ局所性に向きますが、これも負荷の動的状態は見ない静的方式です。
least-connection:実行中の負荷を見る
least-connection(最小接続数) は、いま 実行中(in-flight)の接続が最も少ないサーバー を選びます。重いリクエストを抱えたサーバーは接続が長く残るので接続数が高止まりし、自然と新規が回ってこなくなる。配った数ではなく 現在の負荷 に応じて配るため、リクエスト長が不均一な L7 で round-robin より偏りに強いのが利点です。
ただし注意点があります。接続数は 負荷の代理指標 にすぎません。1接続あたりの仕事量がサーバーごとに違えば、接続数が同じでも実 CPU 負荷は違います。能力差があるなら weighted least-connection(接続数を重みで割った値を比較)を使います。また、新しく追加したばかりの空きサーバーは接続数 0 なので、追加直後に新規が一気に殺到する(thundering herd 的な偏り)点にも配慮が要ります。
EWMA:遅延そのものを動的に追う
接続数は「忙しさ」の間接指標です。より直接に 遅さ を見るのが EWMA(指数加重移動平均, Exponentially Weighted Moving Average) です。各バックエンドの応答遅延を、新しい観測ほど重く、古い観測ほど指数的に軽く平均して追跡します。
ewma_new = α * latency_sample + (1 - α) * ewma_old
(α は 0〜1 の平滑化係数。α が大きいほど直近の変化に俊敏、
小さいほど滑らかで雑音に鈍感)
実装では固定の α ではなく、観測間隔と時定数から減衰を計算する方式(時間ベースの EWMA)が一般的です。長く観測がないサーバーは推定が古くなるため、最後の観測からの経過時間で重みを減衰させ、徐々に「不明=平均的」へ戻します。Finagle や Linkerd の P2C+EWMA 実装はこの考え方を使い、遅くなり始めたバックエンドを接続数の変化より早く検知して避けられる のが強みです。GC ストールや一時的な遅延スパイクに敏感に反応できます。
単純な全期間平均は古い好調時のデータを引きずり、いま遅くなった事実を薄めてしまいます。直近のウィンドウだけ見る単純移動平均は、ウィンドウ分のサンプルを保持するメモリと、窓の端で値が飛ぶ問題を抱えます。EWMA は1つの状態値(前回の平均)だけで「最近を重視し古いものを忘れる」を実現でき、状態が軽く滑らかなので分散ルーティングに向きます。
power-of-two-choices:2台だけ見て偏りを潰す
least-connection や EWMA を「全バックエンドを毎回見て最小を選ぶ」素直な実装で動かすと、サーバー台数が増えるほど選択コストが上がり、さらに 分散した複数のロードバランサが同じ「最も空いているサーバー」へ一斉に殺到する 群れ問題が起きます。全員が同じ最小値を見て同じ1台に振るからです。
これを解くのが power-of-two-choices(2択のべき乗, P2C) です。原理は単純で、全候補からランダムに2台だけ選び、その2台のうち負荷が小さい方へ振る だけです。
P2C(least-connection と組む例)
1. 全サーバーから一様ランダムに2台 a, b を選ぶ
2. a と b の実行中接続数を比べる
3. 接続数が少ない方へリクエストを振る
直感に反して、たった2台の比較が劇的に効きます。完全ランダム(1台だけランダムに選ぶ)だと、最も混むサーバーの待ち行列長は台数 n に対しておおむね log n / log log n のオーダーで伸びます。ところが2台見て小さい方を選ぶと、最大待ち行列長は log log n / log 2 程度まで一気に縮みます。「もう1台見る」だけで指数的に偏りが改善する——これが power-of-two-choices の核心です。
実務では「ランダム2台を P2C で選び、その2台の優劣を EWMA 遅延(や接続数)で判定する」構成が広く使われます(Finagle / Linkerd など)。全台を走査しないので大規模でも選択が軽く、群れ問題も起きにくく、かつ遅いバックエンドを動的に避けられます。サービスメッシュ(/devops/service-mesh/)のサイドカーが各クライアント側でこの分散を行うと、中央集権の単一ボトルネックなしに賢い分散ができます。
| アルゴリズム | 見る指標 | 状態の重さ | 偏りへの強さ | 向く場面 |
|---|---|---|---|---|
| round-robin | 順番(カウンタのみ) | ほぼ無状態 | 弱い(処理時間差を無視) | 処理が均一・等性能のバックエンド |
| least-connection | 実行中接続数 | 接続数の追跡 | 中(負荷の代理指標) | リクエスト長が不均一な L7 |
| EWMA | 応答遅延の指数移動平均 | サーバーごと1値 | 強い(遅さを直接検知) | 遅延スパイク・GCストールを避けたい |
| power-of-two-choices | ランダム2台の負荷比較 | 選択時のみ参照 | 非常に強い(群れ問題に堅牢) | 大規模・分散LB・メッシュ |
ヘルスチェック:壊れた相手を候補から外す
どんな分散アルゴリズムも、死んだサーバーを候補に残したまま では失敗を量産します。ヘルスチェックは候補プールから不健全なバックエンドを除外する仕組みで、2方式を併用します。
- アクティブヘルスチェック:LB が定期的にプローブ(
/healthzへの HTTP GET、TCP 接続確認など)を送り、応答で健全性を判定します。連続成功で復帰、連続失敗で除外する ヒステリシス(戻り判定と落とし判定でしきい値を変える)を入れ、1回の揺らぎでの状態振動を防ぎます。 - パッシブヘルスチェック(アウトライア検出):実トラフィックの結果を観測し、エラー率やタイムアウトが急増したサーバーを一時的に外します。プローブと違い 実リクエストの異常を直接捉える のが利点で、サーキットブレーカ(/devops/circuit-breaker-bulkhead/)と同じ発想です。一定時間後に少量のトラフィックで復帰を試します。
浅いヘルスチェック(TCP が開くかだけ)はアプリの内部障害を見逃し、深いチェック(依存DBまで叩く)は 共有依存が落ちると全台が同時に不健全と判定され、全滅 します。プローブ先と判定ロジックは慎重に選ぶ必要があります。あわせて、健全な台が一定割合を切ったら除外をやめる panic mode(パニックしきい値) を持たせ、「全台外して0台になる」自殺的挙動を防ぎます。
コネクションドレイン:無停止で入れ替える
デプロイやスケールインでサーバーを取り外すとき、いきなり止めれば処理中のリクエストが切られます。コネクションドレイン(connection draining / graceful shutdown) は、取り外す台へ 新規リクエストの割り当てを止めつつ、既存の実行中接続は完了まで待つ 手順です。
ドレインの流れ
1. 対象サーバーを候補プールから外す(新規は他へ振られる)
2. 既存の in-flight リクエストは最後まで処理させる
3. すべて完了、またはドレインタイムアウト到達で接続を閉じる
4. プロセスを停止/インスタンスを撤去
ポイントは 順序 です。LB がドレイン開始を認識する前にプロセスを殺すと、その瞬間に振られた新規が失敗します。実務では「ヘルスチェックをわざと失敗させる→LB が新規を止めたのを確認→既存を捌き切る→終了」という段取りにし、ドレインタイムアウトの上限も設けます。これにより、デプロイ戦略(/devops/deployment-strategies/)のローリング更新やオートスケールの縮小を、ユーザーに気づかれず実施できます。
「ラウンドロビンで分散している」だけでは L7 として不十分です。問われるのは——(1) リクエスト長が不均一なら least-connection / EWMA / P2C で動的負荷を見る、(2) アクティブとパッシブのヘルスチェックを併用しヒステリシスとパニックしきい値を持つ、(3) 取り外し時はコネクションドレインで in-flight を保護する。配分カウントの均等とテールレイテンシの最小化は別問題、という理解が上級者の作法です。
まとめ
- L7 LB はリクエストごとにバックエンドを選び、その選択が平均よりテールレイテンシを左右する。round-robin は無状態で単純だが処理時間差を無視し、負荷が偏る。
- least-connection は実行中接続数で動的負荷を見て偏りに強く、EWMA は応答遅延を指数移動平均で追って遅いサーバーを早期に避ける。
- power-of-two-choices はランダム2台を比べるだけで最大待ち行列長を
log log nオーダーまで縮め、大規模・分散環境の群れ問題に強い。EWMA と組むのが定石。 - ヘルスチェック(アクティブ+パッシブ)で不健全な台を外し、コネクションドレイン で取り外し時の in-flight を保護することで、賢い分散と無停止運用を両立する。
DevOps/インフラ Article
L7ロードバランサの内部とアルゴリズムを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
ロードバランサ
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6
導入後に効く点
EWMA は各サーバーの応答遅延を指数移動平均で追い、遅いバックエンドを動的に避ける。power-of-two-choices は全候補を見ずランダム2台の負荷を比較するだけで、最悪偏りを劇的に下げる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「ロードバランサ / 負荷分散」に近いか確認する。
- 強みである「round-robin は実装が単純だが処理時間の差を無視するため遅いサーバーに負荷が溜まる。least-connection は実行中接続数の最小を選び、リクエスト長が不均一な L7 で偏りに強い。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。