Skip to content

Retry transaction metadata writes under noexcept callbacks instead of terminating - #2396

Open
filimonov wants to merge 2 commits into
antalya-26.6from
fix/antalya-26.6/transaction-metadata-store-retry-squashed
Open

filimonov wants to merge 2 commits into
antalya-26.6from
fix/antalya-26.6/transaction-metadata-store-retry-squashed

Conversation

@filimonov

Copy link
Copy Markdown
Member

MergeTreeTransaction::afterCommit and rollback are noexcept and write part version metadata and mutation CSNs to disk. A storage error in one of these writes escaped and terminated the server, although the transaction was already committed (or rolled back) in the transaction log and a restart repairs the files from it. This is what killed the server in the CAS test runs of the linked issue.

The six writes now go through one helper, retryMetadataStore: the error is retried with backoff for up to 60 seconds per object; LOGICAL_ERROR and NOT_IMPLEMENTED are rethrown at once; an exhausted budget, or a server shutdown, rethrows as before, so a lost write is never hidden. MergeTreeMutationEntry::writeCSN rewrites the whole mutation record through a temporary file, so a retry is idempotent. setMutationCSN logs a warning instead of throwing LOGICAL_ERROR when KILL MUTATION erased the entry during the commit window. Two ONCE failpoints and the stateless test 05053_transaction_metadata_store_retry cover the commit of parts, the commit of a mutation and a rollback.

The change is generic MergeTree code and is meant to be cherry-picked to upstream unchanged.

Closes: #2344

Changelog category (leave one):

  • Critical Bug Fix (crash, data loss, RBAC)

Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):

Fixed a server termination when a disk write of transaction metadata (part CSN or mutation CSN) failed inside the commit or rollback of a MergeTree transaction; such writes are now retried for a bounded time. Also fixed a termination when KILL MUTATION raced with the commit of a transactional mutation.

Documentation entry for user-facing changes

  • Documentation is not needed (no user-visible interface changes)

CI/CD Options

Exclude tests:

  • Fast test
  • Integration Tests
  • Stateless tests
  • Stateful tests
  • Unit tests
  • Performance tests
  • Aarch64 tests
  • All with ASAN
  • All with TSAN
  • All with MSAN
  • All with UBSAN
  • All with Coverage
  • All Regression
  • Disable CI Cache

Regression jobs to run:

  • Fast suites (mostly <1h)
  • Aggregate Functions (2h)
  • Alter (1.5h)
  • Benchmark (30m)
  • CAS (content-addressed storage; Antalya only)
  • ClickHouse Keeper (1h)
  • Iceberg (2h)
  • LDAP (1h)
  • OAuth (5m)
  • Parquet (1.5h)
  • RBAC (1.5h)
  • SSL Server (1h)
  • S3 (2h)
  • S3 Export (2h)
  • Swarms (30m)
  • Tiered Storage (2h)

🤖 Generated with Claude Code

https://claude.ai/code/session_01GhVd7eMAWdFubNk4g1B2Tx

… terminating

`MergeTreeTransaction::afterCommit` and `rollback` are `noexcept`. They write
part version metadata (`creation_csn`, `removal_csn`, `removal_tid`) and the
CSN of a mutation to disk. A storage error in one of these writes escaped and
terminated the server, although the transaction was already committed (or
rolled back) in the transaction log and a restart repairs the files from it.

The six writes now go through `retryMetadataStore`: the error is retried
with backoff for up to 60 seconds per object; `LOGICAL_ERROR` and
`NOT_IMPLEMENTED` are rethrown at once; an exhausted budget, or a server
shutdown, rethrows as before, so a lost write is never hidden.

`MergeTreeMutationEntry::writeCSN` used to append one line to the mutation
file; a write that fails half-way, or is repeated, could leave a partial or
duplicated `csn` line, which the loader rejects. The whole record is now
written through a temporary file and replaces the old one, so a retry is
idempotent. `loadMutations` tolerates a temporary file that a repair earlier
in the same pass has already consumed.

`KILL MUTATION` between the log write and `afterCommit` erases the mutation
entry and cannot roll the committing transaction back; `setMutationCSN` then
threw `LOGICAL_ERROR` under `noexcept`. It now logs a warning: the parts are
already mutated and there is nothing left to write.

Two `ONCE` failpoints and the stateless test
`05053_transaction_metadata_store_retry` cover the commit of parts, the
commit of a mutation and a rollback.

Closes: #2344

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GhVd7eMAWdFubNk4g1B2Tx
Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
@github-actions

github-actions Bot commented Sep 17, 2026

Copy link
Copy Markdown

Workflow [PR], commit [86539d6]

@filimonov filimonov added the CAS label Sep 17, 2026
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.

CAS: recoverable Code 210 on ref-log abandon escapes into noexcept afterCommit → std::terminate (Server died)

1 participant