microsoft/garnet · error · TsavoriteException
estimatedRecordSize ({estimatedTotalSize}) exceeds max alloc
Error message
estimatedRecordSize ({estimatedTotalSize}) exceeds max allocated heap size (to use: {allocationSizeToUse}; max: {maxHeapAllocationSize}) What it means
DirectCopyInlinePortionOfRecord copies a log record's inline bytes directly into the output SpanByte (network buffer) when it fits; otherwise it allocates heap memory bounded by maxHeapAllocationSize. The throw fires when the record's estimated total size exceeds that budgeted heap allocation, i.e. the record is larger than the per-operation heap memory the caller authorized. For non-object records allocationSizeToUse is estimatedTotalSize+4 (so the check only trips for object-valued records whose stored size exceeds maxHeapAllocationSize).
Source
Thrown at libs/storage/Tsavorite/cs/src/core/Allocator/DiskLogRecord.cs:411
}
/// <summary>
/// Directly copies a record in inline format to the SpanByteAndMemory. Allocates <see cref="SpanByteAndMemory.Memory"/> if needed.
/// </summary>
/// <remarks>If <paramref name="output"/>.<see cref="SpanByteAndMemory.IsSpanByte"/>, it points directly to the network buffer so we include the length prefix in the output.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void DirectCopyInlinePortionOfRecord<TSourceLogRecord>(in TSourceLogRecord logRecord, int alignedInlineRecordSize, int estimatedTotalSize, int maxHeapAllocationSize,
MemoryPool<byte> memoryPool, ref SpanByteAndMemory output)
where TSourceLogRecord : ISourceLogRecord
{
// See if we have enough space in the SpanByte and, if not, if we would fit in maxHeapAllocationSize.
// For SpanByte the recordSize must include the length prefix, which is included in the output stream
// if we can write directly to the SpanByte, which is a span in the network buffer.
if (!output.IsSpanByte || estimatedTotalSize + sizeof(int) > output.SpanByte.Length || logRecord.DataHeader.ValueIsObject)
{
var allocationSizeToUse = logRecord.DataHeader.ValueIsObject ? maxHeapAllocationSize : estimatedTotalSize + sizeof(int);
if (estimatedTotalSize > allocationSizeToUse)
throw new TsavoriteException($"estimatedRecordSize ({estimatedTotalSize}) exceeds max allocated heap size (to use: {allocationSizeToUse}; max: {maxHeapAllocationSize})");
output.EnsureHeapMemorySize(allocationSizeToUse, memoryPool);
}
// We must reset the LogRecord's filler size, because we truncated the record down to the (rounded-up) ActualSize if it had been shrunken.
var newFillerLength = alignedInlineRecordSize - logRecord.ActualSize;
if (output.IsSpanByte)
{
// TotalSize includes the length prefix. If there is a SpanByte it is a span in the network buffer, so we include the prefix length in the output stream.
var outPtr = output.SpanByte.ToPointer();
*(int*)outPtr = alignedInlineRecordSize;
outPtr += sizeof(int);
Buffer.MemoryCopy((byte*)logRecord.PhysicalAddress, outPtr, alignedInlineRecordSize, alignedInlineRecordSize);
new LogRecord((long)outPtr).SetFillerLength(newFillerLength);
}
else
{
// Do not include the length prefix in the output stream; this is done by the caller before writing the stream to the network buffer.
fixed (byte* outPtr = output.MemorySpan)View on GitHub (pinned to 951b0fc683)
Solutions
- Increase the maxHeapAllocationSize passed to the Serialize/DirectCopy call so it covers your largest record.
- Reduce stored value size: chunk or offload large blobs instead of keeping them as single object records.
- Use a directly-writable SpanByte output (network buffer) so the direct-copy path is taken and the heap limit is bypassed.
- For object values, ensure the configured serializer's max object size matches the data actually being stored.
- Log estimatedTotalSize vs maxHeapAllocationSize at write time so oversized records are rejected before they are stored.
Example fix
// before DiskLogRecord.Serialize(rec, maxHeapAllocationSize: 1 << 20, serializer, pool, ref output); // after DiskLogRecord.Serialize(rec, maxHeapAllocationSize: 1 << 24, serializer, pool, ref output);
Defensive patterns
Strategy: validation
Validate before calling
// Reject oversized records before the copy
int est = estimatedTotalSize;
int budget = logRecord.DataHeader.ValueIsObject ? maxHeapAllocationSize : estimatedTotalSize + sizeof(int);
if (est > budget)
throw new InvalidOperationException($"record {est} > budget {budget}; raise maxHeapAllocationSize or shrink the value"); Try / catch
try { DiskLogRecord.Serialize(rec, maxHeap, ser, pool, ref output); }
catch (TsavoriteException ex) when (ex.Message.Contains("exceeds max allocated heap size"))
{
// log est vs budget, drop or chunk the record, surface to caller
} Prevention
- Size maxHeapAllocationSize to your largest expected record at configuration time.
- Cap value sizes at the write path so oversized records never get stored.
- Prefer a directly-writable SpanByte output (network buffer) to bypass the heap budget.
When it happens
Trigger: Reading/scanning/serializing a record whose inline value plus length prefix and metadata exceeds maxHeapAllocationSize, while the output is heap-backed (not a directly writable SpanByte) or the record is object-valued. Replication/checkpoint serialization of an oversized object record is the typical path.
Common situations: Large object values in an object store with maxHeapAllocationSize set too low for the workload; deserializing records written under a larger memory budget; checkpoint or replica copy where the per-record heap budget was not scaled to actual value sizes.
Related errors
- Stream does not support SetLength.
- Iterator address is less than log HeadAddress in memory-scan
- invalid metadata length: {hlri.cookie.Length} < 4
- invalid metadata length: {hlri.cookie.Length} < 12
- Exceeded maximum response size of ({Array.MaxLength:N0}) byt
AI-assisted analysis of microsoft/garnet@951b0fc683 (2026-08-13).
Data as JSON: /api/errors/12c4e1e8c6d68402.
Report an issue: GitHub.