ウォーターマーク(ストリーム処理)とは?生成戦略と進行停止の切り分けを解説
ストリーム処理でイベント時間の集計を組むと、必ず一度は「窓がいつまで経っても閉じない」「昨日まで出ていた数字が急に空になった」という現象に当たります。原因のほとんどは、集計ロジックではなくウォーターマークの作り方にあります。この記事では、ウォーターマークが何を宣言する印なのか、どの生成戦略をどう選ぶのか、遅延許容幅をどの数字から決めるのか、そして進行が止まったときにどこから切り分けるのかを、印を作る側の視点で整理しました。端末の時計がずれたイベントが混ざったときの被害と防ぎ方、イベント時間そのものを見送ってよい条件まで扱います。
まとめ:ウォーターマーク設計で着手前に決める3つの値と役割分担
ウォーターマークは「このタイムスタンプより前のイベントはもう届かないとみなす」という宣言をストリーム上に流す仕組みです。集計そのものは何も計算せず、下流の処理に対して時間の進み具合だけを伝えます。この印をどう作るかで、結果が出るまでの遅さと取りこぼしの量が同時に決まります。
着手前に確定させる値は3つです。第一に遅延許容幅、つまり観測した最大タイムスタンプからどれだけ引いた位置に印を置くか。第二にアイドル判定の有無と待ち時間、つまり無音のパーティションを進行の計算から外すかどうか。第三にイベント時刻の出どころ、つまりどのフィールドを時刻として信じ、どこまでの値を許すか。この3点が決まれば、生成戦略の選択とソース側の実装はほぼ自動的に決まります。
役割分担を取り違えないでください。ウォーターマークの遅延許容幅は「結果を出す前にどれだけ待つか」を決める値で、窓に入りそこねたイベントを救う値ではありません。救済側の設定は許容遅延で、これはウィンドウ4方式と遅延データの締め切り設計を扱った記事で整理しています。待つ時間と救う窓は別のつまみだと理解しておくと、設定値の議論が噛み合います。
ウォーターマークの定義とイベント時間の進行を示す水位線としての役割
まず印そのものの意味を確定させます。ここが曖昧なままでは、設定値を触っても進行停止の切り分けは困難です。ストリーム処理全体の枠組みはストリーム処理の仕組みとバッチ処理との使い分けを解説した記事にまとめており、本記事は時間の進め方だけに絞ります。
指定時刻より前のイベントは届かないと宣言する印が持つ処理上の意味
ウォーターマークは、値としては単なるタイムスタンプです。ストリーム上をイベントと同じ経路で流れていき、途中のオペレータに「ここまでの時刻は完了した」と伝えます。受け取った側は、その時刻以前を対象とする処理を確定させてよいと判断できます。
ここで押さえるべきは、この宣言が推測にすぎない点です。実際にそれ以前のイベントが二度と来ない保証はどこにもなく、印より後ろに届いたイベントは遅延データとして扱われます。つまりウォーターマークは正しさの証明ではなく、待ち切るための打ち切り線です。打ち切り線を後ろに置けば取りこぼしは減り、結果は遅れる。前に置けば結果は早く出て、取りこぼしが増えます。このトレードオフの調整つまみが遅延許容幅です。
時刻を割り当てる部品と印を発行する部品に分かれる設計上の内部構造
実装では2つの部品を組み合わせます。ひとつはイベントからタイムスタンプを取り出す部品、もうひとつはその観測結果から印を発行する部品です。Flinkでは前者をタイムスタンプアサイナ、後者をウォーターマークジェネレータと呼び、WatermarkStrategy がこの2つを束ねます。
時刻を取り出す部分を軽く見ないでください。ここで参照するフィールドが「レコードがデータベースに書かれた時刻」なのか「利用者が操作した時刻」なのかで、集計の意味そのものが変わります。注文の集計で書き込み時刻を使えば、バッチ連携で夜間にまとめて届いた注文が全部その夜の売上になる。時刻の出どころは、集計仕様の一部として業務側と合意しておく項目です。
ウォーターマークの生成戦略3種と遅延許容幅を実測から決める手順
Flink 2.3系の WatermarkStrategy には、そのまま使える静的メソッドが用意されています。自前でジェネレータを書く前に、既製の戦略で足りるかを確認します。
単調増加を前提にする戦略と遅延許容幅つき戦略を使い分ける基準
既製の戦略は実質3種類です。forMonotonousTimestamps はタイムスタンプが単調増加する前提で、観測した最大値をそのまま印にします。forBoundedOutOfOrderness は指定した時間だけ最大値より手前に印を置き、その幅の中での順序乱れを許します。noWatermarks は印を出さず、イベント時間の処理を止めたままにする選択です。
選び分けの基準は、上流が順序を保証しているかどうかに尽きます。単一パーティションのログを1本のプロセスが順に書いているなら単調増加で足りる。複数の端末やサーバから集まる、あるいはKafkaの複数パーティションを1つのサブタスクが読むなら、順序は崩れる前提で遅延許容幅つきを選びます。実務では後者がほとんどで、単調増加を選べる場面は限られます。判断に迷ったら遅延許容幅つきを選び、幅の値で調整してください。
周期的に発行する方式と特定イベントを合図に発行する方式の実装上の差
印を出すタイミングにも2通りあります。周期発行はフレームワークが一定間隔でジェネレータを呼び、そのとき保持している最大タイムスタンプから印を作る方式です。断続発行はイベントごとの処理の中で、特定の目印を持つレコードを見つけたときだけ印を出します。
既製の戦略はいずれも周期発行で、発行間隔は設定値として与えます。間隔を短くすれば時間の進みは滑らかになる一方、印そのものがレコードとして流れる量が増えます。断続発行が効くのは、上流のシステムが「この時刻までは送り終えた」という制御メッセージを明示的に流してくる構成です。この場合は推測ではなく事実に基づいて印を出せるため、遅延許容幅をゼロに近づけられます。上流を自社で作れるなら、制御メッセージを流す設計を先に検討する価値があります。
遅延許容幅を到着遅延の分位点から逆算して設定する具体的な実測手順
遅延許容幅を勘で決めた基盤は、後から必ず作り直しになります。手順は3段です。まずイベント時刻と取り込み時刻の差を、平日と休日をまたいで数日分収集する。次にその分布の分位点を出す。最後に99パーセンタイル前後を出発点として置き、そこから結果の遅さと取りこぼし率を見て詰めます。
分布は片側に長い裾を持つのが普通で、平均値を使うと必ず短すぎる幅になります。逆に最大値に合わせると、モバイル端末の圏外復帰のような数時間の外れ値に引きずられ、全体の結果が数時間遅れる基盤ができあがる。裾の側は幅で吸収せず、遅延データとして別経路で回収する設計に寄せてください。幅の妥当性は稼働後も変わるため、上流のシステムが増えたタイミングで測り直す運用を組み込んでおきます。
ウォーターマークが進まない原因の切り分けとアイドル判定の設定手順
現場で最も多い相談が「窓が閉じない」です。原因は集計側ではなく、ほぼ印の進行側です。切り分けは上流から順に3段でたどります。エンジン側の実行モデルはApache Flinkの特徴とユースケースを解説した記事で扱った構造の上に乗ります。
パーティション単位の進行と最小値での合流が全体を止める仕組み
複数の入力を持つオペレータは、入力それぞれのウォーターマークのうち最小値を自分の現在時刻とします。ソースがKafkaのようにスプリット単位で印を持てる場合も同じで、スプリットごとの印がまとめられるときに最小値が採られる。この設計自体は正しく、遅れている入力を無視して確定させないための仕組みです。
問題は、1つでも進まない入力があると全体が止まる点にあります。深夜に取引が途切れる店舗、テスト用に作ったまま誰も書き込んでいないパーティション、障害で停止した1系統。どれか1つで全体の時間が凍り、他の入力がいくら流れても窓は閉じません。切り分けの最初の一手は、パーティションごとの最終書き込み時刻を並べて、進んでいない系統を特定することです。
並列度がパーティション数を上回るとサブタスクが空になる落とし穴
見落とされやすいのが並列度の設定です。ソースの並列度を入力パーティション数より大きくすると、割り当てが1つも来ないサブタスクが生まれます。そのサブタスクはイベントを1件も観測しないため、印を進める材料を持ちません。結果として、データは正常に流れているのに全体の時間だけが止まります。
この状態は監視画面上でスループットが出ているため、原因にたどり着くまで時間を取られます。確認は単純で、ソースの並列度とパーティション総数を突き合わせるだけ。並列度はパーティション数以下に揃えるのが原則で、処理が追いつかないなら並列度ではなく後段の処理を分けるか、パーティション自体を増やします。増設が難しい場合は、次に述べるアイドル判定で回避します。
アイドル判定と進行の同期で停止と先走りの両方を抑えるための設定方法
アイドル判定は、一定時間イベントが届かない入力を「進行の計算から一時的に外す」設定です。WatermarkStrategy に withIdleness で待ち時間を与えると、無音になった入力は最小値の計算対象から除かれ、残りの入力だけで時間が進みます。無音の系統が復活すれば、また計算対象に戻ります。
待ち時間の目安は、正常時のイベント間隔の数倍です。短く置きすぎると、たまたま空いた数秒でその入力を外してしまい、直後に届いたイベントが遅延データ扱いになります。逆の問題もあります。片方の入力だけが猛烈に速く、過去分のバックフィルで一気に時間を進めてしまうと、遅いほうの入力のイベントが軒並み手遅れになる。この対策が進行の同期で、withWatermarkAlignment にグループ名と許容ドリフト、更新間隔を与えると、進みすぎた入力の読み取りが一時的に抑えられます。読み取りを止める形で全体の流量が変わるため、バックプレッシャーによる流量制御を解説した記事と合わせて挙動を確認してください。
端末の時計がずれて未来のイベント時刻が混ざったときの被害と対処
ここまでは印が進まない話でした。逆方向の事故、つまり印が進みすぎる事故のほうが被害は大きく、しかも気づきにくい構造を持っています。
未来のタイムスタンプ1件が水位線を飛ばして全件を遅延にする構造
遅延許容幅つきの戦略は、観測した最大タイムスタンプから幅を引いた位置に印を置きます。ここに、時計が3日進んだ端末からのイベントが1件混ざるとどうなるか。最大タイムスタンプが3日先へ跳ね、印もそれに追随します。以後に届く正常なイベントはすべて印より過去となり、遅延データとして扱われる。集計値は空になり、3日経つまで自然回復しません。
この事故は、モバイルアプリやIoT機器のように時刻をクライアント側で打つ構成で起こります。サーバ側で時刻を打つ構成なら発生しませんが、その場合は通信遅延がそのままイベント時刻の誤差になるため、どちらを選んでも検証は要ります。厄介なのは、ジョブは正常稼働のまま数字だけが消える点です。監視項目に遅延データの件数比率を入れておかないと、業務側からの指摘で初めて気づきます。
取り込み時刻との差を見てイベント時刻を検証し弾くための実装手順
対処は、印を作る前段でイベント時刻を検証することに尽きます。実装は3通りあり、要件で選びます。ひとつは上限クランプで、取り込み時刻より一定時間以上先のタイムスタンプを取り込み時刻に置き換える方法。ふたつめは除外で、しきい値を超えたイベントを別経路へ流して本系の印に影響させない方法。みっつめは二重保持で、クライアント時刻とサーバ時刻の両方をレコードに持ち、印はサーバ時刻から作って分析はクライアント時刻で行う方法です。
推奨は3つめです。時刻を捨てずに残せるため、後から端末側の時計ずれの分布を分析でき、しきい値の見直しにも使えます。実装コストが許さない場合はクランプで妥協し、クランプが発生した件数を必ず記録してください。件数が増えていれば、特定機種やアプリ版での時計不整合を疑う材料になります。どの方法を採るにせよ、未来時刻に対する防御がない基盤は、いつか静かに数字を失います。
基盤ごとのウォーターマーク相当機能の違いと設計を移す際の注意点
ウォーターマークという語はFlinkの用語として広まりましたが、同じ役割の仕組みは各基盤にあります。呼び名と粒度が違うため、移植時の翻訳表を持っておくと設計が崩れません。
ストリーム時間で進める方式と印を明示的に流す方式の違いと選定基準
Kafka Streamsにはストリーム上を流れる印がありません。代わりにタスクごとのストリーム時間、つまり観測した最大タイムスタンプを内部で保持し、窓の確定に使います。印を流さないため、下流のオペレータへ進行を伝える経路もありません。この差は、複数入力の合流で表面化します。明示的に印を流す方式では最小値合流とアイドル判定という制御点を持てる一方、ストリーム時間方式では基盤の実装に委ねる部分が増えます。
| 基盤 | 時間の進め方 | 締め切りの設定 | 停止への対処 |
|---|---|---|---|
| Flink | 印を明示的に流す | 遅延許容幅と許容遅延 | アイドル判定と同期設定 |
| Kafka Streams | タスクのストリーム時間 | 猶予期間で確定 | 基盤の実装に依存 |
| Spark | 宣言した遅延しきい値 | しきい値超過で破棄 | 複数入力の方針を設定 |
| Beam | ソースが水位を報告 | 許容遅延とトリガ | ランナー実装に依存 |
移植でずれるのは、締め切りの意味です。同じ「5分」という設定でも、待つ時間を指すのか救済窓を指すのかが基盤で違います。設計書には数値だけでなく、その値がどちらのつまみかを書き添えてください。
SQLで宣言する方法とAPIで組み立てる方法のどちらを選ぶか
Flink SQLでは、テーブル定義の中でイベント時刻の列と遅延許容幅を宣言する形になります。時刻列から一定の時間を引いた値を印とする、という1行の宣言で済むため、データ担当者が自分で書ける点が利点です。SQLレイヤ全体の実行モデルはFlink SQLの動的テーブルと継続クエリを解説した記事で扱っています。
線引きは印の作り方の複雑さで決めます。単純に「最大値から一定時間を引く」だけならSQL側で宣言するほうが保守が楽で、テーブル定義を見れば設定が分かる。一方、上流の制御メッセージを合図に印を出す、送信元ごとに幅を変える、未来時刻をクランプするといった処理はSQLの語彙に収まりません。これらが要るならAPI側でジェネレータを書きます。混在させる構成も可能で、その場合は印を作る箇所を1か所に寄せ、どこで印が生まれるかを設計書で明示しておいてください。
ウォーターマークを採用する条件とイベント時間を見送る場面の線引き
印を持つと、遅延許容幅の維持と進行監視という運用負担が生まれます。要件が本当にイベント時間を求めているかを、先に確かめる価値があります。
再現性が要る要件と処理時間で足りる要件を分ける判断軸と採用条件
判断軸は再現性です。障害復旧やバックフィルで同じ入力を流し直したとき、同じ結果が出る必要があるか。業務側が金額や件数として参照する数字は、ここが必須になります。逆に、稼働監視のダッシュボードで直近の傾向だけを見る用途なら、処理時間で組んだほうが実装も運用も軽く済みます。
もうひとつの軸は、順序の乱れが実際に起きているかどうかです。到着遅延を実測して、乱れが数十ミリ秒に収まる単一系統なら、印を作る仕組みを持ち込む価値は薄い。イベント時間を採るなら遅延許容幅の維持まで含めて引き受ける、という前提で選んでください。中途半端に印を置いて幅を放置した基盤が、いちばん切り分けに時間を取られます。
ストリーム基盤を内製で保守する条件と受託に切り出す線引きの判断基準
ウォーターマークの設計は、作って終わりではありません。上流の系統が増えれば到着遅延の分布が変わり、幅とアイドル判定の見直しが要ります。内製が成立する条件は、ストリーム基盤の運用経験者が複数名いて、遅延データの件数比率を継続的に見る体制を持てること。この体制がないまま印を置くと、数字が合わないと指摘された時点で切り分けの手段がありません。当社ではデータ分析基盤構築・MLOps構築支援として、時刻設計と生成戦略の選定から進行監視の設計までを受託しています。設計レビューだけを依頼し、実装は内製で進める形も選べます。
よくある質問
ウォーターマークの設定で判断が割れやすい論点に、実装の観点から答えます。
ウォーターマークと許容遅延はどう違いますか?
役割が別です。ウォーターマークの遅延許容幅は、結果を確定させる前にどれだけ待つかを決める値で、待つほど結果が遅くなります。許容遅延は、印が通り過ぎた後に届いたイベントをどこまで受け入れて再計算するかを決める救済窓です。前者を伸ばすと全体の出力が遅れ、後者を伸ばすと保持する状態が増えます。片方だけを大きくして解決しようとすると、必ずどちらかの副作用を踏みます。
ウォーターマークが進まないときは何から確認すればよいですか?
確認は3段です。第一にソースの並列度が入力パーティション数を超えていないか。超えていれば、割り当ての来ないサブタスクが進行を止めています。第二にパーティションごとの最終書き込み時刻を並べ、無音の系統がないか。第三にアイドル判定が設定されているか。この3点で大半は切り分けられます。それでも進まない場合は、時刻を取り出すフィールドが空やゼロ値になっていないかを疑ってください。
遅延許容幅はどのくらいの値に設定すべきですか?
実測してから決めます。イベント時刻と取り込み時刻の差を数日分集め、99パーセンタイル付近を出発点に置く手順が扱いやすい形です。平均値では必ず短すぎ、最大値に合わせると外れ値に引きずられて結果が大幅に遅れます。裾の部分は幅で吸収せず、遅延データとして別経路で回収する設計に寄せてください。上流の系統が増えたら測り直します。
ウォーターマークはソースとオペレータのどちらで作るべきですか?
ソース側を推奨します。ソース側で割り当てれば、Kafkaのようにスプリット単位で印を持てるコネクタでは、パーティションごとの進行を個別に追えるためです。後段で割り当てると、複数パーティションが混ざった後の順序で判断することになり、必要な遅延許容幅が実態より大きくなります。後段で作らざるを得ないのは、時刻が入れ子の構造の中にあり、パース処理を挟まないと取り出せない場合などに限られます。
ウォーターマークを使わない構成は選べますか?
選択は可能です。印を出さない設定を明示的に選び、処理時間で窓を切る構成が成立します。再現性が要らない監視用途や、順序の乱れが実質ゼロの単一系統なら、この選択のほうが運用は軽くなります。ただし後からイベント時間へ切り替える場合、時刻の出どころの合意と遅延の実測をやり直すことになるため、業務側が数字として参照する集計を含むなら最初からイベント時間で組んでおくほうが安全です。
関連記事
- ストリームウィンドウ処理とは?4方式の使い分けと遅延データの締め切り設計を実装視点で解説:本記事で作った印を消費する側、つまり窓の確定と許容遅延の設計を扱っています。
- Apache Beamとは|バッチとストリームを同じコードで書く仕組みとランナー選定の判断基準:ウォーターマークとトリガを抽象化したモデルを持つ実行基盤で、複数ランナーへ移す場合の前提になります。
- イベント駆動アーキテクチャとは?仕組み・実装パターンと採用判断を実装者目線で解説:印の材料となるイベントがどう発行され、どんな順序で届くかという上流側の設計です。