{"record":{"id":"c35598cd48b36c50","repo":"microsoft/garnet","slug":"page-size-must-be-at-least-of-device-sector-size","errorCode":null,"errorMessage":"Page size must be at least of device sector size ({sectorSize} bytes). Set PageSizeBits accordingly.","messagePattern":"Page size must be at least of device sector size \\((.+?) bytes\\)\\. Set PageSizeBits accordingly\\.","errorType":"validation","errorClass":"TsavoriteException","httpStatus":null,"severity":"critical","filePath":"libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorBase.cs","lineNumber":653,"sourceCode":"            // Segment size\n            LogSegmentSizeBits = logSettings.SegmentSizeBits;\n            SegmentSize = 1L << LogSegmentSizeBits;\n            if (SegmentSize < PageSize)\n                throw new TsavoriteException($\"Segment ({SegmentSize}) must be at least of page size ({PageSize})\");\n\n            PageStatusIndicator = new FullPageStatus[BufferSize];\n\n            if (!IsNullDevice)\n            {\n                PendingFlush = new PendingFlushList[BufferSize];\n                for (int i = 0; i < BufferSize; i++)\n                    PendingFlush[i] = new PendingFlushList();\n            }\n            device = logSettings.LogDevice;\n            sectorSize = (int)device.SectorSize;\n\n            if (PageSize < sectorSize)\n                throw new TsavoriteException($\"Page size must be at least of device sector size ({sectorSize} bytes). Set PageSizeBits accordingly.\");\n\n            AlignedPageSizeBytes = RoundUp(PageSize, sectorSize);\n\n            if (BufferSize > 0)\n            {\n                pageArrays = new byte[BufferSize][];\n                pagePointersArray = GC.AllocateArray<long>(BufferSize, pinned: true);\n                pagePointers = (long*)Unsafe.AsPointer(ref pagePointersArray[0]);\n            }\n        }\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        internal long GetPhysicalAddress(long logicalAddress)\n        {\n            // if (disposed)    // TODO: Clean up dispose sequence\n            //     ThrowTsavoriteException(\"GetPhysicalAddress called when disposed\");\n\n            // Index of page within the circular buffer, and offset on the page.","sourceCodeStart":635,"sourceCodeEnd":671,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorBase.cs#L635-L671","documentation":"Thrown during allocator initialization when the configured page size (PageSize, derived from PageSizeBits) is smaller than the storage device's reported sector size. Tsavorite aligns page flushes to the device sector boundary, so a page that cannot hold even one sector is unusable. The check guards AlignedPageSizeBytes = RoundUp(PageSize, sectorSize), which would otherwise round a sub-sector page up to a meaningless size.","triggerScenarios":"Constructing a TsavoriteKV with LogSettings.PageSizeBits set so low that (1 << PageSizeBits) < logDevice.SectorSize. Hits AllocatorBase.Initialize() at startup. Common with PageSizeBits=12 (4KB) against a 4Kn (4096-byte native) device that reports a larger logical sector, or with a custom IDevice whose SectorSize is overridden to a large value.","commonSituations":"Copying a config tuned for a 512-byte-sector disk onto a 4K-native (4Kn) advanced-format disk; using an Azure Managed Disk / NVMe device that reports 4096-byte physical sectors; a custom MemoryDevice/NullDevice set with an inflated SectorSize; lowering PageSizeBits to reduce memory footprint without accounting for the underlying device.","solutions":["Raise LogSettings.PageSizeBits so that (1 << PageSizeBits) >= device.SectorSize (e.g. use the default 25, or at least 12 for 4KB sectors, 13+ for safety).","Inspect the actual device sector size (logDevice.SectorSize) before constructing the store and pick PageSizeBits accordingly.","If using a custom IDevice, verify its SectorSize reflects the real hardware and is not accidentally inflated."],"exampleFix":"// before\nvar logSettings = new LogSettings { PageSizeBits = 12, LogDevice = device };\n// after (device.SectorSize is 4096, so 1<<12==4096 only matches exactly; give headroom)\nvar sectorBits = (int)Math.Ceiling(Math.Log2(device.SectorSize));\nvar logSettings = new LogSettings { PageSizeBits = Math.Max(sectorBits, 25), LogDevice = device };","handlingStrategy":"validation","validationCode":"// Validate before constructing the store\nvar sectorSize = (int)logDevice.SectorSize;\nvar pageSize = 1 << logSettings.PageSizeBits;\nif (pageSize < sectorSize)\n    throw new ArgumentException($\"PageSizeBits {logSettings.PageSizeBits} ({pageSize}B) < device sector size {sectorSize}B\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always derive PageSizeBits from the device's actual SectorSize, not a hard-coded constant.","Log device.SectorSize at startup to catch mismatches early.","Prefer the default PageSizeBits (25) unless you have measured memory pressure."],"tags":["configuration","storage","device","initialization"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}