{"record":{"id":"466508f0f4604bbe","repo":"microsoft/garnet","slug":"iterator-address-is-less-than-log-headaddress-in-m-466508","errorCode":null,"errorMessage":"Iterator address is less than log HeadAddress in memory-scan mode","messagePattern":"Iterator address is less than log HeadAddress in memory-scan mode","errorType":"exception","errorClass":"TsavoriteException","httpStatus":null,"severity":"error","filePath":"libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteScanIterator.cs","lineNumber":142,"sourceCode":"                return false;\n            }\n\n            // Success; caller will suspend the epoch as needed.\n            return true;\n        }\n\n        private bool LoadPageIfNeeded(out long headAddress, out long currentPage, long stopAddress)\n        {\n            headAddress = hlogBase.HeadAddress;\n\n            if (currentAddress < hlogBase.BeginAddress && !assumeInMemory)\n                currentAddress = hlogBase.BeginAddress;\n\n            // If currentAddress < headAddress and we're not buffering and not guaranteeing the records are in memory, fail.\n            if (frameSize == 0 && currentAddress < headAddress && !assumeInMemory)\n            {\n                // Caller will suspend the epoch.\n                throw new TsavoriteException(\"Iterator address is less than log HeadAddress in memory-scan mode\");\n            }\n\n            currentPage = hlogBase.GetPage(currentAddress);\n            if (currentAddress < headAddress && !assumeInMemory)\n                _ = BufferAndLoad(currentAddress, currentPage, currentPage % frameSize, headAddress, stopAddress);\n\n            // Success; keep the epoch held for GetNext (SnapCursorToLogicalAddress will Suspend()).\n            return true;\n        }\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        internal long SnapToLogicalAddressBoundary(ref long logicalAddress, long headAddress, long currentPage)\n        {\n            var offset = hlogBase.GetOffsetOnPage(logicalAddress);\n\n            // Subtracting offset means this physicalAddress is at the start of the page. Adjust for PageHeader.\n            long totalSizes = PageHeader.Size;\n            if (currentPage == 0)","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteScanIterator.cs#L124-L160","documentation":"SpanByteScanIterator.LoadPageIfNeeded throws when frameSize == 0 (no read-back frame / no buffered disk pages), currentAddress < hlogBase.HeadAddress (the address has been evicted or overwritten in memory), and assumeInMemory is false. Pure memory-scan mode cannot serve records that have already rolled off the in-memory log.","triggerScenarios":"Starting or continuing a memory-only scan (no disk read frame) at an address behind HeadAddress — the log has wrapped and overwritten/evicted those records — without setting assumeInMemory and without a buffered frame to read them back from disk.","commonSituations":"Long-running scan that falls behind while the log rolls over; resuming a scan from a saved stale address; MemorySize configured too small for the scan range; using the in-memory scan API for data that has spilled to disk.","solutions":["Increase the log MemorySize so the entire scanned address range stays resident in memory.","Start the scan at or after the current HeadAddress (use hlog.HeadAddress / BeginAddress), not a saved stale address.","Use a disk-backed scan (ensure the read frame is allocated so frameSize > 0) so evicted pages are read back.","If skipping evicted records is acceptable, document it and start the iterator at HeadAddress."],"exampleFix":"// before\nusing var it = store.Log.Scan(startAddress: staleAddr, ...);\n\n// after: clamp the start address to the in-memory head\nlong begin = Math.Max(staleAddr, store.Log.HeadAddress);\nusing var it = store.Log.Scan(startAddress: begin, ...);","handlingStrategy":"validation","validationCode":"long begin = Math.Max(requestedStartAddress, store.Log.HeadAddress);\nif (!assumeInMemory && requestedStartAddress < store.Log.HeadAddress && frameSize == 0)\n    throw new InvalidOperationException($\"start {requestedStartAddress} < HeadAddress {store.Log.HeadAddress}; raise MemorySize or use a disk-backed scan\");\nusing var it = store.Log.Scan(begin, ...);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Size MemorySize to hold the entire range you intend to scan.","Always clamp scan start addresses to current HeadAddress for memory scans.","Use a disk-backed scan (frameSize > 0) when the range may have spilled to disk.","Persist and resume scans by address only when you know the range stays resident."],"tags":["tsavorite","scan","memory","eviction","log-rollover"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}