Skip to content

cluster/rm_stm: preserve open transaction producers in local snapshots - #30003

Merged
ballard26 merged 1 commit into
redpanda-data:devfrom
ballard26:rm-stm-1
Mar 31, 2026
Merged

cluster/rm_stm: preserve open transaction producers in local snapshots#30003
ballard26 merged 1 commit into
redpanda-data:devfrom
ballard26:rm-stm-1

Conversation

@ballard26

@ballard26 ballard26 commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Fixes a bug in rm_stm::do_take_local_snapshot where transactional producers with open transactions but no data batches are silently dropped from local snapshots. On restart, the fence batch (which carries tx_seq, timeout, and coordinator_partition) is behind the snapshot offset and not replayed. Data batches replayed after the snapshot synthesize transaction state with tx_seq{-1} and timeout=nullopt, making the transaction impossible to commit, abort, or auto-expire, permanently stalling LSO on the partition.

Steps to reproduce

  1. Begin a transaction to ensure a fence batch is replicated
  2. Take a local snaphot before any data batches are replicated. The producer has finished_requests empty, so it's dropped from the snapshot.
  3. Add data batches to the transaction.
  4. Restart a node that leads some partition in the transaction. This will then load the snapshot, replay the log from snapshot_offset + 1 (fence is not replayed). The data batches will be replayed and apply_data will synthesizes transaction_state with tx_seq{-1} and timeout=nullopt.
  5. Ensure the restarted node still leads some partition in the transaction.
  6. The coordinator then could send a commit_tx with tx_seq=0(or whatever the actual seq was) and the rm_stm for the partition will reject it(-1 != 0). It could also send a abort_tx with tx_seq=0 and the rm_stm for the partition will reject it as well(-1 < 0 interpreted as "from the future")
  7. Auto-abort timer checks ms_since_last_update() > timeout_ms(), timeout_ms() returns max() due to nullopt so it'll never expire.

All of this will result in the LSO for the partition becoming permanently stuck.

Backports Required

  • none - not a bug fix
  • none - this is a backport
  • none - issue does not exist in previous branches
  • none - papercut/not impactful enough to backport
  • v26.1.x
  • v25.3.x
  • v25.2.x

Release Notes

  • none

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an rm_stm local-snapshot correctness bug where transactional producers with an open transaction but no finished requests yet could be dropped from the snapshot, causing restart-time log replay to synthesize incomplete transaction state and permanently stall LSO.

Changes:

  • Preserve producers with has_transaction_in_progress() in rm_stm::do_take_local_snapshot.
  • Add a regression test that snapshots after tx fences but before data batches, then simulates restart and verifies commit/abort work.
  • Add a test-fixture helper to replay-apply record batches.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/v/cluster/rm_stm.cc Includes open-transaction producers in local snapshots to prevent losing tx metadata across restarts.
src/v/cluster/tests/rm_stm_tests.cc Adds restart/regression test for open-tx producer preservation and post-restart commit/abort behavior.
src/v/cluster/tests/rm_stm_test_fixture.h Exposes a helper for applying batches during simulated replay in tests.

Comment thread src/v/cluster/tests/rm_stm_test_fixture.h Outdated
Comment thread src/v/cluster/tests/rm_stm_tests.cc Outdated
Comment thread src/v/cluster/tests/rm_stm_tests.cc Outdated

@bharathv bharathv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nice find.

Comment thread src/v/cluster/tests/rm_stm_tests.cc Outdated
Comment on lines +1237 to +1248
apply_snapshot(snapshot.header, std::move(snapshot.data)).get();
auto rdr = _raft->log()
->make_reader(
storage::local_log_reader_config(
model::next_offset(snap_offset), model::offset::max()))
.get();
auto batches = model::consume_reader_to_memory(
std::move(rdr), model::no_timeout)
.get();
for (const auto& batch : batches) {
replay_apply(batch).get();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

wondering if we can have a restart() helper in the stm and rm_stm fixtures.
this seems a bit hacky.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point, will switch to using _stm->{start, stop}() in a restart helper for the fixture as discussed.

@vbotbuildovich

Copy link
Copy Markdown
Collaborator

Retry command for Build#82511

please wait until all jobs are finished before running the slash command

/ci-repeat 1
skip-redpanda-build
skip-units
skip-rebase
tests/rptest/tests/simple_e2e_test.py::SimpleEndToEndTest.test_relaxed_acks@{"write_caching":false}

@ballard26
ballard26 enabled auto-merge March 31, 2026 00:21
@vbotbuildovich

Copy link
Copy Markdown
Collaborator

Retry command for Build#82515

please wait until all jobs are finished before running the slash command

/ci-repeat 1
skip-redpanda-build
skip-units
skip-rebase
tests/rptest/tests/compatibility/java_compression_test.py::JavaCompressionTest.test_upgrade_java_compression@{"compression_type":"snappy"}

@vbotbuildovich

vbotbuildovich commented Mar 31, 2026

Copy link
Copy Markdown
Collaborator

CI test results

test results on build#82515
test_class test_method test_arguments test_kind job_url test_status passed reason test_history
JavaCompressionTest test_upgrade_java_compression {"compression_type": "gzip"} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d413f-2a73-4822-80dc-702fe199fe5d FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=JavaCompressionTest&test_method=test_upgrade_java_compression
JavaCompressionTest test_upgrade_java_compression {"compression_type": "gzip"} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d4141-2774-4ade-904a-61df0198a3e4 FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=JavaCompressionTest&test_method=test_upgrade_java_compression
JavaCompressionTest test_upgrade_java_compression {"compression_type": "lz4"} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d413f-2a74-45ee-8a6f-6c5c74223b4e FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=JavaCompressionTest&test_method=test_upgrade_java_compression
JavaCompressionTest test_upgrade_java_compression {"compression_type": "lz4"} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d4141-2774-41e0-9678-f5a598142f15 FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=JavaCompressionTest&test_method=test_upgrade_java_compression
JavaCompressionTest test_upgrade_java_compression {"compression_type": "snappy"} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d413f-2a74-46f6-a4e0-f4883f7a1902 FLAKY 9/11 Test FAILS after retries.Significant increase in flaky rate(baseline=0.0000, p0=0.0000, reject_threshold=0.0100) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=JavaCompressionTest&test_method=test_upgrade_java_compression
JavaCompressionTest test_upgrade_java_compression {"compression_type": "snappy"} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d4141-2774-4baa-90e1-6714d8bad282 FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=JavaCompressionTest&test_method=test_upgrade_java_compression
JavaCompressionTest test_upgrade_java_compression {"compression_type": "zstd"} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d413f-2a6f-4f73-a602-790cd1488702 FAIL 0/1 https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=JavaCompressionTest&test_method=test_upgrade_java_compression
JavaCompressionTest test_upgrade_java_compression {"compression_type": "zstd"} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d4141-2771-4832-b58c-40097b7b0326 FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=JavaCompressionTest&test_method=test_upgrade_java_compression
LogCompactionTxRemovalUpgradeFrom25_2_Test test_tx_control_batch_removal_with_upgrade null integration https://buildkite.com/redpanda/redpanda/builds/82515#019d413f-2a74-45ee-8a6f-6c5c74223b4e FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=LogCompactionTxRemovalUpgradeFrom25_2_Test&test_method=test_tx_control_batch_removal_with_upgrade
LogCompactionTxRemovalUpgradeFrom25_2_Test test_tx_control_batch_removal_with_upgrade null integration https://buildkite.com/redpanda/redpanda/builds/82515#019d4141-2774-41e0-9678-f5a598142f15 FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=LogCompactionTxRemovalUpgradeFrom25_2_Test&test_method=test_tx_control_batch_removal_with_upgrade
LogCompactionTxRemovalUpgradeFrom25_2_Test test_tx_control_batch_removal_with_upgrade_and_recovery null integration https://buildkite.com/redpanda/redpanda/builds/82515#019d413f-2a74-46f6-a4e0-f4883f7a1902 FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=LogCompactionTxRemovalUpgradeFrom25_2_Test&test_method=test_tx_control_batch_removal_with_upgrade_and_recovery
LogCompactionTxRemovalUpgradeFrom25_2_Test test_tx_control_batch_removal_with_upgrade_and_recovery null integration https://buildkite.com/redpanda/redpanda/builds/82515#019d4141-2774-4baa-90e1-6714d8bad282 FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=LogCompactionTxRemovalUpgradeFrom25_2_Test&test_method=test_tx_control_batch_removal_with_upgrade_and_recovery
LogCompactionTxRemovalUpgradeFrom25_3_1_Test test_tx_control_batch_removal_with_upgrade null integration https://buildkite.com/redpanda/redpanda/builds/82515#019d413f-2a6f-4f73-a602-790cd1488702 FAIL 0/1 https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=LogCompactionTxRemovalUpgradeFrom25_3_1_Test&test_method=test_tx_control_batch_removal_with_upgrade
LogCompactionTxRemovalUpgradeFrom25_3_1_Test test_tx_control_batch_removal_with_upgrade null integration https://buildkite.com/redpanda/redpanda/builds/82515#019d4141-2771-4832-b58c-40097b7b0326 FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=LogCompactionTxRemovalUpgradeFrom25_3_1_Test&test_method=test_tx_control_batch_removal_with_upgrade
LogCompactionTxRemovalUpgradeFrom25_3_1_Test test_tx_control_batch_removal_with_upgrade_and_recovery null integration https://buildkite.com/redpanda/redpanda/builds/82515#019d413f-2a6f-47e6-926b-54a0895286fe FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0085, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=LogCompactionTxRemovalUpgradeFrom25_3_1_Test&test_method=test_tx_control_batch_removal_with_upgrade_and_recovery
LogCompactionTxRemovalUpgradeFrom25_3_1_Test test_tx_control_batch_removal_with_upgrade_and_recovery null integration https://buildkite.com/redpanda/redpanda/builds/82515#019d4141-2772-4d57-a702-a28ddf5946ee FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0085, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=LogCompactionTxRemovalUpgradeFrom25_3_1_Test&test_method=test_tx_control_batch_removal_with_upgrade_and_recovery
BogusTimestampTest test_bogus_timestamps {"mixed_timestamps": false, "use_broker_timestamps": false} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d413f-2a72-45fe-9056-07e651d3827a FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=BogusTimestampTest&test_method=test_bogus_timestamps
BogusTimestampTest test_bogus_timestamps {"mixed_timestamps": false, "use_broker_timestamps": false} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d4141-2773-40a4-9888-f6e68aa13984 FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=BogusTimestampTest&test_method=test_bogus_timestamps
BogusTimestampTest test_bogus_timestamps {"mixed_timestamps": false, "use_broker_timestamps": true} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d413f-2a73-42fa-99d3-0a3591fb3dcd FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=BogusTimestampTest&test_method=test_bogus_timestamps
BogusTimestampTest test_bogus_timestamps {"mixed_timestamps": false, "use_broker_timestamps": true} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d4141-2773-4e47-84b8-20b3fec74358 FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=BogusTimestampTest&test_method=test_bogus_timestamps
BogusTimestampTest test_bogus_timestamps {"mixed_timestamps": true, "use_broker_timestamps": false} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d413f-2a73-4822-80dc-702fe199fe5d FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=BogusTimestampTest&test_method=test_bogus_timestamps
BogusTimestampTest test_bogus_timestamps {"mixed_timestamps": true, "use_broker_timestamps": false} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d4141-2774-4ade-904a-61df0198a3e4 FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=BogusTimestampTest&test_method=test_bogus_timestamps
BogusTimestampTest test_bogus_timestamps {"mixed_timestamps": true, "use_broker_timestamps": true} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d413f-2a74-45ee-8a6f-6c5c74223b4e FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=BogusTimestampTest&test_method=test_bogus_timestamps
BogusTimestampTest test_bogus_timestamps {"mixed_timestamps": true, "use_broker_timestamps": true} integration https://buildkite.com/redpanda/redpanda/builds/82515#019d4141-2774-41e0-9678-f5a598142f15 FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0000, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=BogusTimestampTest&test_method=test_bogus_timestamps
test results on build#82561
test_class test_method test_arguments test_kind job_url test_status passed reason test_history
TxAtomicProduceConsumeTest test_basic_tx_consumer_transform_produce {"with_failures": true} integration https://buildkite.com/redpanda/redpanda/builds/82561#019d450b-099b-4be6-a4cc-22fe293545ed FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0071, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=TxAtomicProduceConsumeTest&test_method=test_basic_tx_consumer_transform_produce

@WillemKauf

Copy link
Copy Markdown
Contributor

test failures are unrelated, trying to fetch tagged release of v26.1.1 in JavaCompressionTest. Should be force mergeable with replication approvals IMO

do_take_local_snapshot filters producers by finished_requests, which
drops transactional producers that have begun (fence batch applied) but
not yet replicated data batches. On restart, the snapshot is loaded and
log replay starts from the snapshot offset skipping the fence batch.
Data batches replayed without the fence synthesize transaction state
with tx_seq{-1} and timeout=nullopt. This makes the transaction
impossible to commit (tx_seq mismatch), abort (tx_seq seen as from the
future), or auto-expire (timeout=max), permanently stalling LSO on the
partition.

Include producers with in-progress transactions in local snapshots
regardless of finished_requests so their tx_seq, timeout, and
coordinator partition survive the snapshot roundtrip.
@ballard26
ballard26 merged commit f3ff93b into redpanda-data:dev Mar 31, 2026
19 checks passed
@vbotbuildovich

Copy link
Copy Markdown
Collaborator

/backport v26.1.x

@vbotbuildovich

Copy link
Copy Markdown
Collaborator

/backport v25.3.x

@vbotbuildovich

Copy link
Copy Markdown
Collaborator

/backport v25.2.x

@vbotbuildovich

Copy link
Copy Markdown
Collaborator

Failed to create a backport PR to v25.3.x branch. I tried:

git remote add upstream https://github.com/redpanda-data/redpanda.git
git fetch --all
git checkout -b backport-pr-30003-v25.3.x-967 remotes/upstream/v25.3.x
git cherry-pick -x a697aa800f

Workflow run logs.

@vbotbuildovich

Copy link
Copy Markdown
Collaborator

Failed to create a backport PR to v25.2.x branch. I tried:

git remote add upstream https://github.com/redpanda-data/redpanda.git
git fetch --all
git checkout -b backport-pr-30003-v25.2.x-274 remotes/upstream/v25.2.x
git cherry-pick -x a697aa800f

Workflow run logs.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants