{"record":{"id":"f351714e6f4c7408","repo":"microsoft/garnet","slug":"unable-to-set-last-valid-segment-to-lastvalidsegm","errorCode":null,"errorMessage":"Unable to set last valid segment to {lastValidSegment}, last available segment on disk is {lastAvailSegment}","messagePattern":"Unable to set last valid segment to (.+?), last available segment on disk is (.+?)","errorType":"exception","errorClass":"TsavoriteException","httpStatus":null,"severity":"critical","filePath":"libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorBase.cs","lineNumber":732,"sourceCode":"\n            if (FlushedUntilAddress > GetFirstValidLogicalAddressOnPage(0))\n            {\n                var flushedUntilAddress = FlushedUntilAddress;\n                int currTailSegment = (int)GetSegment(flushedUntilAddress);\n                if (GetOffsetOnSegment(flushedUntilAddress) == 0)\n                    currTailSegment--;\n\n                if (currTailSegment > lastAvailSegment)\n                    lastAvailSegment = currTailSegment;\n            }\n\n            logger?.LogInformation(\"Available segment range on device: [{firstAvailSegment}--{lastAvailSegment}]\", firstAvailSegment, lastAvailSegment);\n\n            if (firstValidSegment < firstAvailSegment)\n                throw new TsavoriteException($\"Unable to set first valid segment to {firstValidSegment}, first available segment on disk is {firstAvailSegment}\");\n\n            if (lastAvailSegment >= 0 && lastValidSegment > lastAvailSegment)\n                throw new TsavoriteException($\"Unable to set last valid segment to {lastValidSegment}, last available segment on disk is {lastAvailSegment}\");\n\n            if (trimLog)\n            {\n                logger?.LogInformation(\"Trimming disk segments until (not including) {firstSegment}\", firstValidSegment);\n                var toAddress = GetStartLogicalAddressOfSegment(firstValidSegment);\n                TruncateUntilAddressBlocking(toAddress);\n                if (storeFunctions.CallOnTruncate)\n                    storeFunctions.OnTruncate(toAddress);\n\n                for (int s = lastValidSegment + 1; s <= lastAvailSegment; s++)\n                {\n                    logger?.LogInformation(\"Trimming tail segment {s} on disk\", s);\n                    RemoveSegment(s);\n                }\n            }\n        }\n\n        /// <summary>Allocate a pinned byte[] for the page at <paramref name=\"index\"/></summary>","sourceCodeStart":714,"sourceCodeEnd":750,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorBase.cs#L714-L750","documentation":"Thrown by VerifyRecoveryInfo during recovery when the checkpoint's flushed address maps to a segment (lastValidSegment) that is beyond the last segment present on the log device (lastAvailSegment). Recovery needs the tail segment of the log to reach the checkpoint's flushed/commit point, but that segment is missing on disk.","triggerScenarios":"Recovering from a checkpoint whose finalLogicalAddress / flushedLogicalAddress falls in a segment greater than device.EndSegment (and the in-memory FlushedUntilAddress doesn't extend it). Occurs when tail segments were lost, the log directory is incomplete, or checkpoint metadata points further than the data present.","commonSituations":"Incomplete copy of the log directory (missing the newest segment files); checkpoint taken then tail segments deleted; disk corruption/loss of the most recent segments; a checkpoint that is ahead of the actually-flushed data (crash between checkpoint commit and segment flush).","solutions":["Restore the missing tail segments from backup so lastAvailSegment >= lastValidSegment.","Recover from an earlier checkpoint whose flushed address falls within the surviving segment range.","Verify the log directory is complete (no missing segment files numbered <= lastValidSegment) before calling Recover.","If segments are genuinely lost and no earlier checkpoint is available, start the store fresh and accept data loss."],"exampleFix":"// before\nstore.Recover(); // lastValidSegment > lastAvailSegment\n// after: recover from an earlier checkpoint within the available segment range\ncheckpointManager.SetRecoveredHLCInfo(earlierCheckpoint);\nstore.Recover();","handlingStrategy":"validation","validationCode":"var lastNeededSeg = (int)(recoveredCheckpoint.flushedLogicalAddress >> logSettings.SegmentSizeBits);\nif (lastNeededSeg > logDevice.EndSegment)\n    throw new InvalidOperationException(\"Checkpoint needs tail segments missing from the log device\");","typeGuard":null,"tryCatchPattern":"try { store.Recover(); }\ncatch (TsavoriteException ex) when (ex.Message.Contains(\"last available segment on disk\"))\n{\n    logger.LogError(ex, \"Recovery needs tail segments not present; restore backup or use earlier checkpoint\");\n    throw;\n}","preventionTips":["Always back up the entire log directory, including the newest segment files.","Confirm no segment files numbered between StartSegment and the checkpoint tail are missing.","Recover from the most recent checkpoint that fits within the available segment range."],"tags":["recovery","checkpoint","storage","segment"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}