{"record":{"id":"d241072b230a472f","repo":"risingwavelabs/risingwave","slug":"truncate-offset-but-prev-truncate-offset-is","errorCode":null,"errorMessage":"truncate offset {:?} but prev truncate offset is {:?}","messagePattern":"truncate offset (.+?) but prev truncate offset is (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/stream/src/common/log_store_impl/in_mem.rs","lineNumber":235,"sourceCode":"                            LogStoreReadItem::Barrier {\n                                is_checkpoint: options.is_checkpoint,\n                                new_vnode_bitmap: options.new_vnode_bitmap,\n                                is_stop: options.is_stop,\n                                schema_change: options.schema_change,\n                            },\n                        ))\n                    }\n                },\n                None => Err(anyhow!(\"end of log stream\")),\n            },\n            AwaitingTruncate { .. } => std::future::pending().await,\n        }\n    }\n\n    fn truncate(&mut self, offset: TruncateOffset) -> LogStoreResult<()> {\n        // check the truncate offset is higher than prev truncate offset\n        if self.truncate_offset >= offset {\n            return Err(anyhow!(\n                \"truncate offset {:?} but prev truncate offset is {:?}\",\n                offset,\n                self.truncate_offset\n            ));\n        }\n\n        // check the truncate offset does not exceed the latest possible offset\n        if offset > self.latest_offset {\n            return Err(anyhow!(\n                \"truncate at {:?} but latest offset is {:?}\",\n                offset,\n                self.latest_offset\n            ));\n        }\n\n        if let AwaitingTruncate {\n            sealed_epoch,\n            next_epoch,","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/stream/src/common/log_store_impl/in_mem.rs#L217-L253","documentation":"Guard in the in-memory log store's truncate: the requested truncate offset is not strictly newer than the previously applied truncate offset (it is <= it). Truncation must monotonically advance; a stale or duplicated truncate request indicates a bug in the caller and returns this error.","triggerScenarios":"truncate(offset) invoked when self.truncate_offset >= offset, i.e. a duplicate or out-of-order truncate request.","commonSituations":"Bugs in min-working-epoch tracking that compute a stale truncate offset, multiple components racing to truncate, or recovery replaying an old truncation.","solutions":["Ensure the truncation offset is derived from the minimum consumed offset and is monotonic","Guard callers to skip truncate when offset <= current truncate offset","Inspect epoch/consumption tracking for double-truncate races"],"exampleFix":"// before\nlog_store.truncate(offset).await?;\n// after\nif offset > current_truncate_offset {\n    log_store.truncate(offset).await?;\n}","handlingStrategy":"validation","validationCode":"fn should_truncate(offset: u64, prev_truncate_offset: u64) -> bool {\n    offset > prev_truncate_offset\n}","typeGuard":null,"tryCatchPattern":"if offset > current_truncate_offset {\n    log_store.truncate(offset).await?;\n}","preventionTips":["Compute truncate offsets from min consumed epoch monotonically","Deduplicate truncate requests among competing components","Log skip-truncate decisions to detect stale offset computation"],"tags":["stream","log-store","truncate"],"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-14T16:17:12.679Z"}