APIを守る防波堤:トークンバケットとリーキーバケットの深淵
APIの可用性を語る際、往々にして「スケーラビリティ」という甘美な響きに目が向きがちだ。しかし、真のインフラアーキテクトであれば知っているはずだ。システムが崩壊するのは、トラフィックが処理能力を超えた瞬間ではなく、その「想定外のバースト」が制御不能な連鎖反応を引き起こした時であることに。
今日は、APIトラフィック制御の要である「トークンバケット」と「リーキーバケット」について、単なるアルゴリズムの解説を超え、パケットレベルの挙動とカーネルの制約という視点から深掘りしていく。
—
1. トークンバケット vs リーキーバケット:挙動の解像度を上げる
どちらも「溜めて、放出する」という点は同じだが、パケットがその防波堤に到達した時の挙動は対照的だ。
トークンバケット(Token Bucket):バーストを許容する柔軟性
トークンバケットは、一定の間隔でバケツに「トークン」を補充する。リクエストはこのトークンを消費することで通過できる。重要なのは、トークンがバケツの容量(Burst Size)まで溜まることだ。これにより、平時は平滑化しつつ、突発的なバーストトラフィックを即座に処理できる。
- 適しているケース: ユーザー体験を損なわない高速な応答が求められるAPI。
- 懸念:
Burst Sizeの設定を誤ると、バックエンドのデータベースやコネクションプールを一瞬で枯渇させる。
リーキーバケット(Leaky Bucket):厳格なる平滑化
一方、リーキーバケットは「一定の速度でバケツの底から穴が開いている」状態だ。パケットは一旦キューに格納され、一定の間隔で処理される。溢れたパケットは即座にドロップされる。
- 適しているケース: 処理速度が一定である必要があるキューイング処理や、DoS攻撃に対する強固な防御。
- 懸念: 常に一定のレイテンシが発生するため、リアルタイム性が求められるAPIには不向き。
—
2. パフォーマンスの極致:トランスポート層からの最適化
レートリミットを実装する際、アプリケーション層(L7)だけで考えてはならない。TCPの振る舞いまで視野に入れる必要がある。
例えば、TCP_NODELAYフラグがオフであれば、Nagleアルゴリズムによって小さなリクエストパケットがバッファリングされ、意図しないレイテンシが生じる。また、TLSハンドシェイクのRTT削減のために TLS False Start や TCP Fast Open を有効化する際、レートリミットがハンドシェイクの直後に配置されているかを確認してほしい。
もしレートリミットの判定に時間がかかれば、それだけでTCPの Initial Congestion Window (initcwnd) を有効活用できず、スループットが劇的に低下する。
—
3. 実装の指針:Nginxにおけるレートリミットチューニング
現場で最も汎用性が高いのは ngx_http_limit_req_module だ。ここでトークンバケットの挙動を制御する。
# レートリミット定義:10MBの共有メモリ領域を確保(約16万IP分の状態を保持)
# 毎秒10リクエストを許可、バーストは20まで許容
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
location /api/ {
# burst=20: バースト時に20リクエストまでキューイング
# nodelay: キューイングせずに即時処理(レイテンシを最小化)
limit_req zone=api_limit burst=20 nodelay;
# HTTP/2への最適化:ヘッダー圧縮 (HPACK) を有効化
http2_push_preload on;
# 接続のバッファリングを最小限に抑え、バックエンドへの即時転送を優先
proxy_buffering off;
proxy_pass http://backend_pool;
}
}
ここで重要なのは nodelay パラメータだ。これがない場合、Nginxはバーストしたリクエストを律儀に 10r/s の間隔まで遅延させてから処理する。これはリーキーバケットに近い挙動だ。UXを優先するならば nodelay を使い、その代わりバックエンド側で Connection Pool の上限を厳格に管理するバランス感覚が求められる。
—
4. 現場で直面する「見えない」脆弱性
レートリミットを回避しようとする攻撃者は、IPアドレスを分散させるだけでなく、HTTP/2 の Stream Multiplexing を悪用する。単一のTCP接続の中で数百のストリームを同時に開けば、接続ベースの制限は容易にすり抜けられる。
対策の肝:
1. IPベースに頼らない: JWT や API Key ごとの Request ID を基にしたレートリミットを併用する。
2. ヘッダー圧縮の悪用を防ぐ: HPACK の静的/動的テーブルサイズを制限し、巨大なヘッダーによる CPU 枯渇を防ぐ。
3. RTTの削減: BBR 混雑制御アルゴリズムをカーネルレベルで有効化し、ロス率の高い環境下でもスループットを維持する。
# Linuxカーネルパラメータの最適化(BBRの有効化)
sysctl -w net.core.default_qdisc=fq
sysctl -w net.ipv4.tcp_congestion_control=bbr
—
最後に:アーキテクトとしての哲学
レートリミットは単なる「防御」ではない。それは、あなたのAPIが「どのような品質でリクエストを捌くか」を定義する「設計書」そのものだ。
トークンバケットを選ぶのか、リーキーバケットを選ぶのか。その選択には、ビジネス上の要件と、パケットがネットワークを駆け抜けるその瞬間の挙動に対する深い洞察が必要だ。教科書を閉じて、tcpdump を取り出し、実際にパケットがどのようなタイミングでドロップされるかを観察してみてほしい。そこにこそ、真のチューニングの答えがある。
コメント