March 26, 2018·4 min read·engineeringcockroachdbschemaconcurrency

CockroachDB 1.1.7 fixes two schema change deadlocks

The March maintenance release fixes a lock held during intent resolution and a transaction-state error that blocked schema changes encountering retries.

Schema changes exercise more than the SQL parser. They modify metadata that other database activity needs, and they interact with transaction progress. CockroachDB 1.1.7 fixes two deadlocks at those boundaries. One could occur during rapid table creation and deletion. The other involved schema changes encountering retryable transaction errors.

The March 26 release notes are short, but the underlying patches explain distinct causes. Treating both as a general improvement to stability would miss the useful engineering detail. In one path, work could run synchronously while holding an important mutex. In the other, the executor ran schema work before the transaction had reached the state its cleanup required.

I like CockroachDB's default serializable isolation, while accepting that its distributed coordination has a cost. That makes progress guarantees relevant alongside isolation guarantees. A transaction can preserve a correctness rule and still leave an application waiting indefinitely if an internal dependency prevents it from reaching the next state.

Intent resolution must respect the caller's lock

Ben Darnell's patch in pull request 23957 changes system-configuration loading so that intent resolution cannot run synchronously in that path. The caller holds raftMu for the system-configuration range. The patch explanation says synchronous resolution holds that mutex too long and can produce a deadlock in extreme cases.

The changed call is small, but its significance depends on the caller. A helper capable of both asynchronous and synchronous work has different behavior under load than its name alone suggests. If it is permitted to fall back to synchronous work, its caller needs to be safe while that work runs. Holding a mutex changes the answer.

The patch comment identifies the calling path explicitly and disables synchronous processing. That makes the restriction visible where the helper is invoked. A future maintainer does not have to infer why this particular caller differs from others. The lock already held at entry is part of the operation's precondition.

Rapid creation and deletion of tables provide the user-visible trigger documented by the release. Those operations repeatedly change the system metadata involved. The patch does not justify a claim that all table creation was deadlocked, or that the database needed to remove synchronization generally. It changes one interaction between metadata loading and work performed while the caller holds its lock.

A retry state is not an ended transaction

The second patch, pull request 23981, changes the condition under which the SQL executor runs queued schema changes. Previously, the condition asked whether the transaction was no longer open. The corrected condition requires the explicit NoTxn state. An accompanying comment says this must match the condition used to finish the SQL transaction.

That distinction matters because a transaction waiting for a retry is not simply an active transaction executing ordinary statements. It also is not necessarily finished. A negative test for one state can accidentally include several other states whose cleanup obligations differ. The patch uses the state that corresponds to the required lifecycle point.

Darnell's description identifies a transaction heartbeat loop kept alive while schema-change work ran on the same goroutine. That arrangement could deadlock. Waiting for the correct transaction state aligns schema execution with the cleanup that must precede it. The repair concerns when work is allowed to begin, not whether the requested schema definition is valid.

The regression test reproduces the problem at RELEASE SAVEPOINT cockroach_restart. It creates schema work, introduces a conflicting high-priority transaction and expects a retry error. Previously, the release-savepoint operation could deadlock. After restarting, the test repeats its schema work and completes the transaction.

My interest in that test is the observable distinction it captures. A retryable error is a result a client can handle. An indefinite wait does not give the retry procedure a result on which to act. Preserving isolation under contention requires the error path to make progress as well as the successful path.

The test also completes another attempt after the injected conflict. That demonstrates the intended continuation, so the expected error is connected to a usable recovery path.

The fixes preserve different sequencing rules

The two patches address related symptoms through different sequencing rules. Configuration loading must avoid a synchronous dependency while holding its mutex. Schema execution must wait until the transaction reaches the state that matches completion. Both rules concern the work already in progress when another operation is invoked.

For an operator, this explains why an ordinary SQL migration can encounter an internal concurrency defect without unusual syntax. Rapid metadata changes exercise the first path. A schema transaction that needs a retry exercises the second. Version 1.1.7 repairs those documented cases, giving the client a way to receive the retry result and continue its transaction protocol instead of remaining blocked inside the server.