Describe the bug
When a SingleFile (.scdb) database grows beyond a specific size threshold, FreeSpaceManager.FlushAsync throws an InvalidOperationException because the serialized FSM bitmap exceeds its hardcoded 4-page allocation limit.
To Reproduce
- Create a SingleFile database with the default
PageSize = 4096.
- Insert records continuously until the file size approaches ~350MB - 500MB.
- Trigger a flush (e.g., via
ExecuteBatchSQL transaction commit or explicit Flush()).
- See error:
System.InvalidOperationException: FSM too large: 22685 bytes exceeds limit 16384
at SharpCoreDB.Storage.FreeSpaceManager.FlushAsync(CancellationToken cancellationToken)
Expected behavior
The database should support file sizes well beyond 512MB without crashing, or the FSM allocation should scale dynamically with the file size.
Root Cause Analysis
In SingleFileStorageProvider.InitializeNewFile, the FSM is allocated a fixed 4 pages regardless of the expected database size:
header.FsmLength = (ulong)options.PageSize * 4; // 4 pages for FSM
With a 4KB page size, 4 pages = 16,384 bytes.
The FSM L1 bitmap requires 1 bit per allocated page. 16,384 bytes provides exactly 131,072 bits.
131,072 pages × 4KB/page = 512 MB maximum theoretical file size.
In practice, the 64-byte FreeSpaceMapHeader, the L2 extent map, and padding reduce this usable limit. During a 10M record stress test (which peaked at ~338MB / ~86,500 pages), the L1 bitmap (~10.8KB) combined with the L2 extent map exceeded the 16KB limit, triggering the exception.
Current Workaround
During a 10M record stress test (which peaked at ~1.4GB uncompressed), I successfully bypassed this limitation by increasing PageSize from the default 4096 to 16384:
options.PageSize = 16384;
With PageSize = 16384, the fixed 4-page FSM allocation becomes 64KB. This increases the tracking capacity to 524,288 pages, supporting a maximum file size of ~8GB.
However, this is not an ideal permanent solution for downstream users because:
- It significantly increases the WAL memory footprint (default 1024 pages × 16KB = 16MB of RAM just for the WAL).
- It increases internal fragmentation for smaller data blocks.
- It merely raises the hard ceiling rather than eliminating it.
Proposed Fixes
- v1.x Mitigation: Increase the default FSM allocation (e.g., to 64 pages) or expose a
DatabaseOptions.FsmSizePages property to allow users to configure the FSM footprint based on their expected maximum database size.
- v2.0 Architectural Fix: Move the FSM out of the fixed-offset header layout and manage it as a standard dynamic block in the
BlockRegistry (e.g., sys:fsm). This would allow the FSM to grow organically as the database expands, eliminating the hard ceiling entirely.
Desktop (please complete the following information):
- OS: Windows NT 10.0.26200.0
- Runtime: .NET 10.0.3
- Library Version: v1.9.8
Why I haven't submitted a PR for this yet
Unlike the block-level compression feature (PR #344), which was a purely additive extension, fixing this FSM limitation requires an architectural decision that impacts the .scdb file format:
- Option 1 (v1.x Mitigation) is a localized, backward-compatible fix.
- Option 2 (v2.0 Refactor) requires moving the FSM out of the fixed-offset header layout and managing it as a standard dynamic block in the
BlockRegistry. This is a breaking change to the file format.
Given that v2.0 appears to be in development (with the block-registry encryption roadmap mentioned in #341), Option 2 might be the perfect fit for the upcoming release. However, I did not want to invest the effort into a large refactor PR without knowing your architectural preference. I am happy to submit a PR for either approach once you have decided on the direction.
Additional context
Discovered while running a 10M record stress test for the block-level compression PR (#344). The compression feature itself is orthogonal to this issue, but the stress test reliably triggers the FSM ceiling.
Describe the bug
When a SingleFile (
.scdb) database grows beyond a specific size threshold,FreeSpaceManager.FlushAsyncthrows anInvalidOperationExceptionbecause the serialized FSM bitmap exceeds its hardcoded 4-page allocation limit.To Reproduce
PageSize = 4096.ExecuteBatchSQLtransaction commit or explicitFlush()).Expected behavior
The database should support file sizes well beyond 512MB without crashing, or the FSM allocation should scale dynamically with the file size.
Root Cause Analysis
In
SingleFileStorageProvider.InitializeNewFile, the FSM is allocated a fixed 4 pages regardless of the expected database size:With a 4KB page size, 4 pages = 16,384 bytes.
The FSM L1 bitmap requires 1 bit per allocated page. 16,384 bytes provides exactly 131,072 bits.
131,072 pages × 4KB/page = 512 MB maximum theoretical file size.
In practice, the 64-byte
FreeSpaceMapHeader, the L2 extent map, and padding reduce this usable limit. During a 10M record stress test (which peaked at ~338MB / ~86,500 pages), the L1 bitmap (~10.8KB) combined with the L2 extent map exceeded the 16KB limit, triggering the exception.Current Workaround
During a 10M record stress test (which peaked at ~1.4GB uncompressed), I successfully bypassed this limitation by increasing
PageSizefrom the default 4096 to 16384:With
PageSize = 16384, the fixed 4-page FSM allocation becomes 64KB. This increases the tracking capacity to 524,288 pages, supporting a maximum file size of ~8GB.However, this is not an ideal permanent solution for downstream users because:
Proposed Fixes
DatabaseOptions.FsmSizePagesproperty to allow users to configure the FSM footprint based on their expected maximum database size.BlockRegistry(e.g.,sys:fsm). This would allow the FSM to grow organically as the database expands, eliminating the hard ceiling entirely.Desktop (please complete the following information):
Why I haven't submitted a PR for this yet
Unlike the block-level compression feature (PR #344), which was a purely additive extension, fixing this FSM limitation requires an architectural decision that impacts the
.scdbfile format:BlockRegistry. This is a breaking change to the file format.Given that v2.0 appears to be in development (with the block-registry encryption roadmap mentioned in #341), Option 2 might be the perfect fit for the upcoming release. However, I did not want to invest the effort into a large refactor PR without knowing your architectural preference. I am happy to submit a PR for either approach once you have decided on the direction.
Additional context
Discovered while running a 10M record stress test for the block-level compression PR (#344). The compression feature itself is orthogonal to this issue, but the stress test reliably triggers the FSM ceiling.