From d1c1931c42b5597b56cd89631feeaea525fb381a Mon Sep 17 00:00:00 2001 From: MPCoreDeveloper Date: Fri, 4 Sep 2026 13:20:54 +0200 Subject: [PATCH] fix(config): Auto engine selection must not land on PageBased (hardening) With default config (StorageEngineType.Auto + WorkloadHint.General), GetOptimalStorageEngine returned PageBased. The PageBased engine is not yet OLTP-ready (measured UPDATE ~26K ops/s vs ~245K ops/s on the fixed-width Columnar path), so every default-created database risked ending up on the slow engine wherever Auto selection is honored. Auto now routes General / WriteHeavy / unknown hints to AppendOnly/Columnar; PageBased stays reachable only through an explicit StorageEngineType.PageBased until its UPDATE/DELETE fast paths reach parity. Regression: DefaultEngineSelectionTests assert (1) the mapping itself and (2) that a DEFAULT database creates a Columnar fixed-width PK table that engages the single-pass contiguous DELETE fast path and leaves no .pages artifacts. Full suite 1768 tests, 0 failed. --- docs/CHANGELOG.md | 11 +++ src/SharpCoreDB/DatabaseConfig.cs | 21 ++-- .../DefaultEngineSelectionTests.cs | 96 +++++++++++++++++++ 3 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 tests/SharpCoreDB.Tests/DefaultEngineSelectionTests.cs diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3b61d565..3d0c7356 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -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)** ÔÇö diff --git a/src/SharpCoreDB/DatabaseConfig.cs b/src/SharpCoreDB/DatabaseConfig.cs index 34babc4d..d189fe44 100644 --- a/src/SharpCoreDB/DatabaseConfig.cs +++ b/src/SharpCoreDB/DatabaseConfig.cs @@ -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 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. /// public WorkloadHint WorkloadHint { get; init; } = WorkloadHint.General; @@ -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 }; } diff --git a/tests/SharpCoreDB.Tests/DefaultEngineSelectionTests.cs b/tests/SharpCoreDB.Tests/DefaultEngineSelectionTests.cs new file mode 100644 index 00000000..855ccdc2 --- /dev/null +++ b/tests/SharpCoreDB.Tests/DefaultEngineSelectionTests.cs @@ -0,0 +1,96 @@ +// +// Copyright (c) 2026 MPCoreDeveloper. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// +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; + +/// +/// 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. +/// +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(); + _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(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(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(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(); } + } +}