Eryri.WriteAheadLog 1.0.1

dotnet add package Eryri.WriteAheadLog --version 1.0.1
                    
NuGet\Install-Package Eryri.WriteAheadLog -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Eryri.WriteAheadLog" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Eryri.WriteAheadLog" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Eryri.WriteAheadLog" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Eryri.WriteAheadLog --version 1.0.1
                    
#r "nuget: Eryri.WriteAheadLog, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Eryri.WriteAheadLog@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Eryri.WriteAheadLog&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Eryri.WriteAheadLog&version=1.0.1
                    
Install as a Cake Tool

Eryri.WriteAheadLog

NuGet NuGet Downloads

A high-throughput, replayable, append-only log for .NET.

Eryri.WriteAheadLog is a lightweight local persistence engine for applications that need fast ordered writes, deterministic replay, and optional snapshot-based compaction.

Write records to a durable binary log, replay them from the beginning or from a checkpoint, and optionally create snapshots to make recovery fast.

It can be used as a write-ahead log, local event store, event-sourcing foundation, replayable event log, or high-performance application persistence layer — without requiring a database.

Why use it?

  • High-throughput append-only writes through an asynchronous single-writer pipeline.
  • Replay indefinitely from the beginning of the log to reconstruct application state.
  • Optional snapshots to compact history and accelerate recovery.
  • Application-defined serialization using IBufferWriter<byte>.
  • Configurable durability from queued writes through to explicitly flushed and durable records.
  • CRC32 integrity checks detect incomplete or corrupted records.
  • Generation-based checkpoints prevent stale snapshot offsets being applied to a new log.
  • Binary format versioning allows incompatible persisted state to be discarded safely.
  • No database required — persistence is entirely local to the application.

The core idea

The WAL is the history.

A snapshot is simply an optimisation for replay.

     Append
       │
       ▼
┌──────────────┐
│      WAL     │
│              │
│ Event 1      │
│ Event 2      │
│ Event 3      │
│ ...          │
│ Event N      │
└──────┬───────┘
       │
    Replay
       │
       ▼
Application State

When snapshots are enabled:

┌──────────────┐
│   Snapshot   │
│   at Event N │
└──────┬───────┘
       │
       ▼
Replay Event N+1
       │
       ▼
Current State

This means the application can choose the right trade-off between history, recovery time, and storage.

Event sourcing

The library is deliberately well suited to event sourcing.

An application can treat each WAL record as an event and rebuild its state entirely by replaying the log:

Command
   │
   ▼
Append Event
   │
   ▼
┌──────────────┐
│ Event Store  │
│    (WAL)     │
└──────┬───────┘
       │
       ▼
     Replay
       │
       ▼
   Projection

Snapshots can then be introduced purely as a performance optimisation when replaying the complete history becomes unnecessary.

Eryri.WriteAheadLog does not impose an event-sourcing architecture. It provides the underlying ordered, persistent, replayable log that such an architecture can be built upon.

When it fits

Excellent fit

  • Event-sourced applications
  • Local event stores
  • In-memory state with persistent recovery
  • Stateful background services
  • Trading and market-data systems
  • Local indexes and caches
  • Game/server state
  • High-throughput local persistence
  • Applications requiring deterministic replay
  • Systems where a database would add unnecessary operational complexity

Not a database

This library intentionally does not provide:

  • SQL queries
  • Secondary indexes
  • Distributed replication
  • Multi-process coordination
  • Distributed transactions
  • Consensus
  • Network access

If you need those capabilities, use a database or distributed log.

Eryri.WriteAheadLog is the local persistence primitive, not a replacement for every kind of data store.

Install

dotnet add package Eryri.WriteAheadLog

Getting started

Create a concrete WAL by implementing four application-specific operations:

public sealed class MyWal : WriteAheadLog<MyEvent>
{
    protected override ulong FormatVersion => 1;

    protected override void Serialize(
        MyEvent value,
        IBufferWriter<byte> writer)
    {
        // Serialize value to writer.
    }

    protected override MyEvent Deserialize(
        ReadOnlyMemory<byte> data)
    {
        // Deserialize value.
    }

    protected override ValueTask ApplyAsync(
        MyEvent value,
        CancellationToken cancellationToken)
    {
        // Apply the event to application state.
    }

    protected override Task WriteSnapshotAsync(
        Stream stream,
        CancellationToken cancellationToken)
    {
        // Serialize application state.
    }

    protected override Task ReadSnapshotAsync(
        Stream stream,
        CancellationToken cancellationToken)
    {
        // Restore application state.
    }
}

Then:

await using var wal = new MyWal("state");

await wal.InitializeAsync();

await wal.AppendAsync(
    new MyEvent(...),
    Durability.Durable);

The application owns the serialization and state model. The WAL owns the persistence, ordering, integrity, replay and recovery mechanics.

Durability

Writes can be assigned different durability requirements:

await wal.AppendAsync(value, Durability.Queue);
await wal.AppendAsync(value, Durability.Write);
await wal.AppendAsync(value, Durability.Flush);
await wal.AppendAsync(value, Durability.Durable);

This allows applications to choose the appropriate balance between latency, throughput and durability.

For example:

  • Queue — enqueue the record and return immediately.
  • Write — wait for the record to be written.
  • Flush — wait for the stream to be flushed.
  • Durable — flush the underlying file to stable storage.

The WAL preserves record ordering regardless of the durability mode.

Replay and recovery

On startup, the WAL can:

  1. Restore the latest snapshot, if present.
  2. Validate its checkpoint against the WAL generation.
  3. Seek to the checkpoint position when valid.
  4. Replay subsequent records.
  5. Reconstruct the current application state.

Without a snapshot, the complete WAL can simply be replayed from the beginning.

This makes the same log suitable for both fast recovery and complete historical replay.

Snapshots

Snapshots are optional.

They allow an application to periodically persist its current state and discard WAL history that is no longer required for recovery.

WAL

Event 1
Event 2
Event 3
Event 4
Event 5
   ▲
   │
Snapshot checkpoint
   │
Event 6
Event 7
Event 8

After a successful snapshot, recovery only needs to restore the snapshot and replay events after its checkpoint.

Snapshots therefore improve recovery time without changing the underlying event history model.

Data integrity

Each WAL record contains:

┌────────────┬──────────────┬────────────┐
│   Length   │   Payload    │    CRC32   │
└────────────┴──────────────┴────────────┘

Records are validated during replay so incomplete or corrupted records cannot silently become application state.

The WAL also maintains a generation identifier. Snapshots store the generation alongside their checkpoint position, ensuring a checkpoint from an older WAL cannot accidentally be applied to a newly-created log.

Format versioning

Implementations define their persisted format version:

protected override ulong FormatVersion => 1;

When the stored format version differs from the implementation's current version, the existing WAL and snapshot are discarded and a new state is created.

This provides an explicit and deterministic mechanism for handling incompatible persisted formats.

Performance-oriented design

The library is designed around a simple principle:

Keep the hot path append-only and let the application control the representation of its data.

Serialization writes directly into an IBufferWriter<byte>, avoiding unnecessary intermediate allocations.

Writes are processed through an ordered asynchronous pipeline, allowing producers to enqueue work without each producer independently coordinating access to the underlying file.

Snapshots stream directly to disk rather than requiring the complete snapshot to exist in memory.

The result is a small persistence primitive that can handle workloads where throughput and predictable local I/O matter more than database features.

What makes it different?

Most persistence abstractions answer:

"How do I store this state?"

Eryri.WriteAheadLog answers a different question:

"How do I reliably record this history and replay it later?"

That distinction makes it useful far beyond traditional WAL scenarios.

The same primitive can underpin:

  • application state recovery
  • event sourcing
  • event replay
  • local event stores
  • materialized state
  • snapshots
  • deterministic reconstruction
  • append-only persistence

Design philosophy

Eryri.WriteAheadLog deliberately leaves application semantics to the application.

It does not dictate:

  • your event types
  • your serialization format
  • your state model
  • your event-sourcing architecture
  • your snapshot representation
  • your retention strategy

It provides the persistence primitive underneath them.

Append. Replay. Recover.

That's it.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Eryri.WriteAheadLog:

Package Downloads
Eryri.FileDistributedCache

High performance disposable cache, using local disk for more capacity without allocating more RAM or running a cache service. A single-process, filesystem-backed IDistributedCache and IBufferDistributedCache with expiration and automatic byte-limit eviction. Not shared or durable.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 40 10/8/2026
1.0.0 45 10/8/2026