Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Hardening

- **Auto engine selection no longer lands on PageBased (production hardening)** - with default
configuration (`StorageEngineType.Auto` + `WorkloadHint.General`) `GetOptimalStorageEngine`
returned PageBased, which is not yet OLTP-ready (measured UPDATE ~26K ops/s vs ~245K ops/s on the
fixed-width Columnar path). Auto selection now routes General / WriteHeavy / unknown hints to
AppendOnly/Columnar; PageBased remains reachable only through an explicit
`StorageEngineType.PageBased` until its UPDATE/DELETE fast paths reach parity. Regression tests
assert the mapping AND that a default database creates Columnar fixed-width PK tables that engage
the single-pass contiguous DELETE path (no `.pages` artifacts). Full suite 1768 tests, 0 failed.

### Performance

- **Fixed-width record layout is now the default for new columnar PK tables (B7)** ÔÇö
Expand Down
21 changes: 14 additions & 7 deletions src/SharpCoreDB/DatabaseConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -563,9 +563,15 @@ public class DatabaseConfig
/// ✅ NEW: Smart storage selection based on workload characteristics!
/// - ReadHeavy: Optimized for SELECT queries → COLUMNAR storage
/// - Analytics: Optimized for aggregates/scans → COLUMNAR storage
/// - WriteHeavy: Optimized for INSERT/UPDATE → PAGE_BASED storage
/// - General: Balanced for mixed workloads → PAGE_BASED storage
///
/// - WriteHeavy: Optimized for INSERT/UPDATE → currently APPEND_ONLY/COLUMNAR too (PageBased is
/// opt-in only via an explicit <see cref="StorageEngineType.PageBased"/>; see the note below)
/// - General: Balanced for mixed workloads → APPEND_ONLY/COLUMNAR storage (the fast, hardened path)
///
/// NOTE (production hardening): the PageBased engine is NOT yet OLTP-ready — measured UPDATE is
/// ~26K ops/s vs ~245K ops/s on the fixed-width Columnar (AppendOnly) path. Auto selection
/// therefore routes the default General workload (and the unknown-hint fallback) to
/// AppendOnly/Columnar until PageBased reaches UPDATE/DELETE parity.
///
/// When StorageEngineType = Auto, the engine is selected based on this hint.
/// </summary>
public WorkloadHint WorkloadHint { get; init; } = WorkloadHint.General;
Expand All @@ -583,14 +589,15 @@ public Interfaces.StorageEngineType GetOptimalStorageEngine()
return StorageEngineType;
}

// Auto-select based on workload hint
// Auto-select based on workload hint. PageBased is deliberately not selected for General /
// unknown hints (and should be treated as opt-in only) until its UPDATE/DELETE fast paths
// reach the fixed-width Columnar engine's parity (see class-level note).
return WorkloadHint switch
{
WorkloadHint.ReadHeavy => Interfaces.StorageEngineType.Columnar,
WorkloadHint.Analytics => Interfaces.StorageEngineType.Columnar,
WorkloadHint.WriteHeavy => Interfaces.StorageEngineType.PageBased,
WorkloadHint.General => Interfaces.StorageEngineType.PageBased,
_ => Interfaces.StorageEngineType.PageBased // Default to PAGE_BASED (safest choice)
WorkloadHint.General => Interfaces.StorageEngineType.AppendOnly,
_ => Interfaces.StorageEngineType.AppendOnly
};
}

Expand Down
96 changes: 96 additions & 0 deletions tests/SharpCoreDB.Tests/DefaultEngineSelectionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// <copyright file="DefaultEngineSelectionTests.cs" company="MPCoreDeveloper">
// Copyright (c) 2026 MPCoreDeveloper. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace SharpCoreDB.Tests;

using Microsoft.Extensions.DependencyInjection;
using SharpCoreDB.DataStructures;
using SharpCoreDB.Interfaces;
using SharpCoreDB.Storage.Hybrid;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Xunit;

/// <summary>
/// Production-hardening regression: a database created with DEFAULT configuration must stay on the
/// fast, hardened path — Columnar (AppendOnly) tables with the fixed-width record layout for PK
/// tables and the single-pass contiguous DELETE fast path — and must never silently land on the
/// not-yet-OLTP-ready PageBased engine through the Auto/WorkloadHint selection.
/// </summary>
public sealed class DefaultEngineSelectionTests : IDisposable
{
private readonly DatabaseFactory _factory;
private readonly string _dirPath;

public DefaultEngineSelectionTests()
{
var services = new ServiceCollection();
services.AddSharpCoreDB();
_factory = services.BuildServiceProvider().GetRequiredService<DatabaseFactory>();
_dirPath = Path.Combine(Path.GetTempPath(), $"SCDB_DefaultEng_{Guid.NewGuid():N}");
}

public void Dispose()
{
try { if (Directory.Exists(_dirPath)) Directory.Delete(_dirPath, true); } catch { }
}

[Fact]
public void DefaultConfig_AutoSelection_PrefersAppendOnlyOverPageBased()
{
// Regression: WorkloadHint.General (the default) and unknown hints used to resolve Auto to
// PageBased, which is not OLTP-ready (measured UPDATE ~26K ops/s vs ~245K on the
// fixed-width Columnar path). Explicit PageBased opt-in must remain possible.
var config = new DatabaseConfig();

Assert.Equal(StorageEngineType.Auto, config.StorageEngineType);
Assert.Equal(StorageEngineType.AppendOnly, config.GetOptimalStorageEngine());

var explicitPageBased = new DatabaseConfig { StorageEngineType = StorageEngineType.PageBased };
Assert.Equal(StorageEngineType.PageBased, explicitPageBased.GetOptimalStorageEngine());
}

[Fact]
public void DefaultDatabase_NewPkTable_UsesColumnarFixedWidthAndContiguousDelete()
{
var db = _factory.Create(_dirPath, "pw", isReadOnly: false, config: new DatabaseConfig());
try
{
db.ExecuteSQL("CREATE TABLE docs (id INTEGER PRIMARY KEY, name TEXT, score REAL)");
Assert.True(db.TryGetTable("docs", out var t));
var table = Assert.IsType<Table>(t);

Assert.Equal(StorageMode.Columnar, table.StorageMode);
Assert.True(table.IsFixedWidthRecords, "default new PK table must use the fixed-width record layout");
Assert.Equal(StorageEngineType.AppendOnly, table.GetStorageEngineType());

var stmts = new List<string>(1000);
for (int i = 1; i <= 1000; i++)
{
stmts.Add($"INSERT INTO docs VALUES ({i}, 'user{i}', {i * 0.5})");
}

db.ExecuteBatchSQL(stmts);
db.Flush();

var dels = new List<string>(500);
for (int i = 1; i <= 500; i++)
{
dels.Add($"DELETE FROM docs WHERE id = {i}");
}

db.ExecuteBatchSQL(dels);
db.Flush();

Assert.Equal(1, table.BulkContiguousDeleteBatches); // fast path engaged on default config
Assert.Equal(500, db.ExecuteQuery("SELECT id FROM docs").Count);

// No PageBased .pages artifact may appear for a default Columnar table.
Assert.False(Directory.EnumerateFiles(_dirPath, "*.pages").Any());
}
finally { (db as IDisposable)?.Dispose(); }
}
}
Loading