September 30, 2019·4 min read·engineeringcockroachdbtransactionscorrectness

CockroachDB 19.1.5 repairs reads around unresolved write intents

CockroachDB's maintenance update fixes cases involving transaction restarts and unresolved intents in a read's uncertainty interval. It also bounds memory use in SQL range deletion.

An unresolved write intent does not mean that its transaction is uncommitted. CockroachDB 19.1.5 includes a read-correctness fix for cases where treating that intent as irrelevant could produce a stale read. The distinction matters when a later transaction depends on an earlier transaction whose intents have not yet been resolved.

Nathan VanBenschoten's uncertainty-interval repair is part of the changes delivered in 19.1.5. It makes a scan treat intents within its uncertainty interval as uncertain, instead of disregarding them because their timestamps are above the read timestamp.

I value CockroachDB's strong transaction model because it gives application code a defined consistency contract. These fixes concern the implementation of that contract at the versioned storage layer. They deserve more attention than the maintenance-release label alone suggests.

An intent can represent a committed ancestor

The commit explains that a scan previously failed to consider intents in its uncertainty interval. Such an intent could belong to a committed transaction that causally preceded the scan. Unresolved intent state was therefore insufficient evidence that the write could be ignored.

The repaired path returns a WriteIntentError when an intent lies above the scan's read timestamp but below its maximum timestamp. That brings the situation into concurrency control. The database can investigate the conflicting intent instead of silently proceeding as though no relevant value existed.

The commit discusses an alternative of returning a read-within-uncertainty error. It chooses the intent error because resolving the transaction state can avoid moving the read timestamp when the other transaction turns out to have aborted.

This is a specific distinction between uncertain visibility and transaction outcome. A timestamp comparison alone does not tell the scan whether the unresolved write belongs to a committed ancestor. The implementation needs to handle the intent before deciding how the read proceeds.

The author ties the problem to Jepsen multi-register failures and identifies other work still relevant to those failures. The fix should therefore be described by its actual scope. It removes this stale-read path, without establishing that every separately identified test issue is resolved.

Restarts have a different version-selection hazard

A second MVCC repair addresses reading a value from a future timestamp after a transaction restart. The described case involves an intent from the same transaction but an earlier epoch.

The scan could encounter that intent above its read timestamp and then return a committed value that was also above the intended read timestamp. That violates the snapshot requested by the scan, independently of whether the returned value is otherwise valid database data.

The important point is which version is visible to this operation. Retaining historical values gives a database the material needed for timestamped reads, but the selection logic must still exclude versions outside the read's permitted view.

The repair changes both the Go MVCC implementation and the corresponding C++ scan code. Its regression tests exercise the version-selection case directly. This is useful evidence about the intended invariant because the fix follows the read timestamp through more than one implementation path.

This repair and the uncertainty-interval repair concern different mistakes. One could return a value too new for the read. The other could miss a causally relevant committed write represented by an unresolved intent. Both require accurate interpretation of transactional metadata around stored versions.

My preference is to keep these mechanisms distinct when assessing an upgrade. A broad statement that transaction handling improved says little about the affected behavior. These commit descriptions identify the exact relationship between intent state, restart state and read timestamps.

Large deletes need bounded bookkeeping

The same release includes a range-deletion repair. A SQL range delete could send one deletion request per span, with the request returning the keys it deleted so SQL could calculate the affected-row count.

That result bookkeeping could consume unbounded memory. The fix sends requests with a returned-key limit and repeats them until the span has been processed. The SQL operation still needs the count, but it no longer needs one arbitrarily large returned-key result.

This is useful context for an operation that looks compact in SQL. Deleting a range can avoid individual statement submission while still producing internal work proportional to the deleted data. An efficient storage operation needs bounded coordination as well as an efficient way to remove keys.

Other changes in the release include a distributed-SQL version bump for mixed-version clusters performing lookup joins during an index backfill. The release also repairs arithmetic constant folding where unary negatives could be ignored. These are separate correctness and compatibility concerns, alongside the storage-level read fixes. Version 19.1.5's substantive contribution includes restoring the intended visibility rules around unresolved intents and transaction restarts, while bounding memory consumed by range-deletion results.