apache/cassandra · warning

Reverting to epoch

Error message

Reverting to epoch {}

What it means

ClusterMetadataService.revertToEpoch logs a warning while forcibly rewinding cluster metadata to a prior epoch. It commits a ForceSnapshot built from the recovery log state at the given epoch, forcing the metadata forward with a new epoch. This is a destructive recovery operation and the warning flags that it is happening.

Solutions

  1. Verify the target epoch is correct before reverting; this discards later transformations.
  2. Take a metadata dump (`nodetool cms dump`) first for forensics.
  3. After revert, confirm all nodes converge to the forced epoch and CMS membership is intact.
  4. If you did not intend this, check what invoked revertToEpoch and halt the recovery script.
Defensive patterns

Strategy: try-catch

Validate before calling

// confirm target epoch is older than current and intended
if (!epoch.isBefore(ClusterMetadata.current().epoch)) throw new IllegalArgumentException("epoch not in the past");

Try / catch

try { cms.revertToEpoch(target); } catch (Throwable t) { logger.error("revert failed, cluster may be inconsistent", t); }

Prevention

When it happens

Trigger: An operator or internal recovery path calls revertToEpoch(epoch) to discard metadata changes after a given epoch — e.g. after detecting a bad transformation or corrupted state.

Common situations: Manual disaster recovery of the TCM log; rollback after a harmful topology change; recovery procedures guided by Cassandra documentation or support.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

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

        return log.unsafeSetCommittedFromGossip(expected, updated);
    }

    public void setFromGossip(ClusterMetadata fromGossip)
    {
        logger.debug("Setting from gossip, new={}", fromGossip);
        if (state() != GOSSIP)
            throw new IllegalStateException("Can't apply a ClusterMetadata from gossip when CMSState is not GOSSIP: " + state());
        log.unsafeSetCommittedFromGossip(fromGossip);
    }

    public void forceSnapshot(ClusterMetadata snapshot)
    {
        commit(new ForceSnapshot(snapshot));
    }

    public void revertToEpoch(Epoch epoch)
    {
        logger.warn("Reverting to epoch {}", epoch);
        ClusterMetadata metadata = ClusterMetadata.current();
        ClusterMetadata toApply = transformSnapshot(LogState.getForRecovery(epoch))
                                  .forceEpoch(metadata.epoch.nextEpoch());
        forceSnapshot(toApply);
    }

    /**
     * dumps the cluster metadata at the given epoch, returns path to the generated file
     * if the given Epoch is EMPTY, we dump the current metadata
     *
     * @param epoch dump clustermetadata at this epoch
     * @param transformToEpoch transform the dumped metadata to this epoch
     * @param version serialisation version
     */
    public String dumpClusterMetadata(Epoch epoch, Epoch transformToEpoch, Version version) throws IOException
    {
        ClusterMetadata toDump = epoch.isAfter(Epoch.EMPTY)
                                 ? transformSnapshot(LogState.getForRecovery(epoch))

View on GitHub (pinned to 88fd0f6a0e)