Skip to main content

Quickstart

Run a node, append an event, read it back.

1. Start a node

The server image is published to GitHub Container Registry for amd64 and arm64:

docker run -d --name celeriant \
--security-opt seccomp=unconfined \
--ulimit memlock=-1:-1 \
-p 10000:10000 \
-v celeriant-data:/var/lib/celeriant \
ghcr.io/celeriant/celeriant:0.2.0 \
--standalone --data-root /var/lib/celeriant --num-shards 1

--standalone is one process with no replication and no S3. Fine for development, wrong for production; run a two-node cluster there. The storage engine uses io_uring, which Docker's default seccomp profile blocks, hence the first two flags. On macOS and Windows, Docker's Linux VM supplies the kernel.

--num-shards defaults to the CPU count and is fixed once the data root exists. Change it later and the server refuses to start.

2. Append and read (.NET)

Add the client:

dotnet add package Celeriant.Client
using System.Text;
using Celeriant.Client;
using Celeriant.Client.Requests;
using Celeriant.Client.Responses;

await using var pool = new CeleriantPool(new CeleriantPoolOptions
{
Address = "localhost:10000",
});

// org / aggregate type / aggregate id
var key = new AggregateKey(
orgId: Guid.Parse("11111111-1111-1111-1111-111111111111"),
aggregateTypeId: Guid.Parse("22222222-2222-2222-2222-222222222222"),
aggregateId: Guid.Parse("33333333-3333-3333-3333-333333333333"));

// who is writing; keep it stable across restarts
var writerId = Guid.Parse("44444444-4444-4444-4444-444444444444");

// append one event; allowCreate defaults to true, so the first write creates the aggregate
var written = await pool.WriteAsync(
key,
[new AggregateEvent
{
ClientSeq = 1,
EventTypeMajor = 1,
EventTimestamp = DateTimeOffset.UtcNow,
EventValue = Encoding.UTF8.GetBytes("""{ "hello": "world" }"""),
}],
clientId: writerId);

Console.WriteLine($"aggregate version {written.MaxAggregateVersion}");

// read it back from the start of the stream
var response = await pool.ReadAsync(new ReadRequest
{
AggregateKey = key,
Filters = ReadFilters.From(1),
});

foreach (var batch in response.EventBatches)
foreach (var e in batch.Events)
Console.WriteLine(Encoding.UTF8.GetString(e.EventValue));

EventTypeMajor must be non-zero and EventTimestamp must be set. The client rejects either mistake with ArgumentException before anything is sent.

The write is durable before WriteAsync returns: fsync'd on this node, and on a cluster also replicated to the follower (or to S3 when the follower is down).

Next steps