Database Modes

CSharpDB supports three storage modes — file-based, in-memory, and hybrid — each tuned for different workloads. All three share the same SQL engine, Collection API, and transaction model.

File-Based

Default mode

Standard persistent database backed by a single file on disk. All commits are durable immediately via the Write-Ahead Log.

Durable Production

In-Memory

Ephemeral mode

Entire database lives in memory with no disk I/O. Fastest option for testing, caching, or temporary workloads. Can save snapshots to disk on demand.

Fast Non-persistent

Hybrid

Lazy-resident mode

Durable backing file with aggressive page caching. Touched pages stay resident across checkpoints. Best when a hot working set fits in memory but the full dataset doesn't.

Durable Cached

File-Based (Default)

The standard mode for production workloads. Uses FileStorageDevice for true async I/O via RandomAccess.ReadAsync / RandomAccess.WriteAsync. All committed data is durable on disk.

// Simple — uses default options
await using var db = await Database.OpenAsync("myapp.db");

// With custom options
var options = new DatabaseOptions
{
    StorageEngineOptions = new StorageEngineOptionsBuilder()
        .WithCacheSize(4096)
        .WithLockTimeout(TimeSpan.FromSeconds(30))
        .WithCheckpointPolicy(new FrameCountCheckpointPolicy(1000))
        .Build()
};
await using var db = await Database.OpenAsync("myapp.db", options);

How It Works

  • Storage: Single .db file with 4 KB slotted pages, plus a .db.wal file for the Write-Ahead Log
  • Page Cache: Unbounded DictionaryPageCache by default, or bounded LruPageCache when MaxCachedPages is set
  • WAL: Full WAL with frame-level checksums. Pages written to WAL before commit, then merged to the main file during checkpoint
  • Durability: Every committed transaction is immediately durable via FlushToDisk()
  • Recovery: On open, the WAL is scanned to recover any committed frames not yet checkpointed
  • Checkpoint: Configurable policies — frame count, time interval, WAL size, or composite. Pages are evicted from cache after checkpoint by default

Configuration Options

OptionDefaultDescription
MaxCachedPagesUnboundedLimits page cache to N pages using LRU eviction
WriterLockTimeout5 secondsMaximum wait time for acquiring the exclusive write lock
CheckpointPolicyFrame countWhen to merge WAL pages back to the main file
AutoCheckpointExecutionModeBackgroundRun checkpoint inline (foreground) or on a background thread
UseMemoryMappedReadsfalseOpt-in memory-mapped read path for clean main-file pages
EnableSequentialLeafReadAheadtrueSpeculative read-ahead during sequential B+tree scans
Best for: Production databases, persistent storage, any workload that requires crash durability.

In-Memory

The entire database lives in memory with no disk I/O. Uses MemoryStorageDevice — a thread-safe in-memory byte buffer. Fastest option but data is lost when the process exits unless explicitly saved.

// Create a fresh in-memory database
await using var db = await Database.OpenInMemoryAsync();

// Load an existing file into memory
await using var db = await Database.LoadIntoMemoryAsync("myapp.db");

// Save committed state to disk at any time
await db.SaveToFileAsync("snapshot.db");

How It Works

  • Storage: MemoryStorageDevice — an in-memory byte[] buffer with thread-safe access
  • Page Cache: DictionaryPageCache (unbounded, no eviction). All pages remain resident
  • WAL: Full WAL implementation (MemoryWriteAheadLog) using the same frame format, stored in memory. Ensures transaction atomicity
  • Durability: Not persistent by default — data lost on process exit
  • Snapshot Save: SaveToFileAsync(path) writes the complete database image to disk
  • Load from Disk: LoadIntoMemoryAsync(path) reads a file (and its WAL) into memory

Shared In-Memory (ADO.NET)

Multiple ADO.NET connections can share a single in-memory database instance. Useful for test scenarios where several connections need to see the same data.

// Multiple connections share the same in-memory instance
var connStr = "Data Source=:memory:testdb";

await using var conn1 = new CSharpDbConnection(connStr);
await using var conn2 = new CSharpDbConnection(connStr);
await conn1.OpenAsync();
await conn2.OpenAsync();

// Both connections see the same tables and data

Shared instances are managed by SharedMemoryDatabaseHost and serialized via a semaphore to ensure consistency.

Best for: Unit tests, integration tests, caches, temporary in-process data, scenarios where speed matters more than persistence.

Hybrid (Lazy-Resident)

Combines the durability of file-based storage with the performance of in-memory caching. Uses FileStorageDevice for durable backing, but keeps touched pages resident in cache across checkpoints — unlike the default mode which evicts pages after checkpoint.

// Simple — uses default hybrid options
await using var defaultHybrid = await Database.OpenHybridAsync("myapp.db");

// With custom options — preload hot tables and collections
var hybridOptions = new HybridDatabaseOptions
{
    PersistenceMode = HybridPersistenceMode.IncrementalDurable,
    HotTableNames = ["Users", "Sessions"],
    HotCollectionNames = ["Settings"],
};
await using var tunedHybrid = await Database.OpenHybridAsync(
    "myapp.db",
    new DatabaseOptions(),
    hybridOptions);

How It Works

  • Storage: Same FileStorageDevice as default mode — backing .db file on disk with a .db.wal
  • Page Cache: DictionaryPageCache with PreserveOwnedPagesOnCheckpoint = true — pages stay in cache after checkpoint
  • WAL: Full WAL implementation, same as file-based mode
  • Durability: Committed state persisted to disk incrementally via checkpoints
  • Hot Set Preloading: Specify tables and collections to load into cache at open via HotTableNames / HotCollectionNames
  • Lazy Residency: Cold pages load from disk on first access, then stay resident. Over time the working set migrates into memory

Persistence Modes

ModeBehavior
IncrementalDurableDefault. Pages cached per policy; file and WAL update incrementally on commit and checkpoint.
SnapshotFull in-memory copy; periodically writes complete database image to disk.

Persistence Triggers

Control when committed state is persisted to the backing file. Flags can be combined:

TriggerDescription
CommitPersist after each committed write transaction
CheckpointPersist when a WAL checkpoint occurs
DisposePersist when the database is disposed/closed
Best for: Applications with a hot working set that fits in memory but a cold tail on disk — e.g., active user sessions, frequently queried tables with a large archive, or read-heavy services backed by durable storage.

Comparison

AspectFile-BasedIn-MemoryHybrid
Storage deviceFileStorageDeviceMemoryStorageDeviceFileStorageDevice
Default page cacheDictionary (unbounded)Dictionary (unbounded)Dictionary (unbounded)
WALDisk fileIn-memory bufferDisk file
DurabilityImmediateNone (manual save)Incremental
Crash recoveryAutomatic via WALData lostAutomatic via WAL
Pages after checkpointEvicted from cacheAlways residentPreserved in cache
Bounded cache optionLruPageCacheNoOptional
Shared ADO.NETNoYesNo
Save to diskAutomaticSaveToFileAsync()Automatic + triggers
Best forProduction databasesTesting, cachesHot working set + cold tail

Choosing a Mode

Use File-Based when:

  • You need crash durability — every committed transaction survives a power failure
  • Your dataset is larger than available memory
  • You're deploying to production and need standard database guarantees

Use In-Memory when:

  • You're running unit or integration tests and want isolation and speed
  • The data is ephemeral — caches, scratch computations, temporary state
  • You need a shared in-memory database across multiple ADO.NET connections
  • You want to load a file into memory for fast read-only processing, then discard

Use Hybrid when:

  • You have a clear hot/cold split — frequently accessed data should be in memory, but you need the full dataset on disk
  • You want near-in-memory read performance for your working set with durable backing
  • You can identify hot tables or collections to preload at startup
  • You want to control exactly when persistence happens (on commit, checkpoint, or dispose)