microsoft/garnet · error · TsavoriteException
Can compact only until Log.SafeReadOnlyAddress
Error message
Can compact only until Log.SafeReadOnlyAddress
What it means
CompactLookup (TsavoriteCompaction.cs:32) compacts the immutable portion of the log, so it requires untilAddress <= hlogBase.SafeReadOnlyAddress (the boundary between the flushed read-only region and the mutable region). Passing an address beyond SafeReadOnlyAddress would try to relocate records that may still be mutating, so it throws 'Can compact only until Log.SafeReadOnlyAddress' at line 36. SafeReadOnlyAddress advances only after pages are marked read-only and flushed.
Source
Thrown at libs/storage/Tsavorite/cs/src/core/Compaction/TsavoriteCompaction.cs:36
/// <param name="untilAddress">Compact log until this address</param>
/// <param name="compactionType">Compaction type (whether we lookup records or scan log for liveness checking)</param>
/// <returns>Address until which compaction was done</returns>
internal long Compact<TInput, TOutput, TContext, TCompactionFunctions>(TCompactionFunctions cf, long untilAddress, CompactionType compactionType)
where TCompactionFunctions : ICompactionFunctions
{
return compactionType switch
{
CompactionType.Scan => CompactScan<TInput, TOutput, TContext, TCompactionFunctions>(cf, untilAddress),
CompactionType.Lookup => CompactLookup<TInput, TOutput, TContext, TCompactionFunctions>(cf, untilAddress),
_ => throw new TsavoriteException("Invalid compaction type"),
};
}
private long CompactLookup<TInput, TOutput, TContext, TCompactionFunctions>(TCompactionFunctions cf, long untilAddress)
where TCompactionFunctions : ICompactionFunctions
{
if (untilAddress > hlogBase.SafeReadOnlyAddress)
throw new TsavoriteException("Can compact only until Log.SafeReadOnlyAddress");
using var storeSession = NewSession<ITsavoriteScanIterator, TInput, TOutput, TContext, NoOpSessionFunctions<TInput, TOutput, TContext>>(new());
var storebContext = storeSession.BasicContext;
using (var iter1 = Log.Scan(Log.BeginAddress, untilAddress))
{
long numPending = 0;
while (iter1.GetNext())
{
var key = iter1.Key;
if (!iter1.Info.Tombstone && !cf.IsDeleted(in iter1))
{
var iter1AsLogSource = iter1 as ISourceLogRecord; // Can't use 'ref' on a 'using' variable
var status = storebContext.CompactionCopyToTail(in iter1AsLogSource, iter1.CurrentAddress, iter1.NextAddress);
if (status.IsPending && ++numPending > 256)
{
_ = storebContext.CompletePending(wait: true);View on GitHub (pinned to 951b0fc683)
Solutions
- Clamp untilAddress to Log.SafeReadOnlyAddress before calling Compact.
- Ensure the read-only region has advanced: trigger a flush/checkpoint or wait until SafeReadOnlyAddress >= your target before compacting.
- Pass Log.SafeReadOnlyAddress (or a value below it) as the compaction boundary.
Example fix
// before var addr = store.Log.TailAddress; // likely > SafeReadOnlyAddress store.Log.Compact<TInput, TOutput, TContext>(addr, CompactionType.Lookup); // throws // after var addr = Math.Min(requestedAddress, store.Log.SafeReadOnlyAddress); store.Log.Compact<TInput, TOutput, TContext>(addr, CompactionType.Lookup);
Defensive patterns
Strategy: validation
Validate before calling
// Clamp the compaction address to the safe read-only boundary before Lookup compaction. long addr = Math.Min(requestedUntilAddress, store.Log.SafeReadOnlyAddress); if (addr <= store.Log.BeginAddress) return; // nothing to compact store.Log.Compact<TInput, TOutput, TContext>(addr, CompactionType.Lookup);
Type guard
static bool WithinSafeRange(long untilAddress, long safeReadOnlyAddress, long beginAddress)
=> untilAddress > beginAddress && untilAddress <= safeReadOnlyAddress;
// Guard the Compact call with WithinSafeRange(addr, store.Log.SafeReadOnlyAddress, store.Log.BeginAddress). Prevention
- Use Log.SafeReadOnlyAddress as the compaction ceiling, never TailAddress/ReadOnlyAddress.
- Flush/checkpoint first so SafeReadOnlyAddress advances to your intended point.
- Compact below or at SafeReadOnlyAddress; re-check the live value immediately before the call.
When it happens
Trigger: Calling Compact with CompactionType.Lookup and an untilAddress greater than Log.SafeReadOnlyAddress, e.g. compacting up to Log.TailAddress before a flush has advanced the read-only boundary.
Common situations: Using Log.TailAddress or Log.ReadOnlyAddress as the compaction target instead of SafeReadOnlyAddress; compacting immediately after heavy writes with no checkpoint/flush; or racing a compaction against ongoing writes that keep the safe boundary below the requested address.
Related errors
- InPlaceWriter(ReadOnlySpan<byte> value) is not supported in
- InPlaceWriter(IHeapObject value) is not supported in this IS
- GetRMWModifiedFieldInfo is not supported in this ISessionFun
- GetRMWInitialFieldInfo is not supported in this ISessionFunc
- GetUpsertFieldInfo(ReadOnlySpan<byte> value) is not supporte
AI-assisted analysis of microsoft/garnet@951b0fc683 (2026-08-13).
Data as JSON: /api/errors/ce7407e55bc236d3.
Report an issue: GitHub.