microsoft/garnet · error · TsavoriteException

{nameof(settings.LogSettings.NumberOfFlushBuffers)} must be

Error message

{nameof(settings.LogSettings.NumberOfFlushBuffers)} must be between {LogSettings.kMinFlushBuffers} and {LogSettings.kMaxFlushBuffers - 1} and a power of 2

What it means

Thrown by ObjectAllocatorImpl when LogSettings.NumberOfFlushBuffers is below kMinFlushBuffers (2), above kMaxFlushBuffers (64), or not a power of two. Flush buffers back parallel page-flush IO, and the overflow pool / index masking assumes a power-of-two count. (Note: the message text says 'kMaxFlushBuffers - 1' but the code allows exactly kMaxFlushBuffers; the bound is effectively 2..64.)

Source

Thrown at libs/storage/Tsavorite/cs/src/core/Allocator/ObjectAllocatorImpl.cs:89

        /// <inheritdoc/>
        public override string ToString() => BaseToString($" (LI {LastIssuedFlushedUntilAddress}, OG {OngoingFlushedUntilAddress}, No {NoFlushUntilAddress})");

        public ObjectAllocatorImpl(AllocatorSettings settings, TStoreFunctions storeFunctions, Func<object, ObjectAllocator<TStoreFunctions>> wrapperCreator)
            : base(settings, storeFunctions, wrapperCreator, settings.logger, transientObjectIdMap: new ObjectIdMap())
        {
            objectLogDevice = settings.LogSettings.ObjectLogDevice;

            maxInlineKeySize = settings.LogSettings.MaxInlineKeySize;
            maxInlineValueSize = settings.LogSettings.MaxInlineValueSize;

            ObjectLogSegmentSize = 1L << settings.LogSettings.ObjectLogSegmentSizeBits;

            freePagePool = new OverflowPool<PageUnit<ObjectPage>>(4, static p => { });
            pageHeaderSize = PageHeader.Size;

            if (settings.LogSettings.NumberOfFlushBuffers < LogSettings.kMinFlushBuffers || settings.LogSettings.NumberOfFlushBuffers > LogSettings.kMaxFlushBuffers || !IsPowerOfTwo(settings.LogSettings.NumberOfFlushBuffers))
                throw new TsavoriteException($"{nameof(settings.LogSettings.NumberOfFlushBuffers)} must be between {LogSettings.kMinFlushBuffers} and {LogSettings.kMaxFlushBuffers - 1} and a power of 2");
            numberOfFlushBuffers = settings.LogSettings.NumberOfFlushBuffers;

            if (settings.LogSettings.NumberOfDeserializationBuffers < LogSettings.kMinDeserializationBuffers || settings.LogSettings.NumberOfDeserializationBuffers > LogSettings.kMaxDeserializationBuffers || !IsPowerOfTwo(settings.LogSettings.NumberOfDeserializationBuffers))
                throw new TsavoriteException($"{nameof(settings.LogSettings.NumberOfDeserializationBuffers)} must be between {LogSettings.kMinDeserializationBuffers} and {LogSettings.kMaxDeserializationBuffers - 1} and a power of 2");
            numberOfDeserializationBuffers = settings.LogSettings.NumberOfDeserializationBuffers;

            if (settings.LogSettings.ObjectLogSegmentSizeBits is < LogSettings.kMinObjectLogSegmentSizeBits or > LogSettings.kMaxSegmentSizeBits)
                throw new TsavoriteException($"{nameof(settings.LogSettings.ObjectLogSegmentSizeBits)} must be between {LogSettings.kMinObjectLogSegmentSizeBits} and {LogSettings.kMaxSegmentSizeBits}");
            objectLogTail = new(0, settings.LogSettings.ObjectLogSegmentSizeBits);

            objectPages = new ObjectPage[BufferSize];
            for (var ii = 0; ii < BufferSize; ii++)
                objectPages[ii] = new();
        }

        /// <summary>Initialize allocator</summary>
        [MethodImpl(MethodImplOptions.NoInlining)]
        protected internal override void Initialize()

View on GitHub (pinned to 951b0fc683)

Solutions

  1. Set NumberOfFlushBuffers to a power of two in [2, 64] (e.g. 4, 8, 16).
  2. Clamp any computed value to the range and round up to the next power of two.
  3. Leave it at the default (4) unless profiling shows flush IO is the bottleneck.

Example fix

// before
logSettings.NumberOfFlushBuffers = Environment.ProcessorCount; // e.g. 12, not power of 2
// after
static int NextPow2(int v) { var p = 1; while (p < v) p <<= 1; return Math.Clamp(p, 2, 64); }
logSettings.NumberOfFlushBuffers = NextPow2(Environment.ProcessorCount);
Defensive patterns

Strategy: validation

Validate before calling

static bool IsValidFlushBuffers(int n) =>
    n >= LogSettings.kMinFlushBuffers && n <= LogSettings.kMaxFlushBuffers && (n & (n - 1)) == 0;
if (!IsValidFlushBuffers(logSettings.NumberOfFlushBuffers))
    throw new ArgumentException($"NumberOfFlushBuffers must be a power of two in [2,64]");

Prevention

When it happens

Trigger: Setting LogSettings.NumberOfFlushBuffers to a non-power-of-two (e.g. 3, 5), to 0 or 1, or above 64, then constructing an object-backed Tsavorite store. Default is 4.

Common situations: Hand-tuning buffer counts for throughput without reading the constraint; copying a config that worked for a different allocator; setting NumberOfFlushBuffers = NumberOfDeserializationBuffers from an unrelated source; passing the raw CPU count which is rarely a power of two.

Related errors


AI-assisted analysis of microsoft/garnet@951b0fc683 (2026-08-13). Data as JSON: /api/errors/43e623a6b38b11c8. Report an issue: GitHub.