apache/cassandra · error · IllegalStateException

Timed out while waiting for the follower to enact the epoch

Error message

Timed out while waiting for the follower to enact the epoch %s

What it means

After a transformation is committed to the metadata log, the committing node waits for the resulting epoch to be enacted locally/acknowledged (e.g. a follower catching up to the new epoch). If that wait times out, the TimeoutException is converted into an IllegalStateException naming the epoch that failed to be enacted, wrapped as the cause.

Solutions

  1. Check node/CMS health and network connectivity; retry the operation once the epoch catches up.
  2. Inspect logs around the timeout for stalls in the CommitLogProcessor or metadata log replay.
  3. Increase headroom: reduce load or GC pauses; investigate slow disk/CPU on the committing node.
  4. If the node is shutting down, abort the operation gracefully instead of committing.
  5. Verify cluster quorum is reachable — partitions prevent epoch propagation within the timeout.

Example fix

// before
try { service.commit(transform); } // may throw wrapped TimeoutException
// after
try {
    service.commit(transform);
} catch (IllegalStateException e) {
    if (e.getCause() instanceof TimeoutException) {
        logger.warn("Epoch enactment timed out; retrying after catch-up", e);
        service.commit(transform); // safe: commit succeeded, waiting on enactment
    } else throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

// Ensure node healthy and connected to CMS before committing
if (!service.isRunning() || nodeShuttingDown()) skipCommit();

Try / catch

try { service.commit(transform); }
catch (IllegalStateException e) {
  if (e.getCause() instanceof TimeoutException) retryAfterCatchUp();
  else throw e;
}

Prevention

When it happens

Trigger: commit(transform, onSuccess, onFailure) succeeds in proposing the transformation (result.success() present), but the subsequent wait for the follower/node to enact result.success().epoch exceeds the timeout — e.g. the log processor is stalled, network partitioned, or the node is overloaded.

Common situations: CMS leader commits but the local processor stalls under load; network partition between CMS and followers delays catch-up; node shutting down or GC pauses preventing epoch enactment in time; very slow disks replaying the metadata log.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/73231b3032f3f7ff. Report an issue: GitHub.

Appendix: source

Thrown at src/java/org/apache/cassandra/tcm/ClusterMetadataService.java:713

        try
        {
            if (result.isSuccess())
            {
                TCMMetrics.instance.commitSuccessLatency.update(nanoTime() - startTime, NANOSECONDS);
                return onSuccess.accept(awaitAtLeast(result.success().epoch));
            }
            else
            {
                TCMMetrics.instance.recordCommitFailureLatency(nanoTime() - startTime, NANOSECONDS, result.failure().rejected);
                logger.debug("Failed to commit {} after {} attempts ({}): {} {}",
                             transform.kind(), retryPolicy.attempts(), retryPolicy,
                             result.failure().code, result.failure().message);
                return onFailure.accept(result.failure().code, result.failure().message);
            }
        }
        catch (TimeoutException t)
        {
            throw new IllegalStateException(String.format("Timed out while waiting for the follower to enact the epoch %s", result.success().epoch), t);
        }
        catch (InterruptedException e)
        {
            throw new IllegalStateException("Couldn't commit the transformation. Is the node shutting down?", e);
        }
    }

    private static Retry getRetryPolicy(Transformation.Kind kind)
    {
        Retry retryPolicy;
        if (kind == Transformation.Kind.STARTUP)
        {
            retryPolicy = Retry.withNoTimeLimit(TCMMetrics.instance.commitRetries, Retry.unsafeRetryIndefinitely());
        }
        else if (kind == Transformation.Kind.SCHEMA_CHANGE)
        {
            long deadlineNanos = nanoTime() + DatabaseDescriptor.getRpcTimeout(TimeUnit.NANOSECONDS);
            retryPolicy = Retry.until(deadlineNanos, TCMMetrics.instance.commitRetries);

View on GitHub (pinned to 88fd0f6a0e)