{"record":{"id":"b23b0cd021e2f9b9","repo":"microsoft/garnet","slug":"unable-to-set-first-valid-segment-to-firstvalidse","errorCode":null,"errorMessage":"Unable to set first valid segment to {firstValidSegment}, first available segment on disk is {firstAvailSegment}","messagePattern":"Unable to set first valid segment to (.+?), first available segment on disk is (.+?)","errorType":"exception","errorClass":"TsavoriteException","httpStatus":null,"severity":"critical","filePath":"libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorBase.cs","lineNumber":729,"sourceCode":"\n            var firstAvailSegment = device.StartSegment;\n            var lastAvailSegment = device.EndSegment;\n\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            }","sourceCodeStart":711,"sourceCodeEnd":747,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorBase.cs#L711-L747","documentation":"Thrown by VerifyRecoveryInfo during recovery when the checkpoint's beginAddress maps to a segment (firstValidSegment) that is earlier than the first segment still present on the log device (device.StartSegment). In other words, recovery needs a disk segment that has already been deleted/truncated. The store cannot replay records that no longer exist on disk.","triggerScenarios":"Recovering from a HybridLogCheckpoint whose info.beginAddress points below device.StartSegment. Happens when the log device was trimmed (TruncateUntilAddress / RemoveSegment) or a separate process deleted on-disk segments after the checkpoint was taken, or when checkpoint and device directories are out of sync.","commonSituations":"Recovery attempted against a partially deleted/truncated log directory; checkpoint file copied without its corresponding data segments; manual cleanup of segment files; a prior recovery or store open trimmed the head past this checkpoint's begin address; mismatched checkpoint/log paths after a directory move.","solutions":["Recover from a newer checkpoint whose beginAddress >= device.StartSegment (check logger line 'Available segment range on device').","Restore the full log directory (all segments, not just the checkpoint) from backup so StartSegment <= firstValidSegment.","Delete the stale checkpoint and recover from a later one, or start fresh if the data is disposable.","Ensure the log device path and the checkpoint directory always move together and are never partially pruned."],"exampleFix":"// before: recover from an old checkpoint whose beginAddress predates surviving segments\nstore.Recover();\n// after: pick the latest compatible checkpoint, or restore missing segments\n// restore log dir from backup first, then\nstore.Recover();","handlingStrategy":"validation","validationCode":"// Before Recover: ensure the checkpoint's begin address is within surviving segments\nvar firstNeededSeg = (int)(recoveredCheckpoint.beginAddress >> logSettings.SegmentSizeBits);\nif (firstNeededSeg < logDevice.StartSegment)\n    throw new InvalidOperationException(\"Checkpoint begin address predates surviving log segments; restore backup or use a later checkpoint\");","typeGuard":null,"tryCatchPattern":"try { store.Recover(); }\ncatch (TsavoriteException ex) when (ex.Message.Contains(\"first available segment on disk\"))\n{\n    // checkpoint/device mismatch: restore full log dir or pick a later checkpoint\n    logger.LogError(ex, \"Recovery segment mismatch; checkpoint is older than surviving data\");\n    throw;\n}","preventionTips":["Never prune log segment files independent of checkpoints.","Keep checkpoint metadata and log directory in lockstep when moving or backing up.","Verify 'Available segment range on device' log line covers the checkpoint before recovering."],"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"}