{"record":{"id":"43e623a6b38b11c8","repo":"microsoft/garnet","slug":"nameof-settings-logsettings-numberofflushbuffers","errorCode":null,"errorMessage":"{nameof(settings.LogSettings.NumberOfFlushBuffers)} must be between {LogSettings.kMinFlushBuffers} and {LogSettings.kMaxFlushBuffers - 1} and a power of 2","messagePattern":"(.+?) must be between (.+?) and (.+?) and a power of 2","errorType":"validation","errorClass":"TsavoriteException","httpStatus":null,"severity":"error","filePath":"libs/storage/Tsavorite/cs/src/core/Allocator/ObjectAllocatorImpl.cs","lineNumber":89,"sourceCode":"\n        /// <inheritdoc/>\n        public override string ToString() => BaseToString($\" (LI {LastIssuedFlushedUntilAddress}, OG {OngoingFlushedUntilAddress}, No {NoFlushUntilAddress})\");\n\n        public ObjectAllocatorImpl(AllocatorSettings settings, TStoreFunctions storeFunctions, Func<object, ObjectAllocator<TStoreFunctions>> wrapperCreator)\n            : base(settings, storeFunctions, wrapperCreator, settings.logger, transientObjectIdMap: new ObjectIdMap())\n        {\n            objectLogDevice = settings.LogSettings.ObjectLogDevice;\n\n            maxInlineKeySize = settings.LogSettings.MaxInlineKeySize;\n            maxInlineValueSize = settings.LogSettings.MaxInlineValueSize;\n\n            ObjectLogSegmentSize = 1L << settings.LogSettings.ObjectLogSegmentSizeBits;\n\n            freePagePool = new OverflowPool<PageUnit<ObjectPage>>(4, static p => { });\n            pageHeaderSize = PageHeader.Size;\n\n            if (settings.LogSettings.NumberOfFlushBuffers < LogSettings.kMinFlushBuffers || settings.LogSettings.NumberOfFlushBuffers > LogSettings.kMaxFlushBuffers || !IsPowerOfTwo(settings.LogSettings.NumberOfFlushBuffers))\n                throw new TsavoriteException($\"{nameof(settings.LogSettings.NumberOfFlushBuffers)} must be between {LogSettings.kMinFlushBuffers} and {LogSettings.kMaxFlushBuffers - 1} and a power of 2\");\n            numberOfFlushBuffers = settings.LogSettings.NumberOfFlushBuffers;\n\n            if (settings.LogSettings.NumberOfDeserializationBuffers < LogSettings.kMinDeserializationBuffers || settings.LogSettings.NumberOfDeserializationBuffers > LogSettings.kMaxDeserializationBuffers || !IsPowerOfTwo(settings.LogSettings.NumberOfDeserializationBuffers))\n                throw new TsavoriteException($\"{nameof(settings.LogSettings.NumberOfDeserializationBuffers)} must be between {LogSettings.kMinDeserializationBuffers} and {LogSettings.kMaxDeserializationBuffers - 1} and a power of 2\");\n            numberOfDeserializationBuffers = settings.LogSettings.NumberOfDeserializationBuffers;\n\n            if (settings.LogSettings.ObjectLogSegmentSizeBits is < LogSettings.kMinObjectLogSegmentSizeBits or > LogSettings.kMaxSegmentSizeBits)\n                throw new TsavoriteException($\"{nameof(settings.LogSettings.ObjectLogSegmentSizeBits)} must be between {LogSettings.kMinObjectLogSegmentSizeBits} and {LogSettings.kMaxSegmentSizeBits}\");\n            objectLogTail = new(0, settings.LogSettings.ObjectLogSegmentSizeBits);\n\n            objectPages = new ObjectPage[BufferSize];\n            for (var ii = 0; ii < BufferSize; ii++)\n                objectPages[ii] = new();\n        }\n\n        /// <summary>Initialize allocator</summary>\n        [MethodImpl(MethodImplOptions.NoInlining)]\n        protected internal override void Initialize()","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectAllocatorImpl.cs#L71-L107","documentation":"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.)","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set NumberOfFlushBuffers to a power of two in [2, 64] (e.g. 4, 8, 16).","Clamp any computed value to the range and round up to the next power of two.","Leave it at the default (4) unless profiling shows flush IO is the bottleneck."],"exampleFix":"// before\nlogSettings.NumberOfFlushBuffers = Environment.ProcessorCount; // e.g. 12, not power of 2\n// after\nstatic int NextPow2(int v) { var p = 1; while (p < v) p <<= 1; return Math.Clamp(p, 2, 64); }\nlogSettings.NumberOfFlushBuffers = NextPow2(Environment.ProcessorCount);","handlingStrategy":"validation","validationCode":"static bool IsValidFlushBuffers(int n) =>\n    n >= LogSettings.kMinFlushBuffers && n <= LogSettings.kMaxFlushBuffers && (n & (n - 1)) == 0;\nif (!IsValidFlushBuffers(logSettings.NumberOfFlushBuffers))\n    throw new ArgumentException($\"NumberOfFlushBuffers must be a power of two in [2,64]\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never set buffer counts directly from Environment.ProcessorCount without rounding to a power of two.","Keep NumberOfFlushBuffers at the default (4) unless profiling dictates otherwise.","Add a config-validation step at startup that checks all LogSettings numeric constraints."],"tags":["configuration","object-log","flush","validation"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}