ストリームウィンドウ処理とは?4方式の使い分けと遅延データの締め切り設計を実装視点で解説
「1分ごとの売上」「直近5分の異常値」。終わりのないイベント列に対してこの手の集計が成り立つのは、流れを区切るウィンドウという仕掛けがあるからです。本記事では、ストリームウィンドウ処理の定義から、タンブリング・スライディング・セッション・グローバルという4つの区切り方、遅れて届いたデータをどこで締め切るかという許容遅延の設計、ステートを膨らませない差分集計の書き方、SQLのウィンドウ関数とAPI実装の境界までを実装者の目線で整理しました。窓長やスライド幅を業務要件から逆算する手順と、ウィンドウ化を見送るべき条件も示します。
まとめ|ウィンドウ4方式の選び分けと許容遅延を先に決める設計順序
ストリームウィンドウ処理とは、始まりと終わりが定義できないイベント列を時間や件数で区切り、その区切りごとに集計・結合を確定させる仕組みを指します。バッチ集計と違い「全件そろった」瞬間が来ないため、どこまでを1回分とみなすかを設計者が決めなければなりません。窓の切り方が、そのまま集計値の意味を決めます。
方式の選び分けは分析要件から一意に決まります。定点観測の集計値がほしいなら重複なく区切るタンブリング、しきい値監視のように直近区間を連続評価したいなら窓を重ねるスライディング、ユーザーの一連の操作をひとまとまりにしたいなら無操作時間で切るセッション。件数や独自条件で確定させたい要件だけがグローバルウィンドウの領分です。
設計の順序を間違えないでください。先に決めるのは窓の長さではなく、遅れて届くデータをどこで締め切るかです。Apache Flinkの許容遅延(allowed lateness)は既定値が0で、ウォーターマーク通過後に届いたイベントは何も設定しなければ捨てられます。締め切りを決めてから窓長・スライド幅・ステート量を逆算する順序が、稼働後の手戻りを一番減らします。
ストリームウィンドウ処理の定義と無限のデータを有限区間へ区切る仕組み
ウィンドウは、無限のデータに対して集計という演算を定義するための必須の道具です。ストリーム処理そのものの全体像はストリーム処理の仕組みとバッチ処理との使い分けで整理しているため、本記事は窓の内部に絞ります。
終わらないデータ列を集計可能にするウィンドウという区切りの役割
ストリーム処理が扱うのは、アクセスログ・センサー値・データベースの変更履歴のように終端が定義できないイベント列です。ここで合計や平均を計算しようとすると、すぐ矛盾に突き当たります。合計はいつ確定するのか。区間を決めない限り、答えは永遠に出ません。
そこで時間や件数でイベント列を有限の区間へ切り出し、その区間内の集合に従来どおりの集計関数を当てる。この切り出しがウィンドウで、切り出す規則を決める部品をFlinkではウィンドウアサイナと呼びます。窓は割り当て・発火・破棄の3段階で動き、終端に許容遅延を足した時刻を過ぎるとステートごと解放されます。1件のイベントが複数の窓に所属することもあり、窓が重なる方式なら同一イベントが数十の窓に同時に数え上げられる。所属関係は排他ではありません。
イベント時間と処理時間のどちらで窓を切るかで変わる集計結果の再現性
窓の境界を判定する時刻には2種類あります。イベント時間はデータに埋め込まれた発生時刻、処理時間はエンジンが処理した壁時計の時刻です。処理時間ウィンドウは実装が単純で遅延も小さい一方、同じ入力を再処理すると結果が変わります。障害復旧やバックフィルで過去分を流し直したとき、全イベントが「いま」到着したものとして扱われ、窓の中身が丸ごと崩れるためです。
再現性が要るなら選択肢はイベント時間しかありません。監視ダッシュボードの表示遅延を見る用途なら処理時間で足りる一方、業務側が金額や件数として参照する数字は、再計算しても同じ値になる必要があります。
タンブリング・スライディング・セッション・グローバルの4方式と選定基準
Flinkが標準で備えるウィンドウアサイナは4種類です。名前は基盤ごとに揺れますが、区切り方の型はこの4つに収まります。それぞれが向く要件と、実装時に踏みやすい地雷を押さえます。
重複なく区切るタンブリングウィンドウが向く定点集計の要件と境界
タンブリングウィンドウは固定長で隙間も重複もなく並ぶ、もっとも単純な区切りです。1分ごとの注文件数、1時間ごとのエラー数といった定点観測がそのまま書けます。1件のイベントがちょうど1つの窓に属するため、集計値の合計が元データの総量と一致し、バッチ集計との照合に使えます。
境界の位置には注意が要ります。Flinkの時間窓は開始時刻を含み終了時刻を含まない半開区間で、オフセットを指定しなければエポック基準で整列します。1時間の窓なら 1:00:00.000 から 1:59:59.999 が1区間。日本時間の日次境界に合わせるにはアサイナへオフセットを渡す必要があり、忘れると「日次集計が9時始まりになっていた」という報告が上がってきます。
窓が重なるスライディングウィンドウで計算量が増える仕組みと歯止め
スライディングウィンドウは、固定長の窓を一定間隔でずらして並べる方式です。「直近5分の平均を10秒ごとに評価する」といったしきい値監視や不正検知で使います。Kafka Streamsや Azure Stream Analytics では同じ形をホッピングウィンドウと呼ぶため、設計書の用語は揃えておいてください。
コストの構造は単純です。窓長をスライド幅で割った値が、1件のイベントが同時に所属する窓の数になります。5分の窓を10秒ずらしなら30個。イベント1件あたり30箇所のステートが更新され、出力レコード数も30倍です。監視要件で「なるべく細かく」と言われたら、この倍率を提示して要件側と詰めてください。
無操作時間で区切るセッションウィンドウのマージ動作と可変長の扱い
セッションウィンドウは、イベントが一定時間途切れたところで区切る可変長の窓です。無操作30分でユーザーの一連の行動を1セッションとみなす、といった行動分析が代表例になります。固定長の2方式と違い、窓の長さも個数も入力次第で変わる点が特徴です。
内部動作はマージです。イベントが届くたびにギャップ分の仮の窓が作られ、既存の窓と時間的に接すれば両者が1つに統合されます。ここから落とし穴が2つ生まれる。ひとつは確定の遅さで、活動が続く限り窓が閉じないため活発なキーのステートは延々と残ります。もうひとつは集計関数の制約で、マージ可能なステートの形でないと統合ができません。連続アクセスがありうるデータでは、セッション長の上限を業務ルールとして設けておくのが安全です。
件数や独自条件で確定させるグローバルウィンドウとカウント窓の位置
グローバルウィンドウは、キーごとに窓をただ1つだけ持ち、時間では区切らない特殊なアサイナです。既定のトリガが NeverTrigger のため、そのままでは永遠に発火しません。組み込みの CountTrigger を与えれば「100件たまるごとに集計」というカウントウィンドウになります。ただし件数基準は、トラフィックが薄い時間帯に窓が閉じず結果が何時間も出てこない弱点を持つ。受注確定のような業務イベントそのものを区切りの合図にしたい要件に絞って使ってください。
ウォーターマークと許容遅延で遅延データの締め切りを決める設計手順
窓の種類を決めたら、次は「いつ閉じるか」です。イベントは発生順には届かないため、ここの設計が甘いと集計値が静かにずれます。エンジン側の実装はApache Flinkの特徴とユースケースの解説で扱った実行モデルの上に乗ります。
ウォーターマーク到達でウィンドウが確定するまでの発火条件と停止要因
イベント時間の窓は、ウォーターマークが窓の終端を追い越した時点で発火します。ウォーターマークは「この時刻より前のイベントはもう来ないとみなす」という印で、ストリーム上を流れていく。イベント時間アサイナの既定トリガである EventTimeTrigger が、この到達を検知して窓関数を呼び出します。
見落とされがちなのが、ウォーターマークが進む条件そのものです。ウォーターマークは入力イベントのタイムスタンプから導出されるため、データが1件も流れないパーティションがあると全体の進行が止まり、窓が閉じません。深夜帯にトラフィックが途切れる系では、アイドル判定を設定しないと「朝になって一気に結果が出る」挙動になります。
許容遅延の既定値0が遅れて届いたデータを捨てる動作とその回避策
公式ドキュメントが明記するとおり、許容遅延の既定値は0です。ウォーターマーク通過後に到着したイベントは、既定では窓に入らず破棄されます。allowedLateness に正の値を与えると、窓の終端に許容遅延を足した時刻までは遅れて届いたイベントも受け入れ、そのたびに窓が再発火して更新後の結果を出します。
設定値は勘で決めないでください。上流の到着遅延を実測し、分位点で判断します。99パーセンタイルが40秒なら許容遅延は1分前後が出発点になり、そこから取りこぼし率とステート保持時間のバランスを見て詰めていく。「安全のため1時間」といった雑な設定は、そのままメモリ不足の原因になります。なおKafka Streamsは grace を書かずに窓を作れないAPI(TimeWindows.ofSizeAndGrace など)へ変わり、既定値に頼らせず設計者へ判断させる方向に寄せています。
サイドアウトプットで捨てられたデータを回収する運用と突合の手順
許容遅延を超えて届いたデータを黙って捨てると、集計値のずれに気づけません。Flinkには sideOutputLateData で遅延データを別ストリームへ流し、getSideOutput で受け取る経路が用意されています。ここをオブジェクトストレージへ落とし、遅延データの件数と金額を日次で集計して本系の集計値に対する比率を監視する形が扱いやすい構成です。比率が閾値を超えたら、許容遅延の設定か上流の遅延を疑います。この回収経路がない基盤は、「数字が合わない」と指摘された時点で切り分け手段を持ちません。
ウィンドウ集計のステート肥大を抑える差分集計と実装パターンの選択
ウィンドウ処理の障害要因は、ほぼステートに集約されます。窓が閉じるまで途中結果を保持する以上、保持の仕方が性能とメモリを直接決める領域です。
全件を溜める窓関数と差分集計で保持する状態量が桁違いになる理由
Flinkの窓関数には2系統あります。ReduceFunction や AggregateFunction は到着のたびに畳み込みを進めるため、窓が保持するのは集約途中の値だけで、1窓あたり数十バイトに収まります。対して ProcessWindowFunction は全要素を内部バッファへ保持し、発火時にまとめて処理する方式です。中央値や上位N件のように全要素を見ないと計算できない処理には要りますが、保持量は要素数に比例して伸びます。
判断は単純です。合計・件数・最大値・平均のように畳み込める集計は、必ず差分集計側で書く。窓のメタ情報が結果に要るだけなら両者は併用でき、畳み込んだ値を ProcessWindowFunction が受け取る形にすれば、全件バッファを避けたまま開始時刻や終了時刻へアクセスできます。これを知らずに書かれたジョブは、トラフィック増加とともに必ず詰まります。
トリガとエビクタで中間結果の出力頻度と保持件数を変える判断軸
既定の発火タイミングを変えたい場面はあります。1時間窓の途中経過を10分ごとに見たい、といった要件です。組み込みトリガには ProcessingTimeTrigger、CountTrigger、発火後にステートを消す PurgingTrigger が用意され、その組み合わせで発火頻度を制御します。ただし途中経過を出すなら、下流は同じ窓に対する複数回の更新を受け取る前提で作らなければなりません。要素を間引くエビクタ(CountEvictor・DeltaEvictor・TimeEvictor)はJava限定で、挟むと全要素のバッファが前提になり差分集計の利点が消えます。窓の設計自体を見直すほうが安く済む場面がほとんどです。
窓長をスライド幅で割った倍率からステート量を見積もる計算手順
見積もりの骨格は、同時に開いている窓の数×キー数×1窓あたりの状態サイズです。窓の数はタンブリングなら1、スライディングなら窓長÷スライド幅、セッションならアクティブなキー数と一致します。
具体例を置きます。キー(商品ID)が50万、窓長5分、スライド10秒、差分集計で1窓あたり64バイトなら、30窓×50万×64バイトでおよそ1GB弱。ここに許容遅延分の保持とチェックポイントのコピーが加わります。RocksDBのようなディスク併用のステートバックエンドなら数GB規模までは現実的な一方、全件バッファを持てば同じ条件で2桁跳ね上がる。設計前にこの掛け算を1回やるだけで、方式選定の議論が感覚論から抜けます。見積もりを超えて流入したときに送出量そのものを抑える手段はバックプレッシャーによる流量制御の解説で整理しています。
SQLのウィンドウ関数とAPI実装のどちらで書くかを分ける境界線
窓の実装はJavaやScalaのAPIだけではありません。SQLで書ける範囲が広がり、データ担当者が直接ウィンドウ集計を定義する構成も現実的になりました。どちらで書くかを整理します。
TUMBLE・HOP・CUMULATE・SESSIONの4関数と出力される3列
Flink SQLでは、テーブルを引数に取るウィンドウTVFという形式で窓を宣言します。4つの関数が用意され、いずれも結果に window_start・window_end・window_time の3列が追加されます。window_time は window_end から1ミリ秒引いた値で、後続の集計や結合で時刻属性として使われる列です。
| 関数 | 区切り方 | 主な引数 | 向く要件 |
|---|---|---|---|
| TUMBLE | 固定長・重複なし | 時刻列, size, offset | 定点集計 |
| HOP | 固定長・重複あり | 時刻列, slide, size | しきい値監視 |
| CUMULATE | 始点固定で伸長 | 時刻列, step, size | 累計の途中経過 |
| SESSION | 無操作時間で分割 | 時刻列, gap | 行動分析 |
SESSION はストリーム実行専用でバッチモードには対応せず、結合やTopNとの組み合わせも限定的な扱いです。オフセット引数は TUMBLE・HOP・CUMULATE で任意指定できます。なお旧来のグループ化ウィンドウ関数は非推奨で、新規の記述はTVF側へ寄せる流れです。SQLレイヤ全体の実行モデルはFlink SQLの動的テーブルと継続クエリの解説で扱っています。
日次の途中経過を1関数で出せるCUMULATEが効く集計要件
4関数のうち、API側に直接の対応物がないのが CUMULATE です。始点を固定したまま終端をstep刻みで伸ばす窓で、「当日0時からの累計売上を10分ごとに更新する」という要件がそのまま1文で書けます。同じことをDataStream APIで書くなら、グローバルウィンドウに独自トリガを付けるか状態を持つ関数を自前で書くことになる。日次・月次の進捗ダッシュボードは要望として頻出するため、この形が中心ならSQL側で書く判断が有利です。逆に、窓の発火条件をイベントの中身で動的に変える処理はSQLの語彙に収まりません。宣言的に書ける定型の窓はSQL、条件分岐を含む制御はAPI。境界はここに引きます。
ウィンドウ処理を採用する条件とウィンドウ化を見送る場面の線引き
窓は無料ではありません。ステート・許容遅延・再発火という運用負担を抱え込む選択なので、要件が本当に窓を求めているかを先に確かめます。
ウィンドウ集計が要る要件と状態TTLだけで足りる要件の見分け方
窓が要るのは「区間ごとの値」に業務上の意味がある場合だけです。1分ごとの取引件数、直近5分の異常スコア、セッション単位の回遊数。これらは区間そのものが分析単位になっています。一方で「現在の在庫数」のように最新値だけを知りたい要件に窓は不要で、キー単位の状態をTTLで打ち切るほうが単純に済みます。
判断を誤りやすいのが「直近1時間のユニークユーザー数」のような要件です。区間の値に見えて実態は近似で足りるケースが多く、その場合はスライディング窓を並べるよりHyperLogLogのような近似構造を状態に持つほうが桁違いに安く上がります。要件の言葉をそのまま窓へ翻訳しないでください。こうした基盤の設計から構築までを外部と進める選択肢もあり、当社のデータ分析基盤構築・MLOps構築支援でも立ち上げを支援しています。
窓長・スライド幅・許容遅延を業務要件から逆算して決める設計手順
パラメータは次の順で決めます。技術側の都合から入ると、必ずどこかで矛盾します。
- 業務が判断に使う時間粒度を決める(例:5分以内に異常を検知したい)
- その粒度から窓長を置く(検知の感度に合わせて5分または10分)
- 結果の更新頻度からスライド幅を決める(30秒更新なら倍率は10〜20)
- 上流の到着遅延を実測し、分位点から許容遅延を置く
- 倍率×キー数で見積もったステート量が予算に収まるか確認する
5番目で予算を超えたら、戻るのは3番目です。スライド幅を粗くすれば倍率が下がり、ステートも出力レコード数も比例して減ります。窓長を削ると分析の意味が変わるため、削る順序は後回しにしてください。この逆算を先にやっておけば、稼働後に仕様変更を業務側へ相談する事態を避けられます。
実装で頻出する3つの失敗パターンと稼働前に潰しておく確認項目
現場で繰り返し見る失敗は3つです。第一に、処理時間ウィンドウで組んでしまい再処理のたびに数字が変わるパターン。バックフィルの瞬間に過去の集計値が上書きされ、レポートの整合が崩れます。第二に、許容遅延を既定の0のまま出して遅延データを黙って捨てているパターンで、バッチ集計との差分が数パーセント出てから発覚します。第三が、スライディング窓の倍率を意識しないまま「細かいほうが良い」と設定してしまう例。5分窓を1秒スライドにすれば倍率は300で、ステートも出力も300倍です。
稼働前の確認項目は4つに絞れます。窓の時刻属性がイベント時間か、許容遅延と遅延データの回収経路が設定されているか、見積もったステート量が予算内か、ウォーターマークが止まりうるアイドル状態に対処しているか。この4点を設計レビューの定型項目に置くだけで、稼働後の障害はかなり減ります。
よくある質問
ウィンドウ処理の実装で迷いやすい論点をまとめます。用語の混同と設定値の決め方に絞って答えます。
ウィンドウ処理とSQLのウィンドウ関数(OVER句)は同じものですか?
別物です。SQLのOVER句は行ごとの移動平均や順位を計算する分析関数で、対象データはすでに全件そろっています。ストリームのウィンドウ処理は終端のないイベント列を区切って有限の集合を作り出す仕組みで、そもそも全件がそろう瞬間がありません。Flink SQLでウィンドウTVFという専用の構文が用意されているのも、この差があるためです。
タンブリングとホッピングとスライディングはどう違いますか?
タンブリングは固定長で重複せず並ぶ窓、ホッピングは固定長の窓を一定間隔でずらして重ねる窓を指します。混乱の元は「スライディング」で、Flinkではホッピングと同義、Kafka Streamsではレコード間の時間差で定義される別方式です。設計書では「固定長・重複あり・スライド幅30秒」のように性質で書くと誤解が起きません。
遅れて届いたデータはどこまで待てばよいですか?
上流の到着遅延を実測してから決めます。発生時刻と到着時刻の差を数日分集め、99パーセンタイルに少し余裕を足した値を出発点にする手順が扱いやすい形です。待ち時間を伸ばすほど取りこぼしは減る一方、窓のステートが長く残り結果の遅延も伸びます。取りこぼした分はサイドアウトプットへ回収し、件数の比率を見ながら調整してください。
ウィンドウのサイズはどう決めればよいですか?
業務側が判断に使う時間粒度から逆算します。異常を5分以内に検知したい要件なら窓長は5分前後、日次レポートの補助なら1時間でも成立する。技術的な制約から先に決めると、集計値の意味が業務の判断単位とずれます。窓長を置いたあとで、更新頻度からスライド幅、到着遅延から許容遅延、同時に開く窓の数からステート量を確認する順序が安全です。
マネージドサービスでもウィンドウ処理は同じように書けますか?
おおむね同じ考え方が通ります。Amazon Managed Service for Apache FlinkはFlinkのAPIとSQLをそのまま使え、Google Cloud DataflowはApache Beamのウィンドウモデル、Azure Stream AnalyticsはSQL方言で同種の窓を提供しています。差が出るのは遅延データの扱いと上限値。窓の種類・許容遅延・出力頻度を要件として書き出しておけば、基盤を替えても設計の翻訳で済みます。
関連記事
- Apache Beamとは|バッチとストリームを同じコードで書く仕組みとランナー選定の判断基準:ウィンドウとトリガを抽象化したモデルを持つ実行基盤で、複数ランナーへ移植する場合の前提になります。
- ksqlDBとは?ストリームとテーブルの仕組み・Kafka Streamsとの違いと採用判断を解説:Kafka単独構成でウィンドウ集計をSQLで書く場合の選択肢として比較できます。
- イベント駆動アーキテクチャとは?仕組み・実装パターンと採用判断を実装者目線で解説:窓で集計する対象となるイベントが、どう発行され流れてくるかという上流側の設計です。