Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 35 additions & 35 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 32 additions & 2 deletions resources/Table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1733,6 +1733,32 @@ export function makeTable(options) {
// of the updates to the record to ensure consistency across the cluster
// TODO: can the previous version be older, but even more previous version be newer?
if (audit) {
// A re-delivered out-of-order write (full-copy audit-replay re-delivers writes) must not have
// its commutative ops re-folded. additionalAuditRefs is the record's own list of folded
// out-of-order versions, read with read-your-writes consistency, so this skips the duplicate up
// front — before the audit-log walk below, which can miss it: the walk stops at the depth cap, or
// breaks early on a not-yet-visible audit entry, before reaching txnTime, and the keyed
// transaction-log lookup it would otherwise use can lag a back-to-back re-delivery (that lag
// silently double-applied the increment — #1137). This covers the re-delivery while the ref is
// still on the record; a later in-order write rewrites the record and drops the ref (it survives
// only as previousAdditionalAuditRefs on the audit log), so that case falls back to the
// best-effort keyed lookup in the capped block below — see #1148. precedesExistingVersion(...)
// === 0 is the identity tie: same version AND same node (the local node is id 0, so an undefined
// options?.nodeId resolves to the same 0 the ref stored).
if (
existingEntry.additionalAuditRefs?.some(
(ref) =>
ref.version === txnTime &&
precedesExistingVersion(
txnTime,
{ version: txnTime, localTime: txnTime, key: id, nodeId: ref.nodeId },
options?.nodeId
) === 0
)
) {
write.skipped = true;
return; // out-of-order write already folded into this record
}
// incremental CRDT updates are only available with audit logging on
let localTime = existingEntry.localTime;
let auditedVersion = existingEntry.version;
Expand Down Expand Up @@ -1864,8 +1890,12 @@ export function makeTable(options) {
// retained window are not layered in — but the authoritative full-copy record restores exact
// convergence. Because we stopped before reaching txnTime, the inline duplicate detection in
// the walk never ran; full-copy audit-replay re-delivers writes, and re-applying one would
// double-apply its commutative ops, so rule that out here with a single O(1) lookup at txnTime
// (RocksDB audit logs are keyed by version, and the cap is RocksDB-only).
// double-apply its commutative ops. A re-delivered out-of-order write is already ruled out by
// the additionalAuditRefs check at the top of this block; this keyed lookup is the best-effort
// guard for the remaining case — a re-delivered write that was originally in-order (so it left
// no ref) and is now deeper than the cap. It is best-effort because the transaction-log lookup
// can intermittently miss an entry under load (tracked separately); the authoritative full-copy
// record still restores exact convergence.
logger.warn?.(
'Out-of-order audit reconciliation exceeded depth cap; reconciling against most recent updates only',
{
Expand Down
Loading