{"record":{"id":"f5bb881102c3ec79","repo":"microsoft/garnet","slug":"allocator-with-sector-size-sectorsize-cannot-flu","errorCode":null,"errorMessage":"Allocator with sector size {sectorSize} cannot flush to device with sector size {device.SectorSize}","messagePattern":"Allocator with sector size (.+?) cannot flush to device with sector size (.+?)","errorType":"exception","errorClass":"TsavoriteException","httpStatus":null,"severity":"error","filePath":"libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorBase.cs","lineNumber":489,"sourceCode":"\n            if (isEpochOwned)\n                epoch.Dispose();\n            bufferPool.Free();\n\n            flushEvent.Dispose();\n            notifyFlushedUntilAddressTcs?.TrySetCanceled();\n            notifyFlushedUntilAddressTcs = null;\n\n            onReadOnlyObserver?.OnCompleted();\n            onEvictionObserver?.OnCompleted();\n        }\n\n        #endregion abstract and virtual methods\n\n        private protected void VerifyCompatibleSectorSize(IDevice device)\n        {\n            if (sectorSize % device.SectorSize != 0)\n                throw new TsavoriteException($\"Allocator with sector size {sectorSize} cannot flush to device with sector size {device.SectorSize}\");\n        }\n\n        /// <summary>\n        /// This writes data from a page (or pages) for allocators that support only inline data.\n        /// </summary>\n        /// <param name=\"alignedSourceAddress\">The source address, aligned to start of allocator page</param>\n        /// <param name=\"alignedDestinationAddress\">The destination address, aligned to start of allocator page</param>\n        /// <param name=\"numBytesToWrite\">Number of bytes to be written, based on allocator page range</param>\n        /// <param name=\"callback\">The callback for the operation</param>\n        /// <param name=\"asyncResult\">The callback state information, including information for the flush operation</param>\n        /// <param name=\"device\">The device to write to</param>\n        [MethodImpl(MethodImplOptions.NoInlining)]\n        internal void WriteInlinePageAsync<TContext>(IntPtr alignedSourceAddress, ulong alignedDestinationAddress, uint numBytesToWrite,\n                DeviceIOCompletionCallback callback, PageAsyncFlushResult<TContext> asyncResult, IDevice device)\n        {\n            if (asyncResult.partial)\n            {\n                // Write only required bytes within the page","sourceCodeStart":471,"sourceCodeEnd":507,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorBase.cs#L471-L507","documentation":"Thrown by AllocatorBase.VerifyCompatibleSectorSize(IDevice) before flushing pages to a storage device. The allocator performs sector-aligned I/O using the sector size it captured from its primary LogDevice at construction; if it is asked to flush to a different device whose SectorSize does not evenly divide that captured value, alignment would be violated and on-disk data could be corrupted, so the write is rejected up front. The check is `sectorSize % device.SectorSize != 0`, where `sectorSize` is the allocator's own (inherited from the original LogDevice).","triggerScenarios":"A flush or checkpoint path routes data to an IDevice whose SectorSize is not a divisor of the allocator's sectorSize. For example the main hybrid log was opened on a device with sectorSize 512, but the operation flushes to a device (or a custom IDevice wrapper, or a copy-to device) reporting SectorSize 4096; 512 % 4096 != 0, so the guard fires.","commonSituations":"Mixing backing stores in one store (local SSD for the log, Azure/network device for checkpoints or a secondary copy); wrapping a real device in a custom IDevice whose SectorSize getter returns an unrelated power of two; upgrading a device layer that changed its reported SectorSize (e.g. a 512-native device now reporting 4096 logical sectors) without re-creating the allocator.","solutions":["Make the target device's SectorSize a divisor of the allocator's sectorSize. Easiest: ensure the primary LogDevice and any device the same allocator flushes to report the same SectorSize.","If you control the custom IDevice, set its SectorSize to a value that divides the main device's (e.g. 512 when the main device is 512 or 4096), or match it exactly.","Recreate the Tsavorite store so the allocator captures the desired sectorSize from a device consistent with every device it will flush to.","For checkpoint-to-disk flows, point checkpoints at a device constructed with the same sector size as the log device (Devices.CreateLogDevice uses the same sector-size logic for both)."],"exampleFix":"// before: main log on 512-byte device, checkpoint to a 4096-byte device\nvar logDev = Devices.CreateLogDevice(\"log\", sectorSize: 512);\nvar ckptDev = new MyCustomDevice(sectorSize: 4096);\n// -> VerifyCompatibleSectorSize throws during flush\n\n// after: align sector sizes\nvar logDev = Devices.CreateLogDevice(\"log\", sectorSize: 512);\nvar ckptDev = new MyCustomDevice(sectorSize: 512); // divides/matches allocator sectorSize","handlingStrategy":"validation","validationCode":"// Before flushing the allocator to a secondary device, confirm sector compatibility.\n// 'allocatorSectorSize' is the sector size the allocator captured from its primary LogDevice\n// (expose it via your wrapper, or read device.SectorSize of the original log device).\nstatic bool CanFlushToDevice(int allocatorSectorSize, IDevice target)\n    => target is not null && allocatorSectorSize % target.SectorSize == 0;\n\n// usage\nif (!CanFlushToDevice(logDevice.SectorSize, checkpointDevice))\n    throw new InvalidOperationException(\n        $\"Cannot flush: allocator sector {logDevice.SectorSize} not divisible by device sector {checkpointDevice.SectorSize}\");","typeGuard":null,"tryCatchPattern":"try\n{\n    await store.FlushAsync();   // or checkpoint to the secondary device\n}\ncatch (TsavoriteException ex) when (ex.Message.Contains(\"cannot flush to device with sector size\"))\n{\n    // Sector-size mismatch: reconcile device SectorSize values, then retry on a rebuilt store.\n    logger.Error(\"Sector-size mismatch on flush; rebuild store with matching device sector sizes: {Msg}\", ex.Message);\n    throw;\n}","preventionTips":["Construct every device the store will flush to with the same SectorSize as the primary LogDevice.","For custom IDevice implementations, return a SectorSize that divides the main device's SectorSize.","Centralize device creation in one factory so sector sizes cannot drift."],"tags":["storage","io","configuration","sector-size","device"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}