{"record":{"id":"56f0fdc37f5e0289","repo":"apache/cassandra","slug":"reverting-to-epoch","errorCode":null,"errorMessage":"Reverting to epoch {}","messagePattern":"Reverting to epoch (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/java/org/apache/cassandra/tcm/ClusterMetadataService.java","lineNumber":561,"sourceCode":"        return log.unsafeSetCommittedFromGossip(expected, updated);\n    }\n\n    public void setFromGossip(ClusterMetadata fromGossip)\n    {\n        logger.debug(\"Setting from gossip, new={}\", fromGossip);\n        if (state() != GOSSIP)\n            throw new IllegalStateException(\"Can't apply a ClusterMetadata from gossip when CMSState is not GOSSIP: \" + state());\n        log.unsafeSetCommittedFromGossip(fromGossip);\n    }\n\n    public void forceSnapshot(ClusterMetadata snapshot)\n    {\n        commit(new ForceSnapshot(snapshot));\n    }\n\n    public void revertToEpoch(Epoch epoch)\n    {\n        logger.warn(\"Reverting to epoch {}\", epoch);\n        ClusterMetadata metadata = ClusterMetadata.current();\n        ClusterMetadata toApply = transformSnapshot(LogState.getForRecovery(epoch))\n                                  .forceEpoch(metadata.epoch.nextEpoch());\n        forceSnapshot(toApply);\n    }\n\n    /**\n     * dumps the cluster metadata at the given epoch, returns path to the generated file\n     * if the given Epoch is EMPTY, we dump the current metadata\n     *\n     * @param epoch dump clustermetadata at this epoch\n     * @param transformToEpoch transform the dumped metadata to this epoch\n     * @param version serialisation version\n     */\n    public String dumpClusterMetadata(Epoch epoch, Epoch transformToEpoch, Version version) throws IOException\n    {\n        ClusterMetadata toDump = epoch.isAfter(Epoch.EMPTY)\n                                 ? transformSnapshot(LogState.getForRecovery(epoch))","sourceCodeStart":543,"sourceCodeEnd":579,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/tcm/ClusterMetadataService.java#L543-L579","documentation":"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.","triggerScenarios":"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.","commonSituations":"Manual disaster recovery of the TCM log; rollback after a harmful topology change; recovery procedures guided by Cassandra documentation or support.","solutions":["Verify the target epoch is correct before reverting; this discards later transformations.","Take a metadata dump (`nodetool cms dump`) first for forensics.","After revert, confirm all nodes converge to the forced epoch and CMS membership is intact.","If you did not intend this, check what invoked revertToEpoch and halt the recovery script."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// confirm target epoch is older than current and intended\nif (!epoch.isBefore(ClusterMetadata.current().epoch)) throw new IllegalArgumentException(\"epoch not in the past\");","typeGuard":null,"tryCatchPattern":"try { cms.revertToEpoch(target); } catch (Throwable t) { logger.error(\"revert failed, cluster may be inconsistent\", t); }","preventionTips":["Always dump metadata before reverting","Only revert under a documented recovery procedure","Verify cluster convergence after revert"],"tags":["recovery","cluster-metadata","destructive-operation"],"backgroundTag":"invalid-state-transition","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}