Skip to content

KAFKA-20749: Stop offset writes leaking into data iteration#22710

Merged
bbejeck merged 2 commits into
apache:trunkfrom
nicktelford:KAFKA-20749-fix-offset-cf-leak
Jun 30, 2026
Merged

KAFKA-20749: Stop offset writes leaking into data iteration#22710
bbejeck merged 2 commits into
apache:trunkfrom
nicktelford:KAFKA-20749-fix-offset-cf-leak

Conversation

@nicktelford

@nicktelford nicktelford commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

A KStream-KStream OUTER join crashes with a SerializationException
under exactly_once_v2 when enable.transactional.statestores=true. As
soon as the join emits a non-joined (outer) record, the StreamThread
dies and the outer result is never produced.

RocksDBTransactionBuffer stages every write into a single in-memory
pendingWrites map regardless of which column family the write targets.
Writes to the offsets column family (the persisted Position, written
under the "position" key) therefore landed in the same map as data-CF
writes, so subsequent data-CF range()/all()/prefixScan() scans
surfaced the offsets entry and tried to deserialize the serialized
Position as a data record. The outer-join ListValueStore could not
deserialize it as a value list and threw. The get() path was already
gated against the offsets CF; the iterator paths were not.
Non-transactional stores write straight to the offsets CF and never leak
through the buffer, which is why this is transactional-only.

The fix stages a write into pendingWrites only when its column family
is the data column family (cfHandle). Offset-CF writes still go to the
underlying writeBatch so they commit atomically with the data, but
they no longer pollute data-CF iteration.

A RocksDBStoreTest regression test asserts that
all/range/prefixScan return only the data key after
writePosition(), and a new integration test reproduces the original
outer-join crash end to end (parameterized over
enable.transactional.statestores, so the non-transactional case acts
as a passing control).

The tests in this PR originated from @bbejeck's #22709, which fixes the
same bug via a separate stageOffsetWrite path; this PR takes a smaller
approach that guards the single stage entry point and needs no changes
to RocksDBStore.

🤖 Generated with Claude Code

Reviewers: Bill Bejeck bbejeck@apache.org

A KStream-KStream OUTER join crashes with a SerializationException
under exactly_once_v2 when enable.transactional.statestores=true. As
soon as the join emits a non-joined (outer) record, the StreamThread
dies and the outer result is never produced.

RocksDBTransactionBuffer stages every write into a single in-memory
pendingWrites map regardless of which column family the write targets.
Writes to the offsets column family (the persisted Position) therefore
landed in the same map as data-CF writes, so subsequent data-CF scans
surfaced the offsets "position" entry and tried to deserialize it as a
data record, throwing a SerializationException.

Stage a write into pendingWrites only when its column family is the
data column family (cfHandle); offset-CF writes still go to the
underlying writeBatch but no longer pollute data-CF iteration. A
RocksDBStoreTest regression test asserts that all/range/prefixScan
return only the data key after writePosition(), and an integration
test reproduces the original outer-join crash end to end.

Co-Authored-By: Bill Bejeck <bbejeck@apache.org>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added triage PRs from the community streams labels Jun 30, 2026
@bbejeck

bbejeck commented Jun 30, 2026

Copy link
Copy Markdown
Member

Thanks @nicktelford the shouldStageWriteToOtherCF and shouldStageDeleteToOtherCF tests in the RocksDBTransactionBufferTest are gonna fail

stage(cf, key, value) no longer mirrors non-primary-CF writes into
pendingWrites, so buffer.get() correctly returns null for keys that
were only staged to a secondary column family.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@nicktelford

Copy link
Copy Markdown
Contributor Author

Thanks @nicktelford the shouldStageWriteToOtherCF and shouldStageDeleteToOtherCF tests in the RocksDBTransactionBufferTest are gonna fail

@bbejeck Good catch. Fixed!

@bbejeck bbejeck left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @nicktelford LGTM

@bbejeck
bbejeck merged commit 2a0efd0 into apache:trunk Jun 30, 2026
22 checks passed
@bbejeck

bbejeck commented Jun 30, 2026

Copy link
Copy Markdown
Member

Merged #22710 into trunk

@github-actions github-actions Bot removed the triage PRs from the community label Jul 1, 2026
@nicktelford
nicktelford deleted the KAFKA-20749-fix-offset-cf-leak branch July 1, 2026 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants