Skip to content

RocksDB eviction removes a resequenced record that reuses the version its caller read #3384

Closed

Description

@kriszyp

What happens

Eviction on RocksDB decides that a record is unchanged by comparing versions. A resequenced write breaks that check. A write whose timestamp is not newer than the stored version merges onto the record, keeps the stored version and sets VERSION_REUSED (RecordEncoder.ts#L127-L131, #L933-L934). One version then names two different stored values.

Sequence (source-derived, not reproduced):

  1. A caller reads record value R1 at version V. This can be the read-path expiry check, record-expiration eviction, or the cleanup scan.
  2. A resequenced write commits R2 at the same V, flagged VERSION_REUSED.
  3. The caller calls Table.evict(id, R1, V). The check entry.version !== existingVersion passes (Table.ts#L4089), and evict removes the live R2.

The eviction batcher's stageInto has the same equality check (Table.ts#L9961).

On e187b83d2, evict also passed the caller's R1 to updateIndices, which left R2's index keys dangling. The fix for #2861's evict leg switches index cleanup to the entry read in the eviction's own transaction, as the batcher already does. The removal of R2 itself is what remains.

Likely fix

Treat a VERSION_REUSED entry as "version proves nothing" in evict() and stageInto. For example, evict it only when the live entry's own expiresAt has passed, and otherwise leave it for the next scan. Priority (early) eviction would then skip reused versions. That is acceptable for a rare flag, but it should be a stated choice.

Not proven

  • Not reproduced. Building it needs an out-of-order write onto a record that a pending eviction has already read.
  • Whether the merged R2 keeps R1's expiresAt has not been checked. If it does, removing R2 matches TTL semantics, and the impact shrinks to the cache-table case where R2 carried newer data.

Review-Deferral: 8c7a0517e9e70382 severity=minor category=data-integrity

🤖 Generated by Claude Code (Claude Opus); posted via @kriszyp.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Fields

Priority

P2

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions