This is an automated email from the ASF dual-hosted git repository.
fhueske pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git
The following commit(s) were added to refs/heads/master by this push:
new 07b4ff26f54 [hotfix][docs] Document LATERAL SNAPSHOT load-phase
handling for static build sides
07b4ff26f54 is described below
commit 07b4ff26f545e0d9373949fd82573074ae442291
Author: Fabian Hueske <[email protected]>
AuthorDate: Wed Sep 16 14:43:46 2026 +0200
[hotfix][docs] Document LATERAL SNAPSHOT load-phase handling for static
build sides
Add sections for handling static and nearly static build sides and operator
metrics.
Generated-By: Claude Sonnet 5
---
.../content.zh/docs/sql/reference/queries/joins.md | 42 +++++++++++++++++++++
docs/content/docs/sql/reference/queries/joins.md | 43 ++++++++++++++++++++++
2 files changed, 85 insertions(+)
diff --git a/docs/content.zh/docs/sql/reference/queries/joins.md
b/docs/content.zh/docs/sql/reference/queries/joins.md
index eaacc8eee5e..1d83fd7e270 100644
--- a/docs/content.zh/docs/sql/reference/queries/joins.md
+++ b/docs/content.zh/docs/sql/reference/queries/joins.md
@@ -358,6 +358,25 @@ Build-side updates continue to be applied to the state and
become visible to sub
The load phase is what distinguishes a `LATERAL SNAPSHOT` join from the
[processing-time temporal join](#processing-time-temporal-join), which has been
disabled for Flink SQL. The processing-time temporal join starts joining
immediately at query start, so early probe-side rows are joined against
whatever build-side data happens to have been loaded so far, producing missing
or stale results that depend on the order in which the inputs are read. By
first loading the build side, a `LATERAL [...]
+**Completing the load phase for static or nearly-static build sides**
+
+The event-time gate (build-side watermark reaches `load_completed_time`) works
well when the build side keeps producing records and advancing its watermark.
+Build sides that are *static* (loaded once, then silent) or *nearly static*
(infrequent updates) are common for reference data such as currency rates,
product catalogs, or feature tables.
+Since watermarks only advance when new records with larger event times arrive,
such inputs may take a long time to (or never) produce a watermark that reaches
`load_completed_time`.
+This leaves the join **stuck in the load phase indefinitely**: probe-side rows
keep buffering, state keeps growing, and no results are ever emitted.
+
+Picking a `load_completed_time` that both sits above the build side's real
event times and is guaranteed to be reached by its watermark is difficult and
requires insight into the data: set it too low and the join flips before the
build side is fully loaded (probe rows are joined against an incomplete build
side); set it too high and the join never flips.
+
+If the build-side input is truly static and its source connector supports it,
configure the connector to run in **bounded mode**.
+When the source finishes reading, it advances the watermark to its maximum
value, which completes the load phase.
+
+If the build side is not truly static or the connector does not support a
bounded mode, set **`load_completed_idle_timeout`**.
+It is a processing-time (wall-clock) safety net that is independent of
event-time progress: the operator flips from load to join once the build-side
watermark has not advanced for the configured duration, so it triggers the flip
during a genuine stall — exactly the situation a static or idle build side ends
up in once it has been fully read.
+Choose the timeout larger than any expected pause in the build side's changes
during start-up — if it fires while the backlog is still being read, the join
flips against an incomplete build side.
+`load_completed_idle_timeout` is **not set by default**, so you must set it
explicitly if your build side may become static or idle during start-up.
+
+If a `LATERAL SNAPSHOT` join produces no output, first check whether it is
stuck in the load phase using the metrics below, then set
`load_completed_idle_timeout`.
+
**Syntax**
The build side is wrapped in the `SNAPSHOT` table function, which is called
with `LATERAL`. The outer (probe-side) table must be an append-only table.
@@ -395,6 +414,29 @@ Because rows are joined against the build-side state that
is current at processi
The build-side state grows with the number of distinct build-side keys, and
during the load phase the buffered probe-side rows add to the state footprint
until the operator transitions to the join phase. Use `state_ttl` to bound the
build-side state for keys that are no longer updated or joined. You can reduce
the amount of data that is buffered and processed during the load phase by
configuring scan start offsets on the build and probe-side inputs, for example
with a `scan.startup.*` [d [...]
+**Monitoring**
+
+The operator exposes metrics that help you understand which phase the join is
in and whether it is making progress. They are reported under the operator's
[metric group]({{< ref "docs/ops/metrics" >}}) alongside the standard operator
metrics.
+
+| Metric | Type | Description |
+| --- | --- | --- |
+| `currentPhase` | Gauge | The current phase: `0` = load phase, `1` = join
phase. A value stuck at `0` means the load phase has not completed. |
+| `currentBuildSideWatermark` | Gauge | The highest build-side watermark
observed (epoch milliseconds). In the load phase the flip happens once this
reaches `load_completed_time`. A value that stops advancing below
`load_completed_time` is the signature of a static or idle build side that
might not complete the load phase on its own. |
+| `currentProbeSideWatermark` | Gauge | The latest probe-side watermark
observed (epoch milliseconds). Probe-side watermarks are held back during the
load phase and forwarded during the join phase. |
+| `numProbeSideRecordsBuffered` | Gauge | The number of probe-side rows
currently buffered. This grows during the load phase and drops to zero shortly
after the flip to the join phase. |
+| `numStateTtlEvictions` | Counter | The number of build-side keys evicted
from state by `state_ttl`. Only advances during the join phase. |
+| `maxJoinFanOut` | Gauge | The largest number of joined rows emitted for a
single probe-side row. |
+| `avgJoinFanOut` | Gauge | The average number of joined rows emitted per
probe-side row. |
+| `numUnmatchedProbeRecords` | Counter | The number of probe-side rows that
found no build-side match. For an `INNER JOIN` these produce no output; for a
`LEFT JOIN` they are emitted null-padded. |
+| `numUnmatchedBuildRetractions` | Counter | The number of build-side
retractions (`-U`/`-D`) for a row that was not present in state. |
+
+To diagnose a join that emits no output, check `currentPhase` first. If it is
`0` while `numProbeSideRecordsBuffered` keeps growing, the join is stuck in the
load phase.
+Compare `currentBuildSideWatermark` against your `load_completed_time`: if the
build-side watermark has stopped advancing below it, the build side has become
static or idle and the event-time gate will never be crossed.
+Set `load_completed_idle_timeout` as described above to let the load phase
complete.
+
+On a query restart, the metric values are not restored: counters restart at
zero and the watermark and buffer gauges are re-populated as new records and
watermarks arrive.
+The phase, however, is restored from state.
+
**Batch mode**
In batch mode, a `LATERAL SNAPSHOT` join is executed as a regular (`INNER` or
`LEFT`) join between the probe side and the complete build side. Batch
execution reads the entire build side before joining, so there is no load phase
and no incremental state build-up. The streaming-specific arguments (`on_time`,
`load_completed_time`, `load_completed_idle_timeout`, and `state_ttl`) are
accepted but have no effect, and the build side does not need to declare a
watermark or provide a `on_time`.
diff --git a/docs/content/docs/sql/reference/queries/joins.md
b/docs/content/docs/sql/reference/queries/joins.md
index c815b0b585f..ba31ded4566 100644
--- a/docs/content/docs/sql/reference/queries/joins.md
+++ b/docs/content/docs/sql/reference/queries/joins.md
@@ -367,6 +367,25 @@ The load phase is what distinguishes a `LATERAL SNAPSHOT`
join from the [process
The processing-time temporal join starts joining immediately at query start,
so early probe-side rows are joined against whatever build-side data happens to
have been loaded so far, producing missing or stale results that depend on the
order in which the inputs are read.
By first loading the build side, a `LATERAL SNAPSHOT` join avoids this problem.
+**Completing the load phase for static or nearly-static build sides**
+
+The event-time gate (build-side watermark reaches `load_completed_time`) works
well when the build side keeps producing records and advancing its watermark.
+Build sides that are *static* (loaded once, then silent) or *nearly static*
(infrequent updates) are common for reference data such as currency rates,
product catalogs, or feature tables.
+Since watermarks only advance when new records with larger event times arrive,
such inputs may take a long time to (or never) produce a watermark that reaches
`load_completed_time`.
+This leaves the join **stuck in the load phase indefinitely**: probe-side rows
keep buffering, state keeps growing, and no results are ever emitted.
+
+Picking a `load_completed_time` that both sits above the build side's real
event times and is guaranteed to be reached by its watermark is difficult and
requires insight into the data: set it too low and the join flips before the
build side is fully loaded (probe rows are joined against an incomplete build
side); set it too high and the join never flips.
+
+If the build-side input is truly static and its source connector supports it,
configure the connector to run in **bounded mode**.
+When the source finishes reading, it advances the watermark to its maximum
value, which completes the load phase.
+
+If the build side is not truly static or the connector does not support a
bounded mode, set **`load_completed_idle_timeout`**.
+It is a processing-time (wall-clock) safety net that is independent of
event-time progress: the operator flips from load to join once the build-side
watermark has not advanced for the configured duration, so it triggers the flip
during a genuine stall — exactly the situation a static or idle build side ends
up in once it has been fully read.
+Choose the timeout larger than any expected pause in the build side's changes
during start-up — if it fires while the backlog is still being read, the join
flips against an incomplete build side.
+`load_completed_idle_timeout` is **not set by default**, so you must set it
explicitly if your build side may become static or idle during start-up.
+
+If a `LATERAL SNAPSHOT` join produces no output, first check whether it is
stuck in the load phase using the metrics below, then set
`load_completed_idle_timeout`.
+
**Syntax**
The build side is wrapped in the `SNAPSHOT` table function, which is called
with `LATERAL`. The outer (probe-side) table must be an append-only table.
@@ -404,6 +423,30 @@ Because rows are joined against the build-side state that
is current at processi
The build-side state grows with the number of distinct build-side keys, and
during the load phase the buffered probe-side rows add to the state footprint
until the operator transitions to the join phase. Use `state_ttl` to bound the
build-side state for keys that are no longer updated or joined. You can reduce
the amount of data that is buffered and processed during the load phase by
configuring scan start offsets on the build and probe-side inputs, for example
with a `scan.startup.*` [d [...]
+**Monitoring**
+
+The operator exposes metrics that help you understand which phase the join is
in and whether it is making progress.
+They are reported under the operator's [metric group]({{< ref
"docs/ops/metrics" >}}) alongside the standard operator metrics.
+
+| Metric | Type | Description
|
+| --- | ---
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `currentPhase` | Gauge | The current phase: `0` = load phase, `1` = join
phase. A value stuck at `0` means the load phase has not completed.
|
+| `currentBuildSideWatermark` | Gauge | The highest build-side watermark
observed (epoch milliseconds). In the load phase the flip happens once this
reaches `load_completed_time`. A value that stops advancing below
`load_completed_time` is the signature of a static or idle build side that
might not complete the load phase on its own. |
+| `currentProbeSideWatermark` | Gauge | The latest probe-side watermark
observed (epoch milliseconds). Probe-side watermarks are held back during the
load phase and forwarded during the join phase.
|
+| `numProbeSideRecordsBuffered` | Gauge | The number of probe-side rows
currently buffered. This grows during the load phase and drops to zero shortly
after the flip to the join phase.
|
+| `numStateTtlEvictions` | Counter | The number of build-side keys evicted
from state by `state_ttl`. Only advances during the join phase.
|
+| `maxJoinFanOut` | Gauge | The largest number of joined rows emitted for a
single probe-side row.
|
+| `avgJoinFanOut` | Gauge | The average number of joined rows emitted per
probe-side row.
|
+| `numUnmatchedProbeRecords` | Counter | The number of probe-side rows that
found no build-side match. For an `INNER JOIN` these produce no output; for a
`LEFT JOIN` they are emitted null-padded.
|
+| `numUnmatchedBuildRetractions` | Counter | The number of build-side
retractions (`-U`/`-D`) for a row that was not present in state.
|
+
+To diagnose a join that emits no output, check `currentPhase` first. If it is
`0` while `numProbeSideRecordsBuffered` keeps growing, the join is stuck in the
load phase.
+Compare `currentBuildSideWatermark` against your `load_completed_time`: if the
build-side watermark has stopped advancing below it, the build side has become
static or idle and the event-time gate will never be crossed.
+Set `load_completed_idle_timeout` as described above to let the load phase
complete.
+
+On a query restart, the metric values are not restored: counters restart at
zero and the watermark and buffer gauges are re-populated as new records and
watermarks arrive.
+The phase, however, is restored from state.
+
**Batch mode**
In batch mode, a `LATERAL SNAPSHOT` join is executed as a regular (`INNER` or
`LEFT`) join between the probe side and the complete build side. Batch
execution reads the entire build side before joining, so there is no load phase
and no incremental state build-up. The streaming-specific arguments (`on_time`,
`load_completed_time`, `load_completed_idle_timeout`, and `state_ttl`) are
accepted but have no effect, and the build side does not need to declare a
watermark or provide a `on_time`.