Repository navigation
Never abort a transaction on retry once its entry has been written to the transaction log #3236
Description
Problem
When a RocksDB transaction stages an entry in RocksTransactionLogStore (audit: true tables), the first native transaction.commit() attempt in rocksdb-js (Transaction::Commit) flushes the staged transaction-log batch to disk (executeLogWork, setting committedPosition) before txn->Commit() validates optimistic key conflicts on the primary RocksDB column family.
When txn->Commit() encounters a conflict (RETRY_NOW_VALUE or ERR_BUSY / ERR_TRY_AGAIN), rocksdb-js resets the native transaction onto a fresh snapshot while preserving committedPosition > 0 so the transaction log is only written once (rocksdb-js#668).
However, if DatabaseTransaction subsequently aborts that transaction instead of continuing to retry until the RocksDB commit succeeds:
rocksdb-jsthrowsERR_TRANSACTION_ABANDONED(Abandoning transaction after transaction log was already written).- The durable transaction-log entry remains on disk and is delivered to subscribers / replication peers (and replayed on restart by
replayLogs()), while the local RocksDB store rolls back the write—causing permanent divergence between the local database, its transaction log, and its replication peers.
Abort-after-log paths in resources/DatabaseTransaction.ts
- Request-path retry cap (
retries > MAX_RETRIES) on both coordinated (RETRY_NOW_VALUE) and uncoordinated (ERR_BUSY/ERR_TRY_AGAIN) conflict-retry paths callsabortChainAfterRetries(transaction)and throwsServerErroreven whentransaction(or an upstream link in a chained commit) already wrote its log entry. - Commit-conflict deadline (
elapsedPastCommitBudget()) on bothRETRY_NOW_VALUEandERR_BUSY/ERR_TRY_AGAINcallsabandonCommitAfterDeadline(), which aborts the transaction chain and throwsTransactionCommitConflictTimeoutErrorafter the log entry is already on disk. - Record-lock lease expiry fence on retry re-save (
save()): when a conflict retry re-entersperformCommit()and re-runssave(),lockHandle?.isLeaseExpired()throws a 409 lock-lease error andperformCommit()aborts the transaction chain—even though the first nativecommit()attempt already wrote the entry to the transaction log. (By contrast, a pre-first-submit re-save forced by an open read iterator has not yet called nativecommit()or written to the log, and must continue to enforce the lease fence and abort.) - Pre-submit checks re-evaluated on retry re-entry into
performCommit()(completionsclient-disconnect/timeout check,assertDatabaseCommitAllowed(), and chainednextTransactionfailures) can abort a transaction chain after a transaction in the chain has already written its durable log entry.
Expected Behavior & Design Decision (@kriszyp)
Enforce the invariant in resources/DatabaseTransaction.ts:
- Before the first transaction-log write (
committedPosition/hasStagedLognot yet flushed on any link in the transaction chain), aborting remains allowed as today. - Once a transaction's entry has been durably written to the transaction log (determined from durable-log state, including any logged link in a chained transaction), the transaction is carried out unconditionally: keep the request awaiting the commit until it lands, retrying without a cap or conflict-budget abort, matching
sourceApplytransactions (deliberately without detached background-commit ownership or a 503-pending response, even thoughlogging.auditLogdefaults totrueso the Outstanding-commit wedge recurs on 5.2.6/5.2.7: native commits never settle and checkOverloaded 503s every application write on the thread until restart #2450 deadline and 40-attempt cap apply only before the first log flush or onaudit: falsetables). - Native
rocksdb-jschanges (validating conflicts beforeexecuteLogWorkso a conflicting first attempt does not flush the log batch, exposingcommittedPosition > 0to JS, and refusing/reconciling post-log aborts) are scoped to follow-up issue Hardening native rocksdb-js transaction log durability and retry-round drain tracking (follow-up to #3236) #3253.
🤖 Generated by Google Gemini; posted via @kriszyp.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
Never abort a transaction once durably written to the transaction log
fix(resources): isolate per-handle ImmediateTransaction state and cov…
Keep clearWrites as single owner of expired deferred write cleanup (#…
Never abort a transaction once durably written to the transaction log
fix(resources): isolate per-handle ImmediateTransaction state and cov…
Keep clearWrites as single owner of expired deferred write cleanup (#…
Never abort a transaction once durably written to the transaction log
fix(resources): isolate per-handle ImmediateTransaction state and cov…
Keep clearWrites as single owner of expired deferred write cleanup (#…