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 may be used exactly once — calling Build() or BuildAsync() a second time throws InvalidOperationException.

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)
├── WithChangeLogRetainedCapacity(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)
│ ├── OwnedByContext(IRemoteSnapshotHandler)
│ └── BorrowedByCaller(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 until recovery (snapshot + journal replay) completes. Safe for Unity's synchronous init.
BuildAsync()Asynchronous. Preferred in async contexts. Avoid calling from code with a SynchronizationContext to prevent deadlocks.

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 that carry a [Settings] attribute. 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 accessible via DbConfiguration.MemoryBudget after build and 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

ChangeLogRetainedCapacity​

PropertyValue
Typeint (changes per table per transaction)
Default1024 (ChangeLogRetention.DefaultRetainedCapacity)
Valid valuesA power of two, at least 64
Builder methodWithChangeLogRetainedCapacity(int)

How many changes one transaction can stage into one table and still allocate nothing once that table's change log has grown to hold them. When a transaction ends, each table's log keeps its growth segments up to this capacity and releases the rest to the GC, so a one-off large transaction leaves at most this much behind per table (960 slots beyond the fixed 64-slot first segment by default, about 75 KB for a reference-type row). Raise it for a schema that commits larger transactions every frame; lower it to bound retained memory more tightly. DbContext.TrimMemory() releases what is retained; DbSetMemoryReport.ChangeLogBytes reports it.

.WithChangeLogRetainedCapacity(8192) // frames stage up to 8 192 changes per table without allocating

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. The journal thread writes each committed transaction through to the OS in the background, without a physical sync. 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. Also sets the process-level ConjureDB.ServerMode 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 serialized background worker lane 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 serialized background worker lane 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)
SkipCorruptedResynchronize at a later frame, discard all pending and first post-gap end-delimited transaction changes, then replay only versions after the clean boundary.
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
.OwnedByContext(new CloudSaveHandler())
.RequestTimeout(TimeSpan.FromSeconds(30))
.MaxRetryAttempts(5)
.ResolutionStrategy(StateResolutionStrategy.RemoteFirst))

Remote Sync Parameters​

Remote Snapshot Handler Lifetime​

RegistrationBuilder methodDisposal owner
Context-ownedOwnedByContext(IRemoteSnapshotHandler)The created DbContext after snapshot work drains
Caller-borrowedBorrowedByCaller(IRemoteSnapshotHandler)The caller, after its context has stopped using the handler

Handler implementations upload and download remote snapshots. Choose ownership deliberately: inline handlers normally use OwnedByContext, while a handler shared with other services uses BorrowedByCaller and remains the caller's responsibility to dispose.

public interface IRemoteSnapshotHandler : IAsyncDisposable
{
Task<ulong?> GetRemoteVersionAsync();
Task LoadSnapshotAsync(DbContext context);
Task UploadSnapshotAsync(DbContext context, ulong version);
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
OwnedByContextIRemoteSnapshotHandler—Upload/download handler disposed by the created context
BorrowedByCallerIRemoteSnapshotHandler—Upload/download handler retained and disposed by its caller
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
.OwnedByContext(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

Subscribe to MemoryPressureChanged to react to pressure levels:

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
Context-owned handlernullOwnedByContext(IRemoteSnapshotHandler)
Caller-borrowed handlernullBorrowedByCaller(IRemoteSnapshotHandler)
RequestTimeoutnullRequestTimeout(TimeSpan)
MaxRetryAttempts3MaxRetryAttempts(int)
ResolutionStrategyRemoteFirstResolutionStrategy(StateResolutionStrategy)

Migration​

ParameterDefaultBuilder Method
SnapshotAfterMigrationtrueSnapshotAfterMigration(bool)
VerboseLoggingfalseVerboseLogging(bool)