Skip to main content

Configuration Reference

Complete reference for all ConjureDB configuration parameters.

See also: Database Engine · Persistence · Getting Started · PGO


Overview

ConjureDB is configured entirely through the DbContextBuilder<TContext> fluent API. All settings are applied at database creation time and cannot be changed after Build() / BuildAsync() is called. Each builder instance is intended to be used once: BuildAsync() throws InvalidOperationException if the builder is reused, while Build() currently has no reuse guard — do not call it twice on the same builder.

var db = DbContextBuilder<GameDb>.Create()
.WithDataDirectory("/data/game")
.WithSnapshot(snap => snap
.AutomaticSnapshotInterval(TimeSpan.FromMinutes(5))
.WithJournaling(j => j.FlushInterval(TimeSpan.FromSeconds(1))))
.WithEncryption(enc => enc
.Password("strong-password")
.KeySizeInBits(256))
.WithLoggerFactory(loggerFactory)
.WithMemoryBudgetMB(256)
.Build();

Configuration Hierarchy

DbContextBuilder<TContext>
├── WithDataDirectory(string)
├── WithSerializer(IBinarySerializer)
├── WithSettingsLoaderFactory(SettingsLoaderFactory)
├── WithLoggerFactory(ILoggerFactory) / WithNullLogger()
├── WithMemoryBudget(long) / WithMemoryBudgetMB(int)
├── WithBatteryStatusProvider(IBatteryStatusProvider)
├── WithDurabilityPolicy(DurabilityPolicy)
├── WithNoPersistence()
├── WithRuntimeMode(ConjureDBRuntimeMode)
├── WithQueryInterceptor(IQueryInterceptor)
├── WithQueryInterceptorFactory(Func<DbContext, IQueryInterceptor?>)
├── WithSnapshot(Action<SnapshotOptionsBuilder>)
│ ├── SerializationBufferSize(int)
│ ├── AutomaticSnapshotInterval(TimeSpan)
│ ├── MaxSnapshotsToKeep(int)
│ ├── EnableIncrementalSnapshots(bool)
│ ├── MaxDeltaChainLength(int)
│ ├── DeltaToFullThreshold(double)
│ └── WithJournaling(Action<JournalOptionsBuilder>)
│ ├── FlushInterval(TimeSpan)
│ ├── MaxJournalFileSize(int)
│ ├── RecordsPerWrite(int)
│ ├── MaxStateChangesBeforeSnapshot(int)
│ ├── BufferSize(int)
│ ├── ChannelCapacity(int)
│ ├── MaxEntrySize(int)
│ ├── SerializationBufferSize(int)
│ ├── SerializationBuffersCount(int)
│ ├── FileStreamBufferSize(int)
│ ├── QueueThresholdForSnapshot(int)
│ ├── DelayBeforeSnapshotMs(int)
│ ├── HighWriteOpsPerSecond(int)
│ ├── MinFlushIntervalMs(int)
│ └── MaxFlushIntervalMs(int)
├── WithEncryption(Action<EncryptionOptionsBuilder>)
│ ├── Password(string)
│ ├── KeySizeInBits(int)
│ └── Salt(string)
├── WithRemoteSnapshot(Action<RemoteSnapshotOptionsBuilder>)
│ ├── RequestTimeout(TimeSpan)
│ ├── MaxRetryAttempts(int)
│ ├── ResolutionStrategy(StateResolutionStrategy)
│ └── RemoteHandler(IRemoteSnapshotHandler)
└── WithMigration(Action<MigrationOptionsBuilder>)
├── SnapshotAfterMigration(bool)
└── VerboseLogging(bool)

Builder Construction

Use DbContextBuilder<T>.Create() as the standard builder entry point. The builder binds to the public TContext(DbConfiguration) constructor, which keeps the same API across desktop/server runtimes and Unity IL2CPP.

MethodRecommendedDescription
DbContextBuilder<T>.Create()Unified builder path. Requires a public TContext(DbConfiguration) constructor.
// Unified builder path for desktop, server, and IL2CPP
var db = DbContextBuilder<GameDb>.Create().Build();

Build Methods

MethodDescription
Build()Synchronous. Blocks (sync-over-async) until recovery (snapshot + journal replay) completes. Convenient for Unity's synchronous init, but avoid calling it from code with an active SynchronizationContext to prevent deadlocks.
BuildAsync()Asynchronous. Recommended in async contexts — it awaits recovery and is deadlock-safe under a SynchronizationContext.

General Settings

DataDirectory

PropertyValue
Typestring
DefaultPath.Combine(AppDomain.CurrentDomain.BaseDirectory, "db_data")
Builder methodWithDataDirectory(string)

Filesystem path where all persistence files are stored: snapshots (<DataDir>/snapshots/), journals (<DataDir>/journal/), and delta snapshots. Must not be empty or whitespace. Can be absolute or relative.

.WithDataDirectory("/data/game-db")

Note: Ensure the application has read/write permissions to this directory. On mobile platforms, use the application's persistent data path.

Serializer

PropertyValue
TypeIBinarySerializer
DefaultBuilt-in MessagePack serializer
Builder methodWithSerializer(IBinarySerializer)

Custom binary serializer for snapshot and journal persistence. The serializer must implement:

public interface IBinarySerializer
{
byte[] Serialize<T>(T obj);
void Serialize<T>(IBufferWriter<byte> writer, T obj);
T Deserialize<T>(ReadOnlyMemory<byte> data);
T Deserialize<T>(byte[] data);
}
.WithSerializer(new CustomSerializer())

Note: Changing the serializer after data has been persisted will make existing snapshots and journals unreadable. The default MessagePack serializer is recommended unless you have specific requirements.

SettingsLoaderFactory

PropertyValue
TypeSettingsLoaderFactory
DefaultDefault factory (uses DefaultSettingsAccessProvider and MessagePackBinarySerializer)
Builder methodWithSettingsLoaderFactory(SettingsLoaderFactory)

Factory for creating settings loaders used to hydrate non-persistent DbSets declared as settings sets in the schema (identified at runtime by their SettingsName). Override to provide custom settings data sources.

.WithSettingsLoaderFactory(new SettingsLoaderFactory(customLoaders, accessProvider, serializerFactory))

LoggerFactory

PropertyValue
TypeILoggerFactory (Microsoft.Extensions.Logging)
DefaultNullLoggerFactory (no logging)
Builder methodsWithLoggerFactory(ILoggerFactory), WithNullLogger()

Structured logging factory used by the context, background worker, transaction manager, and persistence layer. Compatible with any Microsoft.Extensions.Logging provider.

// Enable logging with a provider
.WithLoggerFactory(LoggerFactory.Create(builder =>
builder.AddConsole().SetMinimumLevel(LogLevel.Debug)))

// Explicitly disable logging (default)
.WithNullLogger()

Note: ConjureDB uses ZLogger internally for zero-allocation structured logging. Any ILoggerFactory implementation is accepted.

MemoryBudget

PropertyValue
Typelong (bytes) or int (megabytes)
DefaultDisabled (no budget tracking)
Builder methodsWithMemoryBudget(long), WithMemoryBudgetMB(int)

Sets a maximum memory allocation budget. When enabled, all allocations are tracked and pressure events are raised at warning (≥90%) and critical (≥100%) thresholds. Allocations that would exceed the budget are rejected.

// Set budget in bytes
.WithMemoryBudget(512L * 1024 * 1024) // 512 MB

// Set budget in megabytes (convenience)
.WithMemoryBudgetMB(512)

Memory Pressure Levels:

LevelThresholdBehavior
Normal< 90% of budgetNormal operation
Warning≥ 90% of budgetMemoryPressureChanged event fired
Critical≥ 100% of budgetNew allocations rejected, event fired

The MemoryBudget instance is not exposed publicly on the built context. It can only be captured inside your derived DbContext(DbConfiguration config) constructor via config.MemoryBudget, which exposes:

Property / MethodTypeDescription
TotalAllocatedByteslongCurrent total allocated bytes (thread-safe)
BudgetByteslongConfigured budget limit
UsageRatiodoubleCurrent usage as ratio (0.0–∞)
CurrentLevelMemoryPressureLevelCurrent pressure level
MemoryPressureChangedeventFired on level transitions
TryAllocate(long, string)boolAttempt allocation; returns false if over budget
Release(long)voidRelease previously allocated bytes

BatteryStatusProvider

PropertyValue
TypeIBatteryStatusProvider
Defaultnull (disabled)
Builder methodWithBatteryStatusProvider(IBatteryStatusProvider)

Supplies battery charge information so the persistence layer can defer non-critical I/O (automatic snapshots, journal flushes) when the device is low on battery. Designed for mobile/handheld game clients.

public class UnityBatteryProvider : IBatteryStatusProvider
{
public bool IsBatteryLow() => SystemInfo.batteryLevel < 0.15f
&& SystemInfo.batteryStatus == BatteryStatus.Discharging;
}

// Usage
.WithBatteryStatusProvider(new UnityBatteryProvider())

The interface has a single method:

public interface IBatteryStatusProvider
{
bool IsBatteryLow();
}

When IsBatteryLow() returns true, the persistence layer defers automatic snapshots and may extend journal flush intervals to conserve battery.

Durability Policy

Durability profiles are configured via WithDurabilityPolicy(DurabilityPolicy). They define how aggressively ConjureDB forces committed journal data to stable storage.

PropertyValue
TypeDurabilityPolicy
DefaultDurabilityPolicy.FastMobile
Builder methodWithDurabilityPolicy(DurabilityPolicy)
.WithDurabilityPolicy(DurabilityPolicy.CriticalState)
ValueBehavior
FastMobileMobile-first default. Journal writes are flushed by the adaptive background policy. Use CommitAsync(forceFlush: true) when a specific commit must wait for durable journal flush.
CriticalStateEvery committed transaction must force the journal to stable storage. Use for purchases, currency, inventory, entitlements, or other state that must fail closed.
ForceFlushEveryCommitAlias for the strictest policy. Every committed transaction forces durable journal flush before the commit completes.

DurabilityPolicy.CriticalState and DurabilityPolicy.ForceFlushEveryCommit require JournalCorruptionPolicy.Fail. These profiles must fail startup on journal corruption instead of accepting partial replay.

No Persistence

Disable all file-based persistence via WithNoPersistence().

PropertyValue
TypeBuilder toggle
DefaultDisabled
Builder methodWithNoPersistence()

WithNoPersistence() turns off snapshots, journals, and incremental snapshots. Use it for server-side or test contexts where an external system owns durability.

.WithNoPersistence()

Note: Table-level PersistenceType still describes entity intent, but with WithNoPersistence() the builder disables file-based persistence for the whole context instance.

Runtime Mode

Runtime mode is configured via WithRuntimeMode(ConjureDBRuntimeMode).

PropertyValue
TypeConjureDBRuntimeMode
DefaultResolved from AppContext / build defaults
Builder methodWithRuntimeMode(ConjureDBRuntimeMode)

Overrides the runtime mode for the current context.

ValueBehavior
ClientClient-oriented runtime mode. This is the default when no server override is active.
ServerServer-oriented runtime mode. Only stores the mode on the context; it does not mutate any process-level AppContext switch.
.WithRuntimeMode(ConjureDBRuntimeMode.Server)

Note: In server builds, overriding back to Client is rejected.

Query Interception

Query interception is configured via WithQueryInterceptor(IQueryInterceptor) or WithQueryInterceptorFactory(Func<DbContext, IQueryInterceptor?>).

Builder methodDescription
WithQueryInterceptor(IQueryInterceptor)Reuses one fixed interceptor instance for all contexts created by the builder.
WithQueryInterceptorFactory(Func<DbContext, IQueryInterceptor?>)Creates an interceptor per context instance before the operation registry is sealed.
.WithQueryInterceptorFactory(db => new MyQueryInterceptor(db))

See Database Engine for the full IQueryInterceptor contract and result semantics.


Snapshot Configuration

Snapshot settings are configured via WithSnapshot(Action<SnapshotOptionsBuilder>) or WithDefaultSnapshot().

.WithSnapshot(snap => snap
.AutomaticSnapshotInterval(TimeSpan.FromMinutes(5))
.MaxSnapshotsToKeep(5)
.SerializationBufferSize(128 * 1024)
.EnableIncrementalSnapshots(true)
.MaxDeltaChainLength(8)
.DeltaToFullThreshold(0.4))

Note: Calling WithSnapshot(...) automatically enables snapshot persistence (IsEnabled = true). Use WithDefaultSnapshot() to enable snapshots with all default settings.

Snapshot Parameters

AutomaticSnapshotInterval

PropertyValue
TypeTimeSpan
DefaultTimeSpan.FromMinutes(5)
Builder methodAutomaticSnapshotInterval(TimeSpan)

Interval between automatic periodic snapshots. Snapshots are taken on the background worker thread after pending commits are applied. Set to TimeSpan.Zero to disable automatic snapshots (manual-only via DbContext.SaveAsync()).

.WithSnapshot(s => s.AutomaticSnapshotInterval(TimeSpan.FromMinutes(10)))

Tip: Shorter intervals reduce data loss window but increase I/O. For games with frequent saves, 1–5 minutes is typical. For idle/background apps, 10–30 minutes may suffice.

MaxSnapshotsToKeep

PropertyValue
Typeint
Default10
Builder methodMaxSnapshotsToKeep(int)

Maximum number of snapshot files retained on disk. When a new snapshot is created and the limit is exceeded, the oldest snapshot is deleted. Applies to both full and delta snapshots.

.WithSnapshot(s => s.MaxSnapshotsToKeep(3))

Note: Keep at least 2–3 snapshots to allow recovery if the latest snapshot is corrupted.

SerializationBufferSize

PropertyValue
Typeint (bytes)
Default65,536 (64 KB)
Builder methodSerializationBufferSize(int)

Initial capacity of the ArrayBufferWriter<byte> used to serialize entities during snapshot writes. The buffer grows automatically if needed, but setting an appropriate initial size avoids reallocation.

.WithSnapshot(s => s.SerializationBufferSize(256 * 1024)) // 256 KB

Tip: Set this to approximately the expected snapshot size. For databases with many large entities, increase to 256 KB–1 MB.

CompressSnapshots

PropertyValue
Typebool
Defaulttrue
Builder methodNot currently exposed on SnapshotOptionsBuilder

Snapshot files are compressed by default in the runtime snapshot format. This is part of the persisted SnapshotOptions contract, but the current fluent configuration builder does not expose a public toggle.

Note: See Persistence for the on-disk snapshot format and option matrix. If a future builder toggle is introduced, it should be documented here as the canonical configuration surface.

EnableIncrementalSnapshots

PropertyValue
Typebool
Defaultfalse
Builder methodEnableIncrementalSnapshots(bool)

When enabled, only entities modified since the last snapshot are written (delta snapshots). Full snapshots are still created periodically based on MaxDeltaChainLength and DeltaToFullThreshold. Significantly reduces snapshot I/O for large databases with low churn.

.WithSnapshot(s => s.EnableIncrementalSnapshots(true))

Note: Delta snapshots require replaying the delta chain on recovery, which increases startup time proportionally to chain length. Balance with MaxDeltaChainLength.

MaxDeltaChainLength

PropertyValue
Typeint
Default10
Builder methodMaxDeltaChainLength(int)

Maximum number of consecutive delta snapshots before a full snapshot is forced. Limits recovery time by bounding the number of deltas that must be replayed during startup.

.WithSnapshot(s => s
.EnableIncrementalSnapshots(true)
.MaxDeltaChainLength(5))

Note: Only relevant when EnableIncrementalSnapshots is true. Lower values produce more full snapshots (more I/O) but faster recovery.

DeltaToFullThreshold

PropertyValue
Typedouble (ratio, 0.0–1.0)
Default0.5
Builder methodDeltaToFullThreshold(double)

When the ratio of dirty (modified) entities to total entities exceeds this threshold, a full snapshot is created instead of a delta. Prevents delta snapshots from being nearly as large as full snapshots.

.WithSnapshot(s => s
.EnableIncrementalSnapshots(true)
.DeltaToFullThreshold(0.3)) // Full snapshot if >30% changed

Note: Only relevant when EnableIncrementalSnapshots is true. A value of 0.5 means a full snapshot is taken when more than half the entities have changed since the last full snapshot.

Snapshot Parameters Summary

ParameterTypeDefaultDescription
AutomaticSnapshotIntervalTimeSpan5 minInterval between automatic snapshots
MaxSnapshotsToKeepint10Maximum snapshot files retained on disk
SerializationBufferSizeint64 KBInitial serialization buffer capacity
CompressSnapshotsbooltrueRuntime default for snapshot file compression
EnableIncrementalSnapshotsboolfalseEnable delta snapshots
MaxDeltaChainLengthint10Max consecutive deltas before forced full
DeltaToFullThresholddouble0.5Dirty ratio to trigger full snapshot

Journal (WAL) Configuration

The write-ahead journal provides crash-recovery by recording each committed transaction to disk before secondary index synchronization completes. Journal settings are nested within the snapshot configuration.

.WithSnapshot(snap => snap
.WithJournaling(j => j
.FlushInterval(TimeSpan.FromMilliseconds(200))
.MaxJournalFileSize(4 * 1024 * 1024)
.MaxStateChangesBeforeSnapshot(500)))

Enabling Journaling

There are two ways to enable the journal:

// With default settings
.WithSnapshot(s => s.WithJournaling())

// With custom settings
.WithSnapshot(s => s.WithJournaling(j => j
.FlushInterval(TimeSpan.FromMilliseconds(500))))

Journaling is disabled by default. When enabled, the journal is written by the background worker thread after each committed transaction.

Journal Parameters

FlushInterval

PropertyValue
TypeTimeSpan
Default100 ms
Builder methodFlushInterval(TimeSpan)

Base interval between forced flushes of journal data to disk. The actual interval may vary based on the adaptive flush logic (see MinFlushIntervalMs / MaxFlushIntervalMs), battery status, and write activity.

.WithJournaling(j => j.FlushInterval(TimeSpan.FromMilliseconds(500)))

Trade-off: Lower values improve durability (less data loss on crash) at the cost of more I/O and battery drain. Higher values batch more writes but increase the data-loss window.

MaxJournalFileSize

PropertyValue
Typeint (bytes)
Default2,097,152 (2 MB)
Builder methodMaxJournalFileSize(int)

Maximum size in bytes for a single journal file before rotation. When a journal file reaches this size, it is closed and a new file is created. Keeps individual files manageable and aids incremental cleanup.

.WithJournaling(j => j.MaxJournalFileSize(4 * 1024 * 1024)) // 4 MB

RecordsPerWrite

PropertyValue
Typeint
Default16
Builder methodRecordsPerWrite(int)

Number of change records batched into a single write I/O operation. Higher values reduce syscall overhead but increase memory pressure per flush.

.WithJournaling(j => j.RecordsPerWrite(32))

MaxStateChangesBeforeSnapshot

PropertyValue
Typeint
Default100
Builder methodMaxStateChangesBeforeSnapshot(int)

Maximum number of state-change entries (individual entity mutations — Add, Update, Remove) that can accumulate in the journal before automatically triggering a full snapshot. Prevents unbounded journal growth and bounds recovery time.

.WithJournaling(j => j.MaxStateChangesBeforeSnapshot(500))

Note: Each Add, Update, or Remove on an entity counts as one state change. A single Commit() with 10 mutations counts as 10 state changes.

BufferSize

PropertyValue
Typeint (bytes)
Default65,536 (64 KB)
Builder methodBufferSize(int)

Size of the write buffer used to batch multiple journal entries before flushing to disk. Larger buffers improve throughput but increase memory usage and potential data loss on crash.

.WithJournaling(j => j.BufferSize(128 * 1024)) // 128 KB

Tip: Should be aligned with flash storage page size for optimal performance on mobile devices.

ChannelCapacity

PropertyValue
Typeint
Default1024
Builder methodChannelCapacity(int)

Bounded capacity of the internal Channel<T> used to queue journal entries. When the channel is full, backpressure is applied — writers are blocked until the journal processor catches up.

.WithJournaling(j => j.ChannelCapacity(2048))

Note: Increase for high-throughput systems with bursty write patterns. The default of 1024 is suitable for most game client workloads.

MaxEntrySize

PropertyValue
Typeint (bytes)
Default131,072 (128 KB)
Builder methodMaxEntrySize(int)

Maximum allowed size for a single journal entry. Entries exceeding this size are considered corrupted during recovery and handled according to CorruptionPolicy. Prevents out-of-memory issues from malformed journal data.

.WithJournaling(j => j.MaxEntrySize(256 * 1024)) // 256 KB

Note: Set based on the largest expected legitimate entity size after serialization.

SerializationBufferSize (Journal)

PropertyValue
Typeint (bytes)
Default4,096 (4 KB)
Builder methodSerializationBufferSize(int)

Initial size of the buffer for serializing individual journal entries via MessagePack. If an entry exceeds this size, the buffer automatically expands. Buffers are managed through an object pool (see SerializationBuffersCount).

.WithJournaling(j => j.SerializationBufferSize(16 * 1024)) // 16 KB

Tip: For mobile devices, 4 KB–16 KB is recommended. For systems with large entities, 64 KB+ reduces reallocation frequency.

SerializationBuffersCount

PropertyValue
Typeint
Default4
Builder methodSerializationBuffersCount(int)

Number of serialization buffers maintained in the object pool. Higher values allow more concurrent serialization operations but increase base memory usage.

.WithJournaling(j => j.SerializationBuffersCount(8))

Tip: Increase for high-concurrency scenarios where multiple threads commit simultaneously.

FileStreamBufferSize (Journal)

PropertyValue
Typeint (bytes)
Default4,096 (4 KB)
Builder methodFileStreamBufferSize(int)

Buffer size for FileStream I/O operations when writing to journal files. Smaller values conserve memory; larger values reduce syscall frequency.

.WithJournaling(j => j.FileStreamBufferSize(8 * 1024)) // 8 KB

Note: The default 4 KB is optimized for mobile environments with memory constraints. For desktop/server, 8–64 KB may improve throughput.

QueueThresholdForSnapshot

PropertyValue
Typeint
Default10
Builder methodQueueThresholdForSnapshot(int)

When the number of pending journal records exceeds this threshold, the snapshot trigger is delayed (by DelayBeforeSnapshotMs) to allow the journal queue to drain first. Prevents snapshot I/O from competing with journal writes during bursts.

.WithJournaling(j => j.QueueThresholdForSnapshot(20))

DelayBeforeSnapshotMs

PropertyValue
Typeint (milliseconds)
Default500
Builder methodDelayBeforeSnapshotMs(int)

Delay in milliseconds before taking a snapshot when the journal queue is busy (exceeds QueueThresholdForSnapshot). Allows additional writes to be batched into the same snapshot.

.WithJournaling(j => j.DelayBeforeSnapshotMs(1000)) // 1 second

HighWriteOpsPerSecond

PropertyValue
Typeint
Default100
Builder methodHighWriteOpsPerSecond(int)

Write operations per second threshold that triggers high-throughput mode. When the journal detects write rates above this threshold, it switches to less frequent flushes and larger batches to optimize throughput.

.WithJournaling(j => j.HighWriteOpsPerSecond(500))

MinFlushIntervalMs

PropertyValue
Typeint (milliseconds)
Default100
Builder methodMinFlushIntervalMs(int)

Minimum flush interval for the adaptive flush logic. The journal will not flush more frequently than this, even under heavy write load. Prevents excessive I/O during burst writes.

.WithJournaling(j => j.MinFlushIntervalMs(50)) // More aggressive flushing

MaxFlushIntervalMs

PropertyValue
Typeint (milliseconds)
Default5000
Builder methodMaxFlushIntervalMs(int)

Maximum flush interval for the adaptive flush logic. The journal will flush at least this often, even under low write load, to bound the data-loss window on crash.

.WithJournaling(j => j.MaxFlushIntervalMs(10000)) // 10 seconds max

Adaptive Flush Logic

The journal uses an adaptive flush strategy that adjusts the flush interval based on write activity:

Write Rate ─────────────────────────────────────────────►
│ │
MaxFlushIntervalMs MinFlushIntervalMs
(5000 ms) (100 ms)
│ │
Low activity ─┤────────────────────┤─ High activity
│ HighWriteOps/s │
│ threshold (100) │
  • Low write rate (below HighWriteOpsPerSecond): Flushes at MaxFlushIntervalMs to conserve I/O.
  • High write rate (at or above HighWriteOpsPerSecond): Flushes at MinFlushIntervalMs for durability.
  • Battery low: Flush intervals may be extended when IBatteryStatusProvider.IsBatteryLow() returns true.

Journal Corruption Policy

The JournalCorruptionPolicy controls how the recovery handler responds to corrupted journal entries. Configure it through WithJournaling(j => j.CorruptionPolicy(...)).

ValueBehavior
StopAtCorruptionStop replaying at the first corrupted entry. Recovers all valid entries up to corruption point. (Default)
SkipCorruptedSkip corrupted entries and continue replaying subsequent valid entries.
FailThrow an exception immediately on encountering corruption.

DurabilityPolicy.CriticalState and DurabilityPolicy.ForceFlushEveryCommit require JournalCorruptionPolicy.Fail. These profiles force committed journal data to stable storage and must also fail startup on journal corruption instead of accepting partial replay.

Journal Parameters Summary

ParameterTypeDefaultDescription
FlushIntervalTimeSpan100 msBase interval between disk flushes
MaxJournalFileSizeint2 MBMax file size before rotation
RecordsPerWriteint16Records batched per write I/O
MaxStateChangesBeforeSnapshotint100State changes before auto-snapshot
BufferSizeint64 KBWrite buffer size
ChannelCapacityint1024Bounded queue capacity
MaxEntrySizeint128 KBMax single entry size
SerializationBufferSizeint4 KBPer-entry serialization buffer
SerializationBuffersCountint4Pooled serialization buffers
FileStreamBufferSizeint4 KBFileStream I/O buffer
QueueThresholdForSnapshotint10Queue depth to delay snapshot
DelayBeforeSnapshotMsint500 msDelay before snapshot on busy queue
HighWriteOpsPerSecondint100Ops/sec for high-throughput mode
MinFlushIntervalMsint100 msMin adaptive flush interval
MaxFlushIntervalMsint5000 msMax adaptive flush interval
CorruptionPolicyJournalCorruptionPolicyStopAtCorruptionRecovery behavior for corrupted journal entries

Encryption Configuration

ConjureDB supports AES-GCM encryption for all persistence files (snapshots and journals). Encryption is configured via WithEncryption(Action<EncryptionOptionsBuilder>).

.WithEncryption(enc => enc
.Password("my-strong-unique-password")
.KeySizeInBits(256)
.Salt("unique-per-database-salt-value-here"))

Encryption Parameters

Password

PropertyValue
Typestring
Defaultnull (required when encryption is enabled)
Builder methodPassword(string)

Encryption password used for AES key derivation via PBKDF2. Required when encryption is enabled. Must be a strong, unique password.

.WithEncryption(enc => enc.Password("secure-password-here"))

Security: Never hardcode passwords in source code. Load from secure storage (e.g., iOS Keychain, Android Keystore, environment variables).

KeySizeInBits

PropertyValue
Typeint
Default256
Allowed values128, 192, 256
Builder methodKeySizeInBits(int)

AES key size in bits. Higher key sizes provide stronger encryption at a marginal CPU cost.

.WithEncryption(enc => enc
.Password("secure-password")
.KeySizeInBits(128)) // Faster, still secure for most use cases
Key SizeSecurity LevelPerformance Impact
128-bitStrongFastest
192-bitStrongerNegligible overhead vs 128
256-bitStrongestNegligible overhead vs 192

Recommendation: Use 256-bit (default) unless profiling shows encryption as a bottleneck on your target device.

Salt

PropertyValue
Typestring
DefaultCryptographically random (32 bytes, Base64-encoded via RandomNumberGenerator)
Builder methodSalt(string)

Cryptographic salt for PBKDF2 key derivation and nonce generation. Must be at least 16 bytes when UTF-8 encoded. Each database instance should use a unique salt.

.WithEncryption(enc => enc
.Password("secure-password")
.Salt("my-unique-database-salt-at-least-16-bytes"))

Important: The default generates a new random salt on each instantiation. If you need to read data encrypted in a previous session, you must persist and reuse the same salt. Store it alongside the encrypted data or in platform-secure storage.

You can generate a salt programmatically:

string salt = EncryptionOptions.GenerateRandomSalt(); // 32 bytes (default)
string salt = EncryptionOptions.GenerateRandomSalt(64); // 64 bytes

Encryption Implementation Details

  • Algorithm: AES-GCM (Galois/Counter Mode) — authenticated encryption with associated data (AEAD)
  • Chunking: Files are encrypted in 4 KB chunks, each with its own authentication tag
  • Key derivation: PBKDF2 from password + salt
  • Implementation: BouncyCastle (portable, Unity-compatible — no platform-specific crypto APIs)
  • Custom streams: IProtectedFileStreamFactory can be overridden for platform-specific encryption (e.g., iOS Keychain integration)

Encryption Parameters Summary

ParameterTypeDefaultDescription
Passwordstringnull (required)PBKDF2 key derivation password
KeySizeInBitsint256AES key size: 128, 192, or 256
SaltstringRandom (32 bytes)Key derivation salt (≥16 bytes UTF-8)

Remote Sync Configuration

Remote snapshot sync enables cloud save / backup functionality. Configured via WithRemoteSnapshot(Action<RemoteSnapshotOptionsBuilder>).

.WithRemoteSnapshot(remote => remote
.RemoteHandler(new CloudSaveHandler())
.RequestTimeout(TimeSpan.FromSeconds(30))
.MaxRetryAttempts(5)
.ResolutionStrategy(StateResolutionStrategy.RemoteFirst))

Remote Sync Parameters

RemoteHandler

PropertyValue
TypeIRemoteSnapshotHandler
Defaultnull (required for remote sync)
Builder methodRemoteHandler(IRemoteSnapshotHandler)

Handler implementation responsible for uploading and downloading remote snapshots. The handler must implement:

public interface IRemoteSnapshotHandler : IAsyncDisposable
{
Task<ulong?> GetRemoteVersionAsync();
Task LoadSnapshotAsync(DbContext context);
Task UploadSnapshotAsync(DbContext context, ulong version);
// plus ValueTask DisposeAsync() from IAsyncDisposable
}

The handler extends IAsyncDisposable, so implementers must also provide ValueTask DisposeAsync().

RequestTimeout

PropertyValue
TypeTimeSpan
Defaultnull (no timeout)
Builder methodRequestTimeout(TimeSpan)

HTTP request timeout for remote snapshot operations (upload, download, version check).

.WithRemoteSnapshot(r => r.RequestTimeout(TimeSpan.FromSeconds(30)))

MaxRetryAttempts

PropertyValue
Typeint
Default3
Builder methodMaxRetryAttempts(int)

Maximum number of retry attempts for failed remote snapshot operations before giving up.

.WithRemoteSnapshot(r => r.MaxRetryAttempts(5))

ResolutionStrategy

PropertyValue
TypeStateResolutionStrategy
DefaultRemoteFirst
Builder methodResolutionStrategy(StateResolutionStrategy)

Conflict resolution strategy when local and remote snapshot versions diverge.

StrategyBehavior
LocalFirstUse local state unless remote is strictly newer
RemoteFirstPrefer remote state (Default)
ForceLocalAlways use local state, ignore remote
ForceRemoteAlways use remote state, overwrite local
.WithRemoteSnapshot(r => r.ResolutionStrategy(StateResolutionStrategy.ForceRemote))

Remote Sync Parameters Summary

ParameterTypeDefaultDescription
RemoteHandlerIRemoteSnapshotHandlernull (required)Upload/download handler
RequestTimeoutTimeSpan?nullHTTP request timeout
MaxRetryAttemptsint3Max retries on failure
ResolutionStrategyStateResolutionStrategyRemoteFirstConflict resolution strategy

Migration Configuration

Schema migration settings are configured via WithMigration(Action<MigrationOptionsBuilder>).

.WithMigration(m => m
.SnapshotAfterMigration(true)
.VerboseLogging(true))

Migration Parameters

SnapshotAfterMigration

PropertyValue
Typebool
Defaulttrue
Builder methodSnapshotAfterMigration(bool)

When enabled, automatically takes a full snapshot after migrations are applied so that subsequent startups do not need to re-run migrations. Recommended to keep enabled.

.WithMigration(m => m.SnapshotAfterMigration(true))

VerboseLogging

PropertyValue
Typebool
Defaultfalse
Builder methodVerboseLogging(bool)

Enables detailed logging of each migration step for debugging schema evolution issues. Requires an active ILoggerFactory to see output.

.WithMigration(m => m.VerboseLogging(true))

Migration Parameters Summary

ParameterTypeDefaultDescription
SnapshotAfterMigrationbooltrueAuto-snapshot after migration
VerboseLoggingboolfalseDetailed migration logging

Configuration Examples

Minimal Configuration (In-Memory Only)

No persistence — data lives only in memory. Fastest startup, zero I/O.

var db = DbContextBuilder<GameDb>.Create()
.Build();

Snapshot-Only Persistence

Periodic snapshots without journaling. Simple and low-overhead, but data since the last snapshot is lost on crash.

var db = DbContextBuilder<GameDb>.Create()
.WithDataDirectory("game_data")
.WithSnapshot(s => s
.AutomaticSnapshotInterval(TimeSpan.FromMinutes(5))
.MaxSnapshotsToKeep(5))
.Build();

Production Game Client

Full durability with journaling, encryption, incremental snapshots, and memory budget.

var db = DbContextBuilder<GameDb>.Create()
.WithDataDirectory(Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"MyGame", "db"))
.WithSnapshot(s => s
.AutomaticSnapshotInterval(TimeSpan.FromMinutes(3))
.MaxSnapshotsToKeep(5)
.EnableIncrementalSnapshots(true)
.MaxDeltaChainLength(5)
.DeltaToFullThreshold(0.3)
.SerializationBufferSize(128 * 1024)
.WithJournaling(j => j
.FlushInterval(TimeSpan.FromMilliseconds(200))
.MaxStateChangesBeforeSnapshot(500)
.MaxJournalFileSize(4 * 1024 * 1024)
.BufferSize(64 * 1024)))
.WithEncryption(enc => enc
.Password(SecureStorage.GetPassword())
.KeySizeInBits(256)
.Salt(SecureStorage.GetSalt()))
.WithMemoryBudgetMB(256)
.WithLoggerFactory(loggerFactory)
.Build();

Unity Mobile Game (AOT-Safe)

Configuration tailored for mobile: unified builder path, battery awareness, conservative memory.

var db = DbContextBuilder<GameDb>.Create()
.WithDataDirectory(Application.persistentDataPath + "/db")
.WithSnapshot(s => s
.AutomaticSnapshotInterval(TimeSpan.FromMinutes(5))
.MaxSnapshotsToKeep(3)
.SerializationBufferSize(32 * 1024)
.WithJournaling(j => j
.FlushInterval(TimeSpan.FromMilliseconds(500))
.MaxStateChangesBeforeSnapshot(200)
.MaxJournalFileSize(1 * 1024 * 1024)
.BufferSize(32 * 1024)
.SerializationBufferSize(4 * 1024)
.SerializationBuffersCount(2)
.FileStreamBufferSize(4 * 1024)
.ChannelCapacity(512)))
.WithEncryption(enc => enc
.Password(GetEncryptionPassword())
.KeySizeInBits(256))
.WithBatteryStatusProvider(new UnityBatteryProvider())
.WithMemoryBudgetMB(128)
.Build();

Development / Debug

Verbose logging, no encryption, fast snapshots for iteration.

var db = DbContextBuilder<GameDb>.Create()
.WithDataDirectory("dev_data")
.WithSnapshot(s => s
.AutomaticSnapshotInterval(TimeSpan.FromSeconds(30))
.MaxSnapshotsToKeep(3)
.WithJournaling())
.WithLoggerFactory(LoggerFactory.Create(builder =>
builder.AddConsole().SetMinimumLevel(LogLevel.Debug)))
.WithMigration(m => m.VerboseLogging(true))
.Build();

High-Write Workload

Optimized for applications with frequent mutations (e.g., real-time simulation, trading).

var db = DbContextBuilder<GameDb>.Create()
.WithDataDirectory("highwrite_data")
.WithSnapshot(s => s
.AutomaticSnapshotInterval(TimeSpan.FromMinutes(10))
.MaxSnapshotsToKeep(5)
.EnableIncrementalSnapshots(true)
.MaxDeltaChainLength(3)
.DeltaToFullThreshold(0.2)
.SerializationBufferSize(256 * 1024)
.WithJournaling(j => j
.FlushInterval(TimeSpan.FromMilliseconds(50))
.MaxJournalFileSize(8 * 1024 * 1024)
.RecordsPerWrite(64)
.MaxStateChangesBeforeSnapshot(2000)
.BufferSize(256 * 1024)
.ChannelCapacity(4096)
.MaxEntrySize(256 * 1024)
.SerializationBufferSize(64 * 1024)
.SerializationBuffersCount(8)
.FileStreamBufferSize(64 * 1024)
.HighWriteOpsPerSecond(1000)
.MinFlushIntervalMs(25)
.MaxFlushIntervalMs(2000)))
.WithMemoryBudgetMB(1024)
.Build();

Cloud Save with Remote Sync

Persistent local database with cloud backup.

var db = DbContextBuilder<GameDb>.Create()
.WithDataDirectory("cloud_game_data")
.WithSnapshot(s => s
.AutomaticSnapshotInterval(TimeSpan.FromMinutes(5))
.MaxSnapshotsToKeep(5)
.WithJournaling())
.WithRemoteSnapshot(r => r
.RemoteHandler(new MyCloudSaveHandler(httpClient))
.RequestTimeout(TimeSpan.FromSeconds(30))
.MaxRetryAttempts(5)
.ResolutionStrategy(StateResolutionStrategy.RemoteFirst))
.WithEncryption(enc => enc
.Password(SecureStorage.GetPassword()))
.Build();

Configuration Best Practices

Journal vs. Snapshot-Only

ConsiderationSnapshot-OnlySnapshot + Journal
Data loss windowUp to AutomaticSnapshotIntervalUp to FlushInterval (ms)
Disk I/OPeriodic large writesContinuous small writes
Recovery timeFast (single file read)Snapshot + journal replay
ComplexitySimpleMore tuning parameters
Recommended forDevelopment, non-critical dataProduction, user-facing data

Rule of thumb: If losing 5 minutes of data is unacceptable, enable journaling.

Memory Budget Sizing

ScenarioRecommended Budget
Mobile casual game64–128 MB
Mobile RPG / strategy128–256 MB
Desktop game client256–512 MB
Desktop simulation512 MB–2 GB

Capture the budget inside your derived DbContext constructor and subscribe to MemoryPressureChanged to react to pressure levels:

public class GameDb : DbContext
{
public GameDb(DbConfiguration config) : base(config)
{
if (config.MemoryBudget is { } budget)
{
budget.MemoryPressureChanged += (sender, args) =>
{
if (args.Level == MemoryPressureLevel.Warning)
EvictCaches();
else if (args.Level == MemoryPressureLevel.Critical)
ReduceEntityPools();
};
}
}
}

Encryption Performance Impact

AES-GCM encryption adds overhead to all persistence I/O:

OperationOverhead (approximate)
Snapshot write5–15% slower
Snapshot read5–15% slower
Journal write3–10% per entry
Journal read (recovery)3–10% per entry

The chunked 4 KB encryption means overhead scales linearly with data size. For most game clients, the overhead is negligible compared to serialization cost.

Flush Interval Tuning

ScenarioFlushIntervalMinFlushIntervalMsMaxFlushIntervalMs
Maximum durability50 ms25 ms1000 ms
Balanced (default)100 ms100 ms5000 ms
Battery-friendly500 ms200 ms10000 ms
High throughput100 ms50 ms3000 ms

Incremental Snapshot Tuning

WorkloadDeltaToFullThresholdMaxDeltaChainLength
Low churn (< 5% entities change between snapshots)0.510
Medium churn (5–20%)0.35–8
High churn (> 20%)0.23–5
Batch imports0.12

All Parameters Quick Reference

General

ParameterDefaultBuilder Method
DataDirectory<BaseDir>/db_dataWithDataDirectory(string)
SerializerMessagePackWithSerializer(IBinarySerializer)
SettingsLoaderFactoryDefault factoryWithSettingsLoaderFactory(SettingsLoaderFactory)
LoggerFactoryNullLoggerFactoryWithLoggerFactory(ILoggerFactory)
MemoryBudgetDisabledWithMemoryBudget(long) / WithMemoryBudgetMB(int)
BatteryStatusProviderDisabledWithBatteryStatusProvider(IBatteryStatusProvider)

Snapshot

ParameterDefaultBuilder Method
AutomaticSnapshotInterval5 minAutomaticSnapshotInterval(TimeSpan)
MaxSnapshotsToKeep10MaxSnapshotsToKeep(int)
SerializationBufferSize64 KBSerializationBufferSize(int)
CompressSnapshotstrueRuntime default only (no fluent builder toggle)
EnableIncrementalSnapshotsfalseEnableIncrementalSnapshots(bool)
MaxDeltaChainLength10MaxDeltaChainLength(int)
DeltaToFullThreshold0.5DeltaToFullThreshold(double)

Durability

ParameterDefaultBuilder Method
DurabilityPolicyFastMobileWithDurabilityPolicy(DurabilityPolicy)

Journal (WAL)

ParameterDefaultBuilder Method
FlushInterval100 msFlushInterval(TimeSpan)
MaxJournalFileSize2 MBMaxJournalFileSize(int)
RecordsPerWrite16RecordsPerWrite(int)
MaxStateChangesBeforeSnapshot100MaxStateChangesBeforeSnapshot(int)
BufferSize64 KBBufferSize(int)
ChannelCapacity1024ChannelCapacity(int)
MaxEntrySize128 KBMaxEntrySize(int)
SerializationBufferSize4 KBSerializationBufferSize(int)
SerializationBuffersCount4SerializationBuffersCount(int)
FileStreamBufferSize4 KBFileStreamBufferSize(int)
QueueThresholdForSnapshot10QueueThresholdForSnapshot(int)
DelayBeforeSnapshotMs500 msDelayBeforeSnapshotMs(int)
HighWriteOpsPerSecond100HighWriteOpsPerSecond(int)
MinFlushIntervalMs100 msMinFlushIntervalMs(int)
MaxFlushIntervalMs5000 msMaxFlushIntervalMs(int)

Encryption

ParameterDefaultBuilder Method
Passwordnull (required)Password(string)
KeySizeInBits256KeySizeInBits(int)
SaltRandom (32 bytes)Salt(string)

Remote Sync

ParameterDefaultBuilder Method
RemoteHandlernull (required)RemoteHandler(IRemoteSnapshotHandler)
RequestTimeoutnullRequestTimeout(TimeSpan)
MaxRetryAttempts3MaxRetryAttempts(int)
ResolutionStrategyRemoteFirstResolutionStrategy(StateResolutionStrategy)

Migration

ParameterDefaultBuilder Method
SnapshotAfterMigrationtrueSnapshotAfterMigration(bool)
VerboseLoggingfalseVerboseLogging(bool)