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​