Durability & recovery
When a write is acknowledged, indeterminate outcomes, and recovery.
When a write is acknowledged
With the default DurabilityMode.Synchronous, a write returns only after its durable commit to the
write-ahead log, so a crash cannot lose an acknowledged write. Asynchronous acknowledges earlier, trading
that guarantee for latency. Concurrent writers share one disk flush (group commit); a single sequential writer pays one
flush per write, so bulk loads should use AddRange or AddBatch rather than a loop of single adds.
builder.Services.AddNetlyDB(o =>
{
o.Durability = DurabilityMode.Synchronous; // acknowledge after the durable commit (default)
o.CommitTimeout = TimeSpan.FromSeconds(30); // how long a caller waits for it
o.SnapshotRetention.PreviousGenerations = 1; // superseded generations to keep
o.DataClasses.Default = DataClass.Critical; // how replaceable this store's data is
});
When an outcome is indeterminate
A write that outlives CommitTimeout is indeterminate, which is not the same as failed: the
caller stopped waiting, the mutation did not stop. Retrying is the one thing not to do, because it may already have taken
effect. Resolve it by id instead - the status survives a restart, and ResolutionProcedure says what to do in
one sentence.
using NetlyDB.Core.Persistence;
var users = host.Services.GetRequiredService<IDocumentCollection<User>>();
try
{
users.Add(new User { Name = "Alice" });
}
catch (NetlyDBIndeterminateCommitException ex)
{
// Do not retry: the mutation may already have taken effect. Resolve it instead.
MutationStatusResult status = await users.GetMutationStatusAsync(ex.MutationId);
if (status.IsSettled)
{
// status.State, status.Status!.Lsn, status.Status.DocumentApplied
}
else
{
// status.OutcomeCode -> catalogue code, for a log line or a support ticket
// status.ResolutionProcedure -> what to do about this answer, in one sentence
// status.History -> how far back the store can still answer at all
}
}
"Not found" inside the retained history window is a real answer. Outside it, the result says so through
IsOutsideRetainedHistory rather than pretending the write never existed.
Recovery at startup
Startup classifies what it finds on disk and replays each collection's log over its last published snapshot generation. Ambiguity it cannot resolve stops writable startup rather than being skipped, and every session leaves a recovery report - readable from code, in the Management UI, and with the offline CLI. How aggressively an unreadable artifact may be discarded and rebuilt is set per collection by data classes.
From 1.2.0, recovery is stricter in two situations where earlier versions opened the store:
- An older snapshot is used only if the log can carry it forward. If a collection's newest snapshot generation cannot be used, recovery falls back to an older one only when the retained log still holds every write between that generation and the state the store last published. Otherwise the collection is refused, and the failure names each generation and why it could not be used. Before 1.2.0 such a collection could open without writes that had been acknowledged.
- A collection recorded as dropped, but still present, stops recovery. This happens when a process stops part-way through a drop. Nothing is deleted or recreated: clear the recorded drop, restart, and then keep the collection or drop it again.
When recovery stops, the engine is in RecoveryRequired. From 1.2.0 it refuses ordinary document and file
reads as well as writes in that state, and creates no collections: data that recovery could not verify is not served.
Collection names, the recovery session and report, and the health probes stay available.
Things to know
- No transactions across collections, and no cross-collection point-in-time recovery.
- Batches are recoverable but not atomically visible to concurrent readers.
- Corruption in the middle of a log - between records that were complete - needs manual, offline intervention. The engine reports it plainly rather than truncating acknowledged writes.
- Downgrade is not supported: rolling back to an earlier version means restoring a backup.