クラスター単位の限界と「真の処理レート」への転換
深夜2時、SlackのPagerDutyチャンネルがけたたましく鳴り響く。画面に映し出されるのは、Kafkaのコンシューマーグループにおけるラグの急激なスパイクと、それに伴うストリーミングジョブのデッドロック寸前の遅延だ。我々プラットフォームエンジニアがこれまで何度も目にしてきた、そして二度と見たくない光景である。Netflixのような超巨大規模の配信基盤において、この手の「ストリーミングの詰まり」は致命傷を意味する。同社は2017年からApache Flinkを導入し、2019年には独自のオートスケーラーを構築して運用してきた。この初期のシステムは、Mantis上で動作し、AtlasからCPUやネットワーク利用率、Kafkaのラグ、インプット/アウトプットレートといったクラスターレベルのテレメトリを収集してスケーリングを判断していた。確かに、これによって数千のパイプライン全体で25%から45%のインフラリソース削減に成功した。
しかし、このアプローチには致命的な設計上の限界があった。それは「スケーリングの最小単位がクラスター(TaskManager)全体である」という点だ。データパイプラインが複雑化し、複数のブランチ(分岐)やジョイン(結合)、そしてテラバイト規模のステート(状態)を持つようになると、ジョブ内の特定のオペレーター(処理ノード)だけがボトルネックになっているにもかかわらず、ジョブ全体を一律にスケールアウトせざるを得なくなる。これは、一部のコードが無限ループに陥っているからといって、アプリケーションサーバー全体のインスタンスを無駄に増やすようなものだ。
そこでNetflixが舵を切ったのが、オープンソースの「Apache Flink Autoscaler(FLIP-271)」への移行である。この新しいアプローチは、クラスターの外側から大雑把なメトリクスを眺めるのをやめ、実行中のジョブ自体が内部的に公開するスループットと「busy time(ビジー時間)」から、各オペレーターの「True Processing Rate(真の処理レート)」を直接推定する。ジョブのデータフローグラフ(DAG)を自律的に走査し、個々の頂点(vertex)ごとに最適な並行度(parallelism)をピンポイントで計算するのだ。このDS2プロジェクトの研究に基づく「極めてシンプルだが強力なアイデア」こそが、複雑怪奇なステートフルストリーミングを制御するための唯一の解であると私は確信している。
TemporalとSpring Bootで構築した独自の制御盤
Netflixのエンジニアリングが真に恐ろしいのは、オープンソースのツールをただそのまま導入するのではなく、自社の超大規模インフラに適合させるために「容赦ない魔改造」を施す点にある。一般的なKubernetes環境であれば、Flink Kubernetes Operatorをそのままデプロイしてオートスケーリングを任せるのが定石だろう。しかし、30,000以上のストリーミングジョブを抱え、複数のAWSリージョンにまたがって稼働するNetflixの環境では、それではコントロールを失う。彼らは、Flink Kubernetes Operatorに依存するのではなく、自社の内部コントロールプレーンにAutoscalerを統合する道を選んだ。
具体的には、Spring Bootで構築されたマイクロサービスが、分散ワークフローエンジンである「Temporal」を呼び出し、個々のジョブに対するオートスケーリングの決定と実行を完全に分離・隔離している。これにより、ある1つの巨大なジョブのスケーリング処理が、他の数万のジョブの処理をブロックするような「システム全体のデッドロック」を防いでいるのだ。
さらに、彼らが加えた技術的修正は極めて実践的で、現場の泥臭い課題を解決している。例えば、最大3,000ものサブタスクを持つ超巨大ジョブに対応するため、JobManagerのメトリクス収集機構を拡張し、サーバーサイドでのメトリクスフィルタリングを実装した。また、Flinkの「FORWARD接続(データの再分配を伴わない直接接続)」をまたいで並行度を変更すると、不要なデータの再シャッフルが発生してパフォーマンスが低下するため、Netflixの実装ではFORWARD接続されたオペレーター群を1つのグループとして維持するよう制御している。ここで、Netflixの旧オートスケーラーと、今回移行を進めているOSSベースのFlink Autoscalerの構造的な違いを表にまとめておこう。
| 比較項目 | 旧オートスケーラー (2019年〜) | OSSベース Flink Autoscaler (現在) |
|---|---|---|
| スケーリングの単位 | クラスター全体 (TaskManager単位) | 個々のオペレーター (DAGの頂点単位) |
| 主なメトリクスソース | Atlas (CPU, ネットワーク, Kafka lag等) | Flink内部メトリクス (True Processing Rate) |
| 制御アーキテクチャ | Mantis上の独自システム | Spring Boot + Temporal ワークフロー |
| 最大サブタスク対応数 | 数千規模 (限界あり) | 最大3,000サブタスク (最適化済み) |
| 削減実績 / 効果 | リソース 25%〜45% 削減 | 特定チームで計算コスト 58% 削減 (年110万ドル節約) |
この表からも明らかなように、Netflixは単に「流行りのOSSに乗っかった」わけではない。自社の運用実績から得られた知見をコードに落とし込み、コミュニティの成果を自社のプラットフォームへ有機的に融合させているのだ。現に、あるチームではこの移行によってFlinkの年間計算支出を58%削減し、約110万ドル(約1.6億円)ものインフラコストを削減することに成功している。
利用率0.45が示すステートフルストリーミングの不都合な真実
しかし、この華々しい成果の裏には、我々分散システムエンジニアが直視しなければならない「不都合な真実」が隠されている。Netflixは、Flink Autoscalerのターゲット利用率(Target Utilization)を、コミュニティのデフォルト推奨値である「0.7(70%)」ではなく、あえて「0.45(45%)」という極めて低い値に設定している。なぜか。ここに、ステートフルストリーミングにおける最大のボトルネックである「再スケーリング(Rescaling)のコスト」という本質的な問題が横たわっている。
Flinkにおいて、ジョブの並行度を変更するということは、それまでメモリやローカルディスク(RocksDBなど)に保持していたテラバイト規模の「状態(State)」を一度チェックポイントから再分配し、新しいタスクに再ロードすることを意味する。この「ステートの再構築」の間、ストリーミング処理は完全にストップする。もし、ターゲット利用率を0.7のような高い値に設定してしまえば、一時的なトラフィックの揺らぎに対してオートスケーラーが過敏に反応し、頻繁に再スケーリング(Rescaling)を繰り返す「スラッシング(チャタリング)」が発生する。その結果、システムは処理を最適化するどころか、ステートの復旧作業という無限ループに陥り、サービスは使い物にならなくなる。
特に、Netflixが近年注力しているライブ配信(例えば、日本国内でも話題となったプロ野球のライブ配信の可能性や、カウントダウンコンサート『STARTO to MOVE』、さらには2027年からの『Westminster Dog Show』の独占配信など)においては、一瞬の遅延やバッファリングも許されない。このようなミッションクリティカルなリアルタイムイベントを支えるためには、インフラの効率性を多少犠牲にしてでも、利用率0.45という「過剰なバッファ」を持たせ、不要な再スケーリングを徹底的に排除せざるを得ないのが実情なのだ。
Netflixはこの課題に対し、すでに次の布石を打っている。彼らは、再スケーリング時のステート復旧コストを根本的に解決するため、Flink 2で導入される「非集約型ステートアーキテクチャ(Disaggregated State Architecture)」の調査を開始している。これは、ステートの保存と計算を完全に分離し、再スケーリング時のデータ移動を最小限に抑える技術だ。
我々エンジニアがここから学ぶべき「実践的な処方箋」は何か。それは、オートスケーリングを単なる「コスト削減のマジックワード」として捉えるのをやめることだ。特にステートフルなシステムにおいては、スケーリングのトリガーを引くこと自体が、システムに巨大な負荷(税金)を課す行為である。あなたが設計しているそのシステムは、本当にオートスケーリングが必要なほど動的なのか? それとも、適切なプロビジョニングと、ステートの局所性を意識したアーキテクチャ設計こそが先決なのではないか? Netflixが提示した「0.45」という数字は、我々の安易な自動化への信仰に対して、冷徹な問いを突きつけている。


コメント