バックプレッシャーとは?流量制御の仕組みと実装方式の選び分けを実装視点で解説
取り込みは順調に見えるのに、数時間後にメモリが尽きてプロセスが落ちる。ストリーム処理やジョブ基盤でこの手の障害を踏んだとき、原因はたいてい流量制御の不在にあります。本記事では、バックプレッシャーの定義から、溜める・捨てる・止める・抑えるという4つの逃がし方の違い、Reactive Streamsのrequest(n)が担う需要の伝達、Reactor・RxJava・Node.js・Kafka・Flinkそれぞれの実装機構、そして滞留量の上限と破棄方針を業務要件から逆算する手順までを実装者の目線で整理しました。背圧では解けず台数の増設が要る場面の線引きも示します。
まとめ|バックプレッシャーの本質と4つの逃がし方の選び分け
バックプレッシャーとは、下流の処理能力を超えてデータが流れ込むときに、その事実を上流へ伝えて送出量を抑える仕組みを指します。語源は配管の背圧で、出口が詰まれば手前に圧力が戻るという物理現象がそのまま比喩になっています。要点は「速くする技術ではない」ことです。処理能力そのものは増えません。増えないまま、壊れる代わりに遅くなるという振る舞いを選び取るのが背圧の役割になります。
詰まりへの対処は4つしかありません。溜める(バッファ)、捨てる(ドロップ)、止める(ブロック)、抑える(生産レートの低下)。このうち上流の速度を変えるのは後半の2つで、前半の2つは時間を稼ぐか諦めるかの手当てにすぎない。実装の議論が噛み合わないときは、たいてい4つのどれを話しているかがずれています。
設計順序も決まっています。先に決めるのは バッファサイズではなく、欠落を許すかどうかです。許さないなら止める系、許すなら捨てる系を選び、そのうえで滞留の上限を「何件」ではなく「何秒分」で置く。この順で決めておけば、稼働後にメモリ設定を触りながら原因を探す時間はほぼ消えます。
バックプレッシャーの定義と処理が追いつかない状態で起きる崩れ方
まず、何が起きているのかを速度の関係で押さえます。ストリーム処理の全体像はストリーム処理の仕組みとバッチ処理との使い分けで整理しているため、本記事は流量の制御に絞ります。
生産速度が消費速度を上回ったときに遅延とメモリが崩れていく連鎖
生産者が毎秒1万件を送り、消費者が毎秒8千件しか捌けないとします。差分は毎秒2千件。この2千件はどこかに存在し続けるしかありません。行き先はメモリ上のキュー、ディスク、あるいはネットワークの受信バッファです。1分で12万件、1時間で720万件が積み上がり、1件1KBならおよそ7GB。処理能力の差がわずか2割でも、時間が線形に効いて破綻します。
崩れ方には順序があります。最初に現れるのは遅延の伸びで、キューに並ぶ時間がそのまま応答時間へ乗ります。次にメモリ使用量が伸び、ヒープが逼迫するとGCの実行時間が伸びて処理速度がさらに落ちる。この時点で差分が広がるため、悪化は加速していきます。最後がプロセスの停止か、コンテナのメモリ上限による強制終了。「昼は問題なく、夜間バッチと重なった日だけ落ちる」という報告の多くは、この連鎖の途中経過を見ています。
溜める・捨てる・止める・抑えるという4つの逃がし方の使い分け
差分を吸収する手段は4種類に整理できます。溜めるは有限のバッファで一時的な波を吸収する手当てで、恒常的な超過には効きません。捨てるは受け取ったデータの一部を破棄する判断で、監視値や現在地のように最新値だけ意味がある系では正解になります。
残る2つが背圧の本体です。止めるは、受け取り側が空くまで送信側の処理を待たせる方式で、同一プロセス内なら有限キューへのブロッキングな追加、ネットワーク越しなら読み取りの停止として現れます。抑えるは、下流が受け取れる量を上流へ数値で伝え、送出そのものを需要に合わせる方式。前者は詰まりを待ち時間へ変換し、後者は詰まりを起こす前に生産量を下げる違いがあり、後者のほうが遅延の跳ね方が穏やかになります。
需要を要求するプル型とバッファ頼みのプッシュ型で変わる効き方
背圧が効くかどうかは、経路が需要を伝える構造を持つかで決まります。ここを取り違えると、どれだけ設定を触っても詰まりが上流へ伝わりません。
下流が処理できる件数だけを要求するプル型の需要ベース流量制御
プル型では、下流が「いま何件受け取れるか」を上流へ通知し、上流はその範囲内でしか送りません。Reactive Streamsの仕様がこの形を規定していて、購読者が Subscription.request(n) で需要を宣言し、発行者は要求された件数を超えて onNext を呼んではならない、という一点が仕様の核になっています。仕様と適合性テストは 1.0.4 が最新版で、同じ4インターフェース(Publisher・Subscriber・Subscription・Processor)が java.util.concurrent.Flow としてJDK標準へ取り込まれました。
この構造の利点は、バッファに頼らず速度が同期する点にあります。下流が遅ければ要求が減り、上流の生成そのものが止まる。データベースのカーソルを1件ずつ読み進める処理や、ページング付きAPIの逐次取得も同じ原理で、背圧を「実装する」のではなく最初から持っている経路です。非同期処理の方式全般は非同期処理と同期処理の違いと実装方式の解説で扱っています。
プッシュ型でバッファだけが緩衝材になる構造と溢れたときの挙動
一方、送信側が自分の都合で送り続ける経路には需要の逆流路がありません。WebSocketのサーバープッシュ、UDPのテレメトリ、コールバックで届くセンサーイベントが典型です。ここで受け手にできるのは、溜めるか捨てるかの2択だけになります。
問題は既定値です。無指定のキューは多くのライブラリで実質無制限として振る舞い、上限に達する前にヒープが尽きます。プッシュ型を受けるときは、キューの上限と溢れた場合の動作を必ず明示してください。捨てるなら古い方か新しい方か、止めるなら誰を待たせるか。この2つを書かないまま本番へ出した経路は、負荷が読みを超えた日に必ず落ちます。
TCPの受信窓とHTTP/2のWINDOW_UPDATEが担う下位層の流量制御
背圧はアプリ層だけの話ではありません。TCPは受信ウィンドウで空き容量を相手へ広告し、埋まれば送信が止まります。HTTP/2はさらにストリーム単位とコネクション単位のフロー制御ウィンドウを持ち、初期値は 65,535 オクテット、受け手が処理を終えるたびに WINDOW_UPDATE フレームで枠を戻す設計です。gRPCのストリーミングはこの仕組みの上に乗っています。
ここに落とし穴があります。下位層の背圧は「受信バッファから読み出していない」間しか効きません。アプリがソケットから素早く読み出してアプリ側のキューへ積み替えてしまえば、受信窓は空き続け、送信側から見れば詰まりは存在しない。つまり読み出しを止められる構造をアプリ層に用意しない限り、下位層の流量制御は無効化されます。
実装基盤ごとのバックプレッシャー機構と設定値の勘所を押さえる
代表的な基盤が何を提供しているかを押さえておくと、設計時に「どこで止めるか」を選べるようになります。バージョンは2026年8月時点の実測値です。
ReactorとRxJavaのonBackpressure系演算子の選び分けの基準
JVM系のリアクティブライブラリは、需要を超えた分の扱いを演算子として選ばせます。Project Reactorは 3.7系がGA(3.8系はマイルストーン公開の段階)、RxJavaは 3.1系が現行です。RxJavaには設計上の分岐があり、背圧に対応するのは Flowable のみで Observable は対応しません。UIイベントのように件数が高々知れている系はObservable、際限なく流れうる入力はFlowable、という選び分けが前提になります。
| 演算子 | 溢れた分の扱い | 向く要件 | 副作用 |
|---|---|---|---|
| onBackpressureBuffer | 上限まで保持 | 短時間のバースト吸収 | 遅延とメモリが伸びる |
| onBackpressureDrop | 破棄する | 監視値や位置情報 | 欠落が起きる |
| onBackpressureLatest | 最新1件を残す | 現在値の表示 | 途中の変化が消える |
| onBackpressureError | 例外で打ち切り | 欠落を許さない経路 | 再購読の設計が要る |
選択の基準は、そのデータが「状態」か「事実」かで割り切れます。現在の温度や在庫数のような状態は最新値だけ残せば足り、Latestが噛み合う。注文や決済のような事実は1件も落とせないため、BufferかErrorしか選べません。Web層でこの仕組みを使う構成はSpring WebFluxのノンブロッキング開発の基礎で扱っています。Scala/Java圏ではApache Pekko Streamsも同種の機構を持ち、1.6系がGAとして公開されています。
Node.jsのwrite()がfalseを返す合図とdrainイベントの待ち方
Node.jsのストリームは、書き込み側の戻り値で背圧を通知します。writable.write() は内部バッファが highWaterMark 未満なら true を返し、false を返した場合は drain イベントが来るまで書き込みを止めるべきだと公式ドキュメント(v26.7.0時点)が明記しています。この戻り値を無視して write() を呼び続けるコードが、Nodeでメモリが伸び続ける最頻の原因です。
手当ては単純で、自前でループを書かず stream.pipeline() に任せる。パイプラインは戻り値と drain の待ち合わせ、エラー時の破棄までまとめて面倒を見ます。自前で書くのは、変換の途中で外部APIを呼ぶような非定型の処理に限ってください。
Kafkaのポーリング制御とFlinkのクレジット方式で止める仕組み
Kafkaのコンシューマはプル型です。poll() が一度に返す件数は max.poll.records(既定500)で制限され、処理が終わるまで次の取得は起きません。ただし猶予には上限があり、poll間隔が max.poll.interval.ms(既定300000ミリ秒)を超えるとグループから外され、リバランスが走ります。重い処理を挟むなら件数を減らすか、pause() で該当パーティションの取得だけを止め、処理後に resume() する形が定石です。送信側も、バッファが満杯になると送信呼び出しが待たされる形で背圧を受け取ります。
Apache Flink(2.x系)はタスク間でクレジットベースの流量制御を使います。下流が受け入れ可能なバッファ数をクレジットとして上流へ通知し、上流はその範囲でしか送りません。詰まりはネットワークバッファを介して段階的にソースまで伝わり、Kafkaソースなら読み取り速度が落ちてコンシューマラグが伸びる形で外から観測できます。各タスクの backPressuredTimeMsPerSecond を見れば、どの演算子が起点かを特定できる作りです。
バッファ上限と破棄方針を業務要件から逆算して決める設計の手順
機構が分かっても、設定値を決められなければ実装は進みません。ここは要件側からしか決まらない領域なので、順序を固定しておきます。
滞留量の上限と破棄方針と待ち時間を要件から決める3つの決め順
決めるのは次の3つだけです。順番を守ってください。
- 欠落を許すか決める(許すなら捨てる系、許さないなら止める系に確定する)
- 滞留の上限を時間で置く(「1万件」ではなく「消費速度の3秒分」と表現する)
- 上限に達したときの動作を1つ選ぶ(古い方を捨てる/新しい方を捨てる/送信側を待たせる/打ち切る)
2番目が実務上の急所になります。件数で上限を置くと、消費速度が変わった瞬間に意味が変わってしまう。毎秒8千件を捌く経路の「1万件」は1.25秒分ですが、処理が重くなって毎秒500件に落ちれば同じ1万件が20秒分の遅延になります。時間で置いておけば、許容遅延の要件とそのまま突き合わせられる。「応答遅延の上限が2秒なら、滞留は1秒分まで」という形で決めれば、値の根拠を後から説明できます。
バイト単位の上限も併記してください。件数や秒数だけでは、想定外に大きいレコードが流れたときにメモリを守れません。件数・時間・バイト数の3つで上限を張り、どれかに触れたら発火させるのが安全側の構成です。
滞留量とラグと待機時間を監視して発火条件を決める運用側の設計
背圧は設定して終わりではなく、効いているかを観測し続ける対象です。見る指標は4つに絞れます。キューの滞留量、入力側のラグ(Kafkaならコンシューマラグ)、1件あたりの滞留時間、そして背圧で待たされている時間の割合。前2つは量、後ろ2つは時間の指標で、両方を並べないと原因の切り分けができません。
アラートの発火条件は滞留時間に置きます。滞留量は処理速度によって意味が変わるのに対し、滞留時間は業務要件の遅延許容と直接比較できるためです。許容遅延の半分を超えたら警告、超過で重大として、同時に背圧時間の割合を添えておく。この2つが同時に伸びていれば下流のスケール不足、滞留だけが伸びていれば入力側の急増と、原因の当たりが即座に付きます。
バックプレッシャーで解く問題と台数の増設で解くべき問題の線引き
最後に、背圧を入れるべきでない場面を言い切ります。背圧は万能の対処ではなく、適用を誤ると詰まりを利用者側へ押し出すだけの結果になります。
バックプレッシャーが効かない場面と台数の増設で解くべき問題の切り分け
言い切ります。生産量が消費能力を恒常的に上回っている系に背圧を入れても、問題は解決しません。背圧が買えるのは時間だけで、平均で足りていない差分は永久に埋まらないからです。この場合に要るのは消費側の並列度を上げること、つまりコンシューマの増設とパーティションの追加、あるいは処理そのものの見直しになります。
切り分けは単純な計測で付きます。1時間の平均処理速度と平均流入量を比べ、流入が上回っていれば増設案件、下回っているのにピークで詰まるならバースト吸収の設計案件です。背圧が効くのはこの後者、つまり平均では足りているが瞬間的に溢れる系と、下流障害時に壊れず劣化させたい系に限られます。判断を誤ると、背圧の設定を延々と調整しながら根本原因に触れない時間が積み上がる。こうした基盤の設計から構築までを外部と進める選択肢もあり、当社のデータ分析基盤構築・MLOps構築支援でも立ち上げを支援しています。
上流へ詰まりを伝えると逆効果になる場面と代わりに置く逃がし先
詰まりを上流へ伝えた先に人がいる場合、背圧はそのまま利用者の待ち時間へ変わります。同期のHTTPリクエストを受けている経路で下流の遅さを素直に伝播させると、接続が滞留してWebサーバー側のスレッドやコネクションが尽き、無関係な画面まで巻き添えで落ちる。ここでは待たせるのではなく、上限を超えた要求に 429 を返して即座に断るか、いったんキューへ書いて非同期の応答へ切り替える判断が要ります。
もう1つが共有経路のヘッドオブラインブロッキングです。1つの遅い宛先に引きずられて同じ経路の全処理が止まる構成では、背圧が障害の伝染路として働きます。宛先ごとに経路とバッファを分けるか、遅い宛先を切り離す仕切りを設けてください。背圧は「壊れずに遅くする」道具なので、遅くなってはいけない相手には向けないという原則で運用します。
実装で頻出する3つの失敗パターンと稼働前に潰しておく確認項目
現場で繰り返し見る失敗は3つです。第一に、上限を書かないキューを挟んでしまい、背圧を入れたつもりでメモリが伸び続けるパターン。第二に、RxJavaで Observable を選び、需要の伝達が存在しない経路を作ってしまうパターン。第三が、Kafkaで重い処理を挟みながら pause() を使わず、max.poll.interval.ms を超えてリバランスが連鎖するパターンで、処理がやり直され続けて外形上は「詰まったまま進まない」挙動になります。
稼働前の確認項目は4点で足ります。すべてのキューに件数・時間・バイト数の上限があるか、上限到達時の動作が明示されているか、滞留時間と背圧時間の指標が取れているか、平均流入が平均処理能力を下回っているか。最後の1点だけは設計ではなく実測で確かめてください。ここが崩れていれば、他の3点をどれだけ整えても稼働後に詰まります。
よくある質問
実装の判断で迷いやすい論点をまとめます。用語の混同と設定値の決め方に絞って答えます。
バックプレッシャーとレートリミットは何が違いますか?
制御の起点が違います。レートリミットは「毎秒1000件まで」のように事前に決めた上限で切る静的な制御で、相手の実際の空き具合は見ていません。バックプレッシャーは受け手の残り容量から動的に送出量を決める仕組みです。外部からの流入を守るのはレートリミット、内部の速度差を吸収するのがバックプレッシャーという分担になり、両方を併用する構成が実務では普通です。
バッファを大きくすればバックプレッシャーは不要になりますか?
なりません。バッファが買えるのは時間だけで、平均の処理能力が不足していれば大きくした分だけ遅延が伸び、最後は同じように溢れます。しかも滞留が長いほど、障害時に失われる未処理データの量も増える。バッファは「何件」ではなく「消費速度の何秒分か」で見て、許容遅延に収まる範囲に抑えるのが扱いやすい形です。
バックプレッシャーが効いているかはどう確認できますか?
時間の指標を見ます。Flinkなら各タスクの背圧時間の割合、Kafkaならコンシューマラグの傾き、アプリ内キューなら1件あたりの滞留時間です。Node.jsは write() が false を返した回数を数えるだけでも傾向が掴めます。量の指標だけを見ていると、処理速度が落ちたのか流入が増えたのかを区別できません。
リアクティブライブラリを使わないと実装できませんか?
不要です。有限長のブロッキングキューを1つ挟むだけで、追加側が待たされる形の背圧が成立します。セマフォで同時実行数を制限する、Kafkaなら pause と resume で取得を止める、HTTPならソケットからの読み出しを遅らせる。いずれも同じ効果です。リアクティブライブラリの利点は、この制御を経路全体へ宣言的に伝播させられる点にあり、単一の境界を守るだけなら標準機能で足ります。
上流を止めると全体が遅くなりませんか?
遅くなります。それが意図した振る舞いです。止めなければ差分はメモリに積み上がり、最終的にプロセスが落ちて未処理データごと失われる。背圧は「遅くなること」と「壊れること」のどちらを選ぶかという設計判断で、遅くなってはいけない相手が上流にいる場合だけ、破棄や非同期化といった別の逃がし方へ切り替えます。
関連記事
- ストリームウィンドウ処理とは?4方式の使い分けと遅延データの締め切り設計を実装視点で解説:流量を抑えた先で、区間ごとの集計をいつ確定させるかという設計です。
- Apache Flinkとは何か?リアルタイム処理エンジンの特徴・利点とユースケースの概要を詳しく解説:本記事で触れたクレジットベースの流量制御を備える処理エンジンの個別解説です。
- Apache Kafkaが選ばれる理由とは?特徴とメリットを徹底解説:プル型の取得と再生可能なログという、背圧を成立させる前提を担うメッセージ基盤です。