microsoft/garnet · error · TsavoriteException
Iterator address is less than log HeadAddress in memory-scan
Error message
Iterator address is less than log HeadAddress in memory-scan mode
What it means
Thrown by ObjectScanIterator.LoadPageIfNeeded when scanning in memory-scan mode (frameSize == 0, no read buffering) and the iterator's currentAddress has fallen below the log HeadAddress while assumeInMemory is false. Once the log's head has moved past the scan cursor, the records are no longer in memory and, with no frame to page them back from disk, the scan cannot continue safely.
Source
Thrown at libs/storage/Tsavorite/cs/src/core/Allocator/ObjectScanIterator.cs:143
return false;
}
// Success; caller will suspend the epoch as needed.
return true;
}
private bool LoadPageIfNeeded(out long headAddress, out long currentPage, long stopAddress)
{
headAddress = hlogBase.HeadAddress;
if (currentAddress < hlogBase.BeginAddress && !assumeInMemory)
currentAddress = hlogBase.BeginAddress;
// If currentAddress < headAddress and we're not buffering and not guaranteeing the records are in memory, fail.
if (frameSize == 0 && currentAddress < headAddress && !assumeInMemory)
{
// Caller will suspend the epoch.
throw new TsavoriteException("Iterator address is less than log HeadAddress in memory-scan mode");
}
currentPage = hlogBase.GetPage(currentAddress);
if (currentAddress < headAddress && !assumeInMemory)
_ = BufferAndLoad(currentAddress, currentPage, currentPage % frameSize, headAddress, stopAddress);
// Success; keep the epoch held for GetNext (SnapCursorToLogicalAddress will Suspend()).
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal long SnapToLogicalAddressBoundary(ref long logicalAddress, long headAddress, long currentPage)
{
var offset = hlogBase.GetOffsetOnPage(logicalAddress);
// Subtracting offset means this physicalAddress is at the start of the page. Adjust for PageHeader.
long totalSizes = PageHeader.Size;
if (currentPage == 0)View on GitHub (pinned to 951b0fc683)
Solutions
- Use a scan mode that buffers pages from disk (provide a frame / use a scan iterator configured for disk-backed reads) so frameSize > 0.
- Scan from the current HeadAddress onward instead of from an old address: set the scan start to hlog.HeadAddress.
- Increase LogSettings.MemorySize so the head does not overtake the scan within its window.
- Set assumeInMemory only when you can guarantee records remain resident; otherwise do not rely on memory-scan mode for historical ranges.
Example fix
// before: memory-scan from an old address that the head has passed using var it = store.Iterate(); // after: start the scan at the current head, or use a disk-buffering scan var startAddr = store.Log.HeadAddress; using var it = store.Iterate(startAddress: startAddr);
Defensive patterns
Strategy: validation
Validate before calling
// Before scanning: clamp the scan start to at least the current head address var start = Math.Max(requestedStartAddress, store.Log.HeadAddress); using var it = store.Iterate(startAddress: start);
Try / catch
try { while (it.GetNext(out var rec)) { /* ... */ } }
catch (TsavoriteException ex) when (ex.Message.Contains("less than log HeadAddress"))
{
// head overtook the scan; restart from the new head
logger.LogWarning("Scan cursor fell behind HeadAddress; restarting from head");
} Prevention
- Use a disk-buffering scan (frameSize > 0) for historical ranges, not memory-scan mode.
- Keep MemorySize large enough that scans complete before HeadAddress advances.
- Start scans from HeadAddress when full history is not required.
When it happens
Trigger: A slow scan over an ObjectStore log where the producer has advanced HeadAddress past the scan cursor, started with ScanIter, and frameSize is 0 (no read cache/frame) and assumeInMemory is false. Calling GetNext after the head overtook the cursor.
Common situations: Long-running scans that can't keep up with writes; scanning a log configured with small MemorySize so HeadAddress advances quickly; using a plain scan iterator expecting full-history traversal when records have spilled to disk; concurrent truncation during a scan.
Related errors
- Iterator address is less than log HeadAddress in memory-scan
- Exceeded maximum response size of ({Array.MaxLength:N0}) byt
- bftree_scan_with_count returned a null handle.
- bftree_scan_with_end_key returned a null handle.
- {nameof(logSettings.PageCount)} or {nameof(logSettings.Memor
AI-assisted analysis of microsoft/garnet@951b0fc683 (2026-08-13).
Data as JSON: /api/errors/324140bea875834f.
Report an issue: GitHub.