{"record":{"id":"91b572acb1cdeb82","repo":"risingwavelabs/risingwave","slug":"table-epoch-committed-epoch","errorCode":null,"errorMessage":"table {} Epoch {} <= committed_epoch {}","messagePattern":"table (.+?) Epoch (.+?) <= committed_epoch (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/meta/src/hummock/manager/context.rs","lineNumber":246,"sourceCode":"                if *context_id == crate::manager::META_NODE_ID {\n                    continue;\n                }\n            }\n            if !context_info\n                .check_context(*context_id, &self.metadata_manager)\n                .await\n            {\n                return Err(Error::InvalidSst(*sst_id));\n            }\n        }\n        drop(context_info);\n\n        // sanity check on monotonically increasing table committed epoch\n        for (table_id, committed_epoch) in tables_to_commit {\n            if let Some(info) = current_version.state_table_info.info().get(table_id)\n                && *committed_epoch <= info.committed_epoch\n            {\n                return Err(anyhow::anyhow!(\n                    \"table {} Epoch {} <= committed_epoch {}\",\n                    table_id,\n                    committed_epoch,\n                    info.committed_epoch,\n                )\n                .into());\n            }\n        }\n\n        // HummockManager::now requires a write to the meta store. Thus, it should be avoided whenever feasible.\n        if !sstables.is_empty() {\n            // Sanity check to ensure SSTs to commit have not been full GCed yet.\n            let now = self.now().await?;\n            check_sst_retention(\n                now,\n                self.env.opts.min_sst_retention_time_sec,\n                sstables\n                    .iter()","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/meta/src/hummock/manager/context.rs#L228-L264","documentation":"commit_epoch's sanity check found a state table whose epoch to commit is not strictly greater than the table's already-committed epoch in the current version. Committing would move a table's visible data backwards, violating the monotonic epoch invariant of Hummock's MVCC versioning.","triggerScenarios":"Calling commit_epoch with tables_to_commit containing (table_id, epoch) where the current version's state_table_info shows committed_epoch >= that epoch. Happens when a stale node replays an old epoch's data after a failover or when the same epoch is committed twice for a table.","commonSituations":"Meta failover causing duplicate epoch commits; a stream actor resending an old SST/data epoch after restart; recovery path replaying an already-committed Hummock version; versioning bugs after a version change.","solutions":["Find the stale producer committing the old epoch (check which actor/node sent the SST or data) and ensure it refreshes from the latest hummock version before committing.","Skip/idempotently handle re-commit of an already-committed epoch in the recovery path instead of failing.","Audit epoch advancement logic (barrier collection) so a table's epoch never regresses; check for meta failover replaying finalized epochs."],"exampleFix":"// before\nreturn Err(anyhow!(\"table {} Epoch {} <= committed_epoch {}\", table_id, committed_epoch, info.committed_epoch));\n// after\nif *committed_epoch <= info.committed_epoch {\n    tracing::warn!(table_id, committed_epoch, prev = info.committed_epoch, \"duplicate/stale epoch commit ignored\");\n    continue; // idempotent re-commit\n}","handlingStrategy":"validation","validationCode":"// before calling commit_epoch, check every table's epoch advances monotonically\nfor (table_id, epoch) in &tables_to_commit {\n    if let Some(info) = current_version.state_table_info.info().get(table_id) {\n        assert!(*epoch > info.committed_epoch,\n            \"table {} epoch {} must exceed committed {}\", table_id, epoch, info.committed_epoch);\n    }\n}","typeGuard":null,"tryCatchPattern":"match hummock_manager.commit_epoch(new_epoch, tables_to_commit).await {\n    Err(e) if e.to_string().contains(\"<= committed_epoch\") => {\n        // stale/duplicate epoch: refresh from latest hummock version and re-drive the commit\n        refresh_local_version().await;\n    },\n    other => other?,\n}","preventionTips":["Always advance table epochs strictly monotonically; never replay an old epoch after restart","Make commit_epoch idempotent for duplicate epochs on the recovery path","After meta failover, re-sync the latest hummock version before committing SSTs/data","Ensure barrier/epoch collection does not re-deliver already-committed epochs"],"tags":["epoch","mvcc","sanity-check","hummock"],"backgroundTag":"invalid-state-transition","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}