Skip to content

CASSANDRA-21537: Serialize local metadata proposals so concurrent schema changes do not exhaust their retry budge - #4970

Open
pmcfadin wants to merge 1 commit into
apache:trunkfrom
pmcfadin:pmcfadin/CASSANDRA-21537/trunk
Open

CASSANDRA-21537: Serialize local metadata proposals so concurrent schema changes do not exhaust their retry budge#4970
pmcfadin wants to merge 1 commit into
apache:trunkfrom
pmcfadin:pmcfadin/CASSANDRA-21537/trunk

Conversation

@pmcfadin

Copy link
Copy Markdown
Contributor

Details, reproduction and measurements: CASSANDRA-21537

Concurrent CQL DDL against a CMS member fails with Could not perform commit after 12 attempts. Time remaining: 4231ms — the retry attempt cap is reached while the request deadline still has seconds left. Every concurrent commit reads the same highest consecutive epoch and proposes at epoch+1, so all but one loser per round burns an attempt, backs off, and refetches the log before recomputing.

Fix

  • AbstractLocalProcessor serialises the read → execute → propose cycle for locally originating commits behind a fair ReentrantLock.
  • Acquisition is bounded by the caller's own remaining Retry deadline, so queue time is charged to the waiter and never extends another request.
  • Each loop iteration is one timed acquisition, one critical section, one finally that unlocks exactly once.
  • A request that is never admitted reports that distinctly, and only claims nothing was proposed when no earlier iteration reached the CAS.

Why this shouldn't regress

  • Not new serialisation. The epoch is the partition key of the log table, and Paxos.cas already takes a per-partition coordinator lock for
    the whole CAS, so local contenders were already serialising — just bounded by cas_contention_timeout instead of the caller's deadline, and
    only discoverable by burning an attempt.
  • tryCommitOne can only apply one proposal per epoch, so racing that slot locally never added throughput.
  • Lock released before the follower wait, the uninterruptible backoff sleep, and any log fetch — none of those block another proposer.
  • Fair, so no starvation by later arrivals.
  • Success, rejection and failure behaviour is otherwise unchanged; the only new outcome is "not admitted".
  • Passing: CMSShutdownTest, DistributedLogTest, LostCommitReqResTest, RetryTest, AlterTopologyTest, CommitStartupByNonCMSNodeTest,
    ConcurrentSchemaCommitTest, AbstractLocalProcessorLockTest, plus ant checkstyle checkstyle-test rat-check.
  • Single-node concurrent DDL: 48 workers / 864 statements go from 311 failures to 0.

Tests added

  • ConcurrentSchemaCommitTest (in-JVM) — asserts every submitted statement succeeds and swallows no exception.
  • AbstractLocalProcessorLockTest (unit, ~2s, no sleeps) — timeout while queued, interrupt while queued, and
    proposal-attempted-then-denied-readmission.

For reviewers

  • Does not address races between separate CMS members; that remains the distributed log's job.
  • Admission is FIFO with no per-Transformation.Kind priority, so a topology change can queue behind earlier schema DDL. Happy to add priority if wanted.
  • No admission metrics yet.
  • Untested on a real multi-CMS-member cluster.
  • Based on trunk; cassandra-6.0 is equally affected and the TCM source is identical between them

patch by Patrick McFadin; reviewed by TBD for CASSANDRA-21537

Assisted-by: Claude Opus 5

…t exhaust their retry budget contending for the same epoch

Concurrent CQL DDL submitted to a CMS member could fail with a SERVER_ERROR while
the request deadline still had seconds remaining. Every concurrent commit read the
same highest consecutive epoch and proposed at epoch+1, so all but one loser per
round burned a retry attempt, backed off and refetched the log before recomputing.
With the attempt limit from the default cms_retry_delay that budget was exhausted
long before request_timeout, so valid statements were rejected while several
seconds of their deadline remained.

Since tryCommitOne can only ever apply one proposal per epoch, and Paxos already
takes a per-partition coordinator lock for the duration of the CAS, racing that
slot from many local request threads cannot increase throughput; it only converts
would-be waiting into wasted attempts. AbstractLocalProcessor now serialises the
read-execute-propose cycle for locally originating commits behind a fair lock
whose acquisition is bounded by the caller's own deadline. The lock is released
before the follower wait, the backoff sleep and any log fetch, so none of those
block another proposer. A request which cannot be admitted in time reports that
distinctly, and only claims that nothing was proposed when no earlier iteration
had reached the CAS, since a lost epoch and an ambiguous Paxos result are
indistinguishable to the caller.

This does not address races between separate CMS members, which remain the
distributed log's responsibility. Admission is FIFO and does not prioritise by
transformation kind, so a topology change can queue behind schema DDL which
arrived first.

patch by Patrick McFadin; reviewed by TBD for CASSANDRA-21537

Assisted-by: Claude Opus 5 <noreply@anthropic.com>
@pmcfadin
pmcfadin force-pushed the pmcfadin/CASSANDRA-21537/trunk branch from 7a8b994 to b029c7b Compare July 28, 2026 04:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant