Skip to content

Commit c64ceaa

Browse files
author
MPCoreDeveloper
committed
feat(scdb): Phase 2 ExtentAllocator with C# 14 features
Implemented ExtentAllocator for optimized extent allocation with modern C# 14 patterns. Supports BestFit, FirstFit, and WorstFit strategies. Integrated into FreeSpaceManager. Prepared for Phase 6 overflow chains. Features: collection expressions, Lock type, primary constructors ready (in design), AggressiveInlining. Build successful.
1 parent b580f9f commit c64ceaa

File tree

6 files changed

+750
-5
lines changed

6 files changed

+750
-5
lines changed

docs/scdb/PHASE2_DESIGN.md

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# SCDB Phase 2: Extent Allocator Design
2+
3+
**Created:** 2026-01-28
4+
**Status:** 📝 Design Phase
5+
6+
---
7+
8+
## 🎯 Goals
9+
10+
1. **Public API** for page/extent allocation
11+
2. **Extent structure** for contiguous block tracking
12+
3. **Allocation optimization** - O(log n) lookup
13+
4. **Defragmentation** support
14+
15+
---
16+
17+
## 📐 API Design
18+
19+
### Public Methods to Add to FreeSpaceManager
20+
21+
```csharp
22+
// Single page allocation
23+
public ulong AllocatePage();
24+
25+
// Extent allocation (returns Extent struct)
26+
public Extent AllocateExtent(int pageCount);
27+
28+
// Single page free
29+
public void FreePage(ulong pageId);
30+
31+
// Extent free
32+
public void FreeExtent(Extent extent);
33+
34+
// Statistics with more detail
35+
public FsmStatistics GetStatistics();
36+
```
37+
38+
---
39+
40+
## 📦 Data Structures
41+
42+
### Extent Structure
43+
44+
```csharp
45+
/// <summary>
46+
/// Represents a contiguous block of pages.
47+
/// </summary>
48+
[StructLayout(LayoutKind.Sequential, Pack = 1)]
49+
public readonly struct Extent
50+
{
51+
public readonly ulong StartPage;
52+
public readonly int PageCount;
53+
54+
public Extent(ulong startPage, int pageCount)
55+
{
56+
StartPage = startPage;
57+
PageCount = pageCount;
58+
}
59+
60+
public ulong EndPage => StartPage + (ulong)PageCount - 1;
61+
public ulong SizeBytes(int pageSize) => (ulong)PageCount * (ulong)pageSize;
62+
}
63+
```
64+
65+
### FsmStatistics Structure
66+
67+
```csharp
68+
/// <summary>
69+
/// FSM statistics for monitoring and debugging.
70+
/// </summary>
71+
public readonly record struct FsmStatistics
72+
{
73+
public required long TotalPages { get; init; }
74+
public required long FreePages { get; init; }
75+
public required long UsedPages { get; init; }
76+
public required long FreeSpace { get; init; }
77+
public required long LargestExtent { get; init; }
78+
public required int ExtentCount { get; init; }
79+
public required double FragmentationPercent { get; init; }
80+
}
81+
```
82+
83+
---
84+
85+
## 🚀 Implementation Plan
86+
87+
### Step 1: Add Public API Methods
88+
- `AllocatePage()` - wrapper around `AllocatePages(1)`
89+
- `FreePage()` - wrapper around `FreePages(offset, 1)`
90+
- `AllocateExtent()` - returns Extent struct
91+
- `FreeExtent()` - accepts Extent struct
92+
93+
### Step 2: Create Extent Structure
94+
- Define in `src/SharpCoreDB/Storage/Scdb/Extent.cs`
95+
- ReadOnly struct for immutability
96+
- Helper properties (EndPage, SizeBytes)
97+
98+
### Step 3: Enhance Statistics
99+
- Add TotalPages, UsedPages fields
100+
- Calculate FragmentationPercent
101+
- Return ExtentCount and LargestExtent
102+
103+
### Step 4: Optimize Allocation
104+
- Use L2 extent list for fast contiguous lookup
105+
- Sort extents by size for O(log n) search
106+
- Implement best-fit allocation strategy
107+
108+
---
109+
110+
## 📊 Performance Targets
111+
112+
| Operation | Current | Target | Notes |
113+
|-----------|---------|--------|-------|
114+
| AllocatePage | O(n) scan | O(log n) | Use extent list |
115+
| AllocateExtent | O(n) scan | O(log n) | Best-fit from sorted list |
116+
| FreePage | O(1) | O(1) | No change needed |
117+
| GetStatistics | O(n) | O(1) | Cache values |
118+
119+
---
120+
121+
## 🧪 Testing Plan
122+
123+
### Unit Tests
124+
1. **Single Page Operations**
125+
- Allocate/free single page
126+
- Round-trip persistence
127+
128+
2. **Extent Operations**
129+
- Allocate extent of various sizes (1, 10, 100, 1000 pages)
130+
- Free extent
131+
- Verify contiguous allocation
132+
133+
3. **Fragmentation**
134+
- Create fragmentation pattern
135+
- Measure fragmentation percent
136+
- Verify extent coalescing
137+
138+
4. **Performance**
139+
- Benchmark allocation speed
140+
- Verify O(log n) complexity
141+
- Compare with Phase 1
142+
143+
### Integration Tests
144+
1. **Database Integration**
145+
- Use FSM through IStorageProvider
146+
- Verify metadata persistence
147+
- Test with real workload
148+
149+
---
150+
151+
## 📝 Implementation Checklist
152+
153+
- [ ] Create `Extent.cs` structure
154+
- [ ] Create `FsmStatistics.cs` structure
155+
- [ ] Add `AllocatePage()` method
156+
- [ ] Add `FreePage()` method
157+
- [ ] Add `AllocateExtent()` method
158+
- [ ] Add `FreeExtent()` method
159+
- [ ] Enhance `GetStatistics()` method
160+
- [ ] Optimize extent allocation (best-fit)
161+
- [ ] Add fragmentation calculation
162+
- [ ] Write unit tests
163+
- [ ] Write benchmarks
164+
- [ ] Update documentation
165+
166+
---
167+
168+
## 🎯 Success Criteria
169+
170+
- [x] Public API complete
171+
- [ ] All tests passing
172+
- [ ] Page allocation <1ms
173+
- [ ] Extent allocation <1ms
174+
- [ ] Fragmentation tracking accurate
175+
- [ ] Documentation updated
176+
177+
---
178+
179+
**Status:** Ready for implementation
180+
**Next:** Step 1 - Create Extent structure

src/SharpCoreDB/Storage/FreeSpaceManager.cs

Lines changed: 134 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ namespace SharpCoreDB.Storage;
1010
using System.Buffers;
1111
using System.Collections.Generic;
1212
using System.Diagnostics;
13+
using System.Linq;
1314
using System.Runtime.InteropServices;
1415
using System.Threading;
1516
using System.Threading.Tasks;
1617

18+
// ✅ SCDB Phase 2: Public type alias for user-facing API
19+
using Extent = SharpCoreDB.Storage.Scdb.FreeExtent;
20+
1721
/// <summary>
1822
/// Free Space Map (FSM) for O(1) page allocation.
1923
/// Uses two-level bitmap inspired by PostgreSQL.
@@ -30,6 +34,10 @@ internal sealed class FreeSpaceManager : IDisposable
3034
private readonly BitArray _l1Bitmap; // 1 bit per page
3135
private readonly List<FreeExtent> _l2Extents; // Large free extents
3236
private readonly Lock _allocationLock = new();
37+
38+
// ✅ SCDB Phase 2: ExtentAllocator for optimized extent allocation
39+
private readonly ExtentAllocator _extentAllocator;
40+
3341
private bool _isDirty;
3442
private bool _disposed;
3543
private ulong _totalPages;
@@ -51,10 +59,130 @@ public FreeSpaceManager(SingleFileStorageProvider provider, ulong fsmOffset, ulo
5159
_totalPages = 0;
5260
_freePages = 0;
5361

62+
// ✅ SCDB Phase 2: Initialize ExtentAllocator
63+
_extentAllocator = new ExtentAllocator
64+
{
65+
Strategy = AllocationStrategy.BestFit // Default: minimize fragmentation
66+
};
67+
5468
// Load existing FSM from disk
5569
LoadFsm();
5670
}
5771

72+
// ========================================
73+
// ✅ SCDB Phase 2: Public API for page/extent allocation
74+
// ========================================
75+
76+
/// <summary>
77+
/// Allocates a single page.
78+
/// </summary>
79+
/// <returns>Page ID of allocated page.</returns>
80+
public ulong AllocatePage()
81+
{
82+
var offset = AllocatePages(1);
83+
return offset / (ulong)_pageSize; // Convert byte offset to page ID
84+
}
85+
86+
/// <summary>
87+
/// Allocates a contiguous extent of pages.
88+
/// ✅ SCDB Phase 2: Uses ExtentAllocator for optimized allocation.
89+
/// </summary>
90+
/// <param name="pageCount">Number of pages to allocate.</param>
91+
/// <returns>Extent representing the allocated pages.</returns>
92+
public Extent AllocateExtent(int pageCount)
93+
{
94+
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(pageCount);
95+
96+
lock (_allocationLock)
97+
{
98+
// ✅ Try to allocate from existing free extents first (O(log n))
99+
var existingExtent = _extentAllocator.Allocate(pageCount);
100+
101+
if (existingExtent.HasValue)
102+
{
103+
// Found suitable extent, mark pages as allocated
104+
for (var i = 0UL; i < (ulong)pageCount; i++)
105+
{
106+
_l1Bitmap.Set((int)(existingExtent.Value.StartPage + i), true);
107+
}
108+
109+
_freePages -= (ulong)pageCount;
110+
_isDirty = true;
111+
112+
return new Extent(existingExtent.Value.StartPage, (ulong)pageCount);
113+
}
114+
115+
// No suitable extent found, allocate new pages
116+
var offset = AllocatePages(pageCount);
117+
var startPage = offset / (ulong)_pageSize;
118+
119+
return new Extent(startPage, (ulong)pageCount);
120+
}
121+
}
122+
123+
/// <summary>
124+
/// Frees a single page.
125+
/// </summary>
126+
/// <param name="pageId">Page ID to free.</param>
127+
public void FreePage(ulong pageId)
128+
{
129+
var offset = pageId * (ulong)_pageSize;
130+
FreePages(offset, 1);
131+
}
132+
133+
/// <summary>
134+
/// Frees an extent of pages.
135+
/// ✅ SCDB Phase 2: Uses ExtentAllocator for coalescing.
136+
/// </summary>
137+
/// <param name="extent">Extent to free.</param>
138+
public void FreeExtent(Extent extent)
139+
{
140+
var offset = extent.StartPage * (ulong)_pageSize;
141+
FreePages(offset, (int)extent.Length);
142+
143+
// ✅ Add to ExtentAllocator for future reuse
144+
lock (_allocationLock)
145+
{
146+
_extentAllocator.Free(extent);
147+
_isDirty = true;
148+
}
149+
}
150+
151+
/// <summary>
152+
/// Gets comprehensive FSM statistics.
153+
/// ✅ SCDB Phase 2: Includes ExtentAllocator metrics.
154+
/// </summary>
155+
/// <returns>Statistics including fragmentation and extent information.</returns>
156+
public FsmStatistics GetDetailedStatistics()
157+
{
158+
lock (_allocationLock)
159+
{
160+
var usedPages = _totalPages - _freePages;
161+
var largestExtent = _extentAllocator.GetLargestExtentSize();
162+
var extentCount = _extentAllocator.ExtentCount;
163+
164+
// Calculate fragmentation percentage
165+
// Fragmentation = (1 - (largest_extent / free_pages)) * 100
166+
var fragmentation = 0.0;
167+
if (_freePages > 0)
168+
{
169+
fragmentation = (1.0 - ((double)largestExtent / (double)_freePages)) * 100.0;
170+
fragmentation = Math.Max(0, Math.Min(100, fragmentation)); // Clamp 0-100
171+
}
172+
173+
return new FsmStatistics
174+
{
175+
TotalPages = (long)_totalPages,
176+
FreePages = (long)_freePages,
177+
UsedPages = (long)usedPages,
178+
FreeSpace = (long)_freePages * _pageSize,
179+
LargestExtent = (long)largestExtent,
180+
ExtentCount = extentCount,
181+
FragmentationPercent = fragmentation
182+
};
183+
}
184+
}
185+
58186
public ulong AllocatePages(int count)
59187
{
60188
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
@@ -157,7 +285,7 @@ public async Task FlushAsync(CancellationToken cancellationToken = default)
157285

158286
// Calculate L2 extent size
159287
var extentCount = _l2Extents.Count;
160-
var extentSizeBytes = extentCount * FreeExtent.SIZE;
288+
var extentSizeBytes = extentCount * Scdb.FreeExtent.SIZE;
161289

162290
// Total size
163291
totalSize = FreeSpaceMapHeader.SIZE + bitmapSizeBytes + sizeof(int) + extentSizeBytes;
@@ -199,7 +327,7 @@ public async Task FlushAsync(CancellationToken cancellationToken = default)
199327
for (var i = 0; i < extentCount; i++)
200328
{
201329
var extent = _l2Extents[i];
202-
var extentSpan = span.Slice(extentOffset + (i * FreeExtent.SIZE), FreeExtent.SIZE);
330+
var extentSpan = span.Slice(extentOffset + (i * Scdb.FreeExtent.SIZE), Scdb.FreeExtent.SIZE);
203331
MemoryMarshal.Write(extentSpan, in extent);
204332
}
205333

@@ -233,6 +361,7 @@ public void Dispose()
233361
}
234362
finally
235363
{
364+
_extentAllocator?.Dispose(); // ✅ SCDB Phase 2: Dispose ExtentAllocator
236365
_disposed = true;
237366
}
238367
}
@@ -366,7 +495,7 @@ private void LoadFsm()
366495
// Read L2 extents
367496
if (extentCount > 0)
368497
{
369-
var extentBufferSize = extentCount * FreeExtent.SIZE;
498+
var extentBufferSize = extentCount * Scdb.FreeExtent.SIZE;
370499
var extentBuffer = ArrayPool<byte>.Shared.Rent(extentBufferSize);
371500
try
372501
{
@@ -375,8 +504,8 @@ private void LoadFsm()
375504

376505
for (var i = 0; i < extentCount; i++)
377506
{
378-
var offset = i * FreeExtent.SIZE;
379-
var extent = MemoryMarshal.Read<FreeExtent>(extentSpan[offset..]);
507+
var offset = i * Scdb.FreeExtent.SIZE;
508+
var extent = MemoryMarshal.Read<Scdb.FreeExtent>(extentSpan[offset..]);
380509
_l2Extents.Add(extent);
381510
}
382511
}

0 commit comments

Comments
 (0)