Repository navigation
Description
Search before asking
- I had searched in the issues and found no similar issues.
Version
master @ 72e9189 (2026-11-03). The code paths below are unchanged on master as of 2026-11-05.
What's Wrong?
For a Unique Key Merge-on-Write base table, <stream>@snapshot() rebuilds the image at the consumption offset for partitions that changed after the offset. The rebuild combines two parts (NormalizeOlapTableStreamScan.java#L318-L345):
- Base table part: rows whose commit TSO is not newer than the partition offset.
- Changelog part: a
MIN_DELTAchangelog scan from the offset, filtered toDELETEandUPDATE_BEFORErows, which adds back the before image of rows changed after the offset (#L160-L166, #L222-L229).
MIN_DELTA drops a key's change group entirely when the first before image equals the last after image (the no-op elimination added in #66849). In the UPDATE_BEFORE_AFTER case, _min_delta_values_equal() returns true and nothing is emitted for the key (block_reader.cpp#L318-L323, #L133-L167).
So when a key is rewritten with identical values after the offset (an upsert of the same row, or a DELETE followed by an INSERT of the same values):
- the base table part filters the row out, because its commit TSO is now newer than the offset;
- the changelog part emits nothing for the key, because
MIN_DELTAtreats the change as a no-op.
The key is then missing from @snapshot(), although it existed at the offset and still exists.
IVM may be affected as well. The join delta handler reads the other side's pre-refresh image through the same snapshot path (IvmJoinDeltaHandler → IvmDeltaRewriteHelper.preSnapshot() → withSnapshot(...); IvmJoinDeltaHandler.java#L123-L149, IvmDeltaRewriteHelper.java#L425-L448), so a join MV refresh could miss matches for such keys.
What You Expected?
@snapshot() returns every row that was visible at the consumption offset. The no-op elimination is correct for consumers of net changes, but the snapshot rebuild needs the UPDATE_BEFORE of such a group, or must read the changelog without the elimination.
How to Reproduce?
-- fe.conf: enable_feature_changelog = true, enable_table_stream = true
CREATE TABLE t (k INT, v VARCHAR(10))
UNIQUE KEY(k)
DISTRIBUTED BY HASH(k) BUCKETS 1
PROPERTIES (
"replication_num" = "1",
"enable_unique_key_merge_on_write" = "true",
"changelog.enable" = "true",
"changelog.need_historical_value" = "true"
);
INSERT INTO t VALUES (1, 'a'), (2, 'b');
CREATE STREAM s ON TABLE t
PROPERTIES ("type" = "min_delta", "show_initial_rows" = "false");
-- identical rewrite after the offset
INSERT INTO t VALUES (1, 'a');
SELECT * FROM s@snapshot() ORDER BY k;
-- expected: (1, 'a'), (2, 'b')
-- suspected: (2, 'b') onlyDELETE FROM t WHERE k = 1; INSERT INTO t VALUES (1, 'a'); between the stream creation and the snapshot read should hit the same path.
Anything Else?
Found by code reading while documenting the changelog and Table Stream features; the reproduction above has not been run yet. Regression cases for identical rewrites and for delete-then-reinsert of the same values, under both @snapshot() and a join IVM refresh, would cover this.
Are you willing to submit PR?
- Yes I am willing to submit a PR!
Code of Conduct
- I agree to follow this project's Code of Conduct
Activity
Doris-Breakwater commentedon Nov 5, 2026
Initial triage: high-confidence snapshot correctness defect supported by the pinned source, with a credible join-IVM consequence. The SQL reproduction and an IVM refresh have not been executed in this investigation, so the predicted missing rows remain to be confirmed at runtime. This assessment is for
72e91896bbdd8760f8bd4e488e5672406b40ce52; it does not establish affected release branches.Verified code path
MIN_DELTA, independently of the stream's configured type. It unions a historical-TSO-bounded base scan with changelog before images. The changelog branch retains onlyDELETEandUPDATE_BEFORE. See snapshot reconstruction and operation filter.UPDATE_BEFORE_AFTER, the reader emits neither row when the first before image equals the final after image. This happens before the snapshot's operation filter can recover anything.DELETE → APPENDalso maps toUPDATE_BEFORE_AFTER, so the delete/reinsert scenario is supported by the same mechanism. See suppression and transition matrix.The contract mismatch is that zero net change does not imply that no before image is needed to reconstruct the offset snapshot. This also covers a value round trip such as
a → b → a, not only an immediately identical upsert. Existing reader tests intentionally expect such net-zero chains to disappear from ordinary MIN_DELTA output; those tests were inspected, not run. The equality check conservatively retains updates for unavailable/all-NULL before images or unsupported comparisons, so the omission is conditional; the supplied non-NULL VARCHAR example fits the supported comparison case.IVM impact: supported call path, unverified outcome
The join handler uses the other side's pre-snapshot for a left-side delta. A focused regression should establish a baseline, rewrite a matching right-side MoW row identically, and insert a matching left-side row before incremental refresh. In the contribution
ΔL JOIN R_pre, the missing right-side snapshot row can suppress the new match, whileΔRis empty. An identical rewrite alone does not demonstrate an incorrect MV refresh. Capture the actual rewritten plan to confirm which side supplies the pre-snapshot.Missing evidence and next steps
SHOW CREATE TABLE,SHOW CREATE STREAM, stream consumption state before/after, andEXPLAIN VERBOSEfor the snapshot. The issue already supplies a useful version and minimal SQL; the main gap is executed evidence. Query IDs and relevant FE/BE logs are useful if execution diverges from this path.DETAILplus the existing operation filter is insufficient:a → b → aexposes intermediate before images and can add extra rows unless reconstruction selects the correct first image per key. Changing the stream type also does not bypass the hard-coded snapshot scan type.enable_materialized_view_rewrite=falseandenable_dml_materialized_view_rewrite=false. Confirm incremental execution rather than a complete-refresh fallback.Suggested routing: Table Stream/Nereids planning and BE changelog reader maintainers, with IVM maintainers reviewing the integration regression. No code changes were made.
Breakwater-GitHub-Analysis-Slot: slot_9bbb44509cce