microsoft/FASTER · error · FasterException

Iterator address is less than log HeadAddress in…

Error message

Iterator address is less than log HeadAddress in memory-scan mode

What it means

Thrown by BlittableScanIterator.GetNext when the iterator's next scan address is below the log's HeadAddress while operating in memory-scan mode (frameSize == 0), the records are not guaranteed buffered, and forceInMemory was not requested. FASTER refuses to scan because records older than the log head may have been deleted from disk, so a full scan would return truncated or wrong data. It fails fast rather than silently skipping missing records.

Solutions

  1. Ensure the iterator's BeginAddress is >= the log's current HeadAddress before scanning (refresh/advance the iterator's begin address).
  2. Configure the log with a non-zero memory/frame size so scan coverage is buffered, e.g. increase LogSettings.MemorySizeBits or use EpochProtectionScope/frame options for the iterator.
  3. Pass forceInMemory: true only if you can guarantee the range is still resident (otherwise you get silent data loss).
  4. Recreate the scan starting from hlog.BeginAddress (or the log's current BeginAddress) instead of a stale address.
  5. If a persisted iterator resume fails, snapshot/advance iterator tokens frequently so they never lag behind the truncated head.

Example fix

// before: resuming a stale iterator address
var iter = log.Scan(staleIteratorAddress, log.TailAddress, scanBufferingMode);

// after: clamp the begin address to what is still available
var begin = log.BeginAddress;
var from = staleIteratorAddress < begin ? begin : staleIteratorAddress;
var iter = log.Scan(from, log.TailAddress, ScanBufferingMode.BufferPaged);
Defensive patterns

Strategy: validation

Validate before calling

// Before scanning, ensure the iterator start is still available
if (scanFromAddress < log.BeginAddress)
{
    scanFromAddress = log.BeginAddress; // or re-create the iterator / fail the operation
}

Type guard

static bool CanScanFrom(BlittableScanIterator<K,V> it, long headAddress, long frameSize, bool forceInMemory)
    => forceInMemory || frameSize > 0 || !(it.NextAddress < headAddress);

Try / catch

try
{
    while (iter.GetNext(out RecordInfo info, out var key, out var value)) { /* ... */ }
}
catch (FasterException ex) when (ex.Message.Contains("less than log HeadAddress"))
{
    // restart scan from log.BeginAddress or re-snapshot the iterator
    iter = log.Scan(log.BeginAddress, log.TailAddress);
}

Prevention

When it happens

Trigger: Iterating a FasterLog or scan iterator where currentAddress < headAddress, the log was created with a memory size / frameSize of 0 (no buffering), and the iterator was neither initialized at BeginAddress nor given enough tail-space coverage to keep the range in memory.

Common situations: Opening an iterator over a long-running log whose head has advanced past the stored iterator begin address; resuming a persisted iterator token after the log has flushed/truncated; scanning a log configured without in-memory buffering or an epoch protection frame.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of microsoft/FASTER@321d872eab (2026-09-15). Data as JSON: /api/errors/ba8aa71de91d8ba9. Report an issue: GitHub.

Appendix: source

Thrown at cs/src/core/Allocator/BlittableScanIterator.cs:102

            recordInfo = default;

            while (true)
            {
                currentAddress = nextAddress;
                if (currentAddress >= endAddress)
                    return false;

                epoch?.Resume();
                var headAddress = hlog.HeadAddress;

                if (currentAddress < hlog.BeginAddress && !forceInMemory)
                    currentAddress = hlog.BeginAddress;

                // If currentAddress < headAddress and we're not buffering and not guaranteeing the records are in memory, fail.
                if (frameSize == 0 && currentAddress < headAddress && !forceInMemory)
                {
                    epoch?.Suspend();
                    throw new FasterException("Iterator address is less than log HeadAddress in memory-scan mode");
                }

                var currentPage = currentAddress >> hlog.LogPageSizeBits;
                var offset = currentAddress & hlog.PageSizeMask;

                if (currentAddress < headAddress && !forceInMemory)
                    BufferAndLoad(currentAddress, currentPage, currentPage % frameSize, headAddress, endAddress);

                long physicalAddress = GetPhysicalAddress(currentAddress, headAddress, currentPage, offset);
                var recordSize = hlog.GetRecordSize(physicalAddress).Item2;

                // If record does not fit on page, skip to the next page.
                if ((currentAddress & hlog.PageSizeMask) + recordSize > hlog.PageSize)
                {
                    nextAddress = (1 + (currentAddress >> hlog.LogPageSizeBits)) << hlog.LogPageSizeBits;
                    epoch?.Suspend();
                    continue;
                }

View on GitHub (pinned to 321d872eab)