Skip to content

[Bug] MIN_DELTA stream returns the wrong value after UPDATE followed by DELETE #65800

Closed

Description

@yujun777

Description

For a Merge-on-Write (MOW) unique-key table, a MIN_DELTA stream incorrectly returns the value from the DELETE operation when an update is followed by a delete on the same row.

MIN_DELTA should merge the sequence below into one DELETE operation using the original value from UPDATE_BEFORE.

Reproduction

CREATE TABLE mow_min_delta_bug (
    id BIGINT,
    v1 INT
) ENGINE=OLAP
UNIQUE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES (
    "replication_num" = "1",
    "enable_unique_key_merge_on_write" = "true",
    "binlog.enable" = "true",
    "binlog.format" = "ROW",
    "binlog.need_historical_value" = "true"
);

INSERT INTO mow_min_delta_bug VALUES (1, 10);

CREATE STREAM mow_min_delta_bug_stream
ON TABLE mow_min_delta_bug
PROPERTIES (
    "type" = "min_delta",
    "show_initial_rows" = "false"
);

INSERT INTO mow_min_delta_bug VALUES (1, 11);
DELETE FROM mow_min_delta_bug WHERE id = 1;

SELECT id, v1, __DORIS_STREAM_CHANGE_TYPE_COL__
FROM mow_min_delta_bug_stream;

The row-level operations are:

UPDATE_BEFORE(10), UPDATE_AFTER(11), DELETE(11)

Actual behavior

The stream returns:

id  v1  change_type
1   11  DELETE

Expected behavior

The stream should return:

id  v1  change_type
1   10  DELETE

When UPDATE and DELETE are merged into a single DELETE, the delete row should preserve the old value from UPDATE_BEFORE, not the updated value from UPDATE_AFTER or DELETE.

Related issues

Related to #65265 and #65418.

Activity

  1. zclllyybb commented on Jul 20, 2026

    @zclllyybb
    Contributor

    Breakwater-GitHub-Analysis-Slot: slot_7ebce944bc43
    This content is generated by AI for reference only.

    Initial code-level analysis:

    The report looks valid against current upstream/master (91a12e13170268812c0002e653b408eca002ca3f). The MIN_DELTA transition itself already classifies UPDATE -> DELETE as a single DELETE in be/src/storage/iterator/binlog_block_reader_utils.h, so the likely problem is not the transition matrix.

    The suspicious part is row materialization in BlockReader::_min_delta_next_block() (be/src/storage/iterator/block_reader.cpp). In the ResultType::DELETE branch, the output operation is correctly changed to STREAM_CHANGE_DELETE, but all non-op payload columns are copied from _stored_data_columns[idx] at group_size - 1, i.e. from the last row in the same-key change group. For the sequence in this issue, that last row is the physical DELETE row after the update, so the emitted value becomes 11.

    For MIN_DELTA semantics, a folded DELETE should describe removal of the row that existed at the stream window start. In the UPDATE -> DELETE case, that value is available from the first UPDATE row's __BEFORE__ columns, not from the later DELETE row. This also matches the FE time-travel/table-stream comments in BindRelation.java, which assume UPDATE_BEFORE and DELETE rows carry the before image.

    One more useful signal: current regression coverage appears to encode the same wrong behavior. regression-test/suites/table_stream_p0/test_min_delta_stream.groovy has an UPDATE then DELETE case and currently asserts the updated value (101) for the folded DELETE, while its comment says the value should use the before snapshot. That test should be corrected to assert the pre-window value.

    Suggested next steps:

    1. Fix the ResultType::DELETE materialization path in BlockReader::_min_delta_next_block() so value columns for an UPDATE -> DELETE group are read from the first row's before-image columns. Keep the stream sequence/binlog metadata behavior reviewed separately, because consumers may still expect the folded event to advance to the latest operation in the group.
    2. Update/add regression coverage with the exact case from this issue: initial (1, 10), then update to 11, then delete, and assert DELETE(10).
    3. Review the adjacent folded-delete cases (DELETE -> DELETE, multi-update followed by delete, cross-rowset same-key grouping) so pure delete still emits the deleted row value and update-delete emits the original pre-window value.

    No runtime profile is needed for this report. The only missing public metadata is the exact Doris build/commit where the reporter reproduced it, but the issue is already reproducible from current upstream code inspection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions