Durable streams
over
plain HTTP,
backed by S3.
One durable timeline per document, session, or agent run. Replayable, tailable, self-hosted.
# create a bucket, then a stream
$ curl -X PUT http://127.0.0.1:4437/demo
$ curl -X PUT http://127.0.0.1:4437/demo/hello
# append, acknowledged at Raft quorum
$ curl -X POST http://127.0.0.1:4437/demo/hello \
--data-binary 'hello world'
# replay from any offset, or tail live
$ curl 'http://127.0.0.1:4437/demo/hello?offset=-1'
hello worldMeasured
- 41,552
- commits/s aggregate · 500 streams
- 8.3 ms
- sse fan-out p99 · 1,000 subscribers, one stream
- 253 ms
- catch-up replay p99 · 1,000 cold clients
measured 2026-05-22 · 3 × c7g.4xlarge · s3 cold flush on, ~675 MiB uploaded · 256 B payloads · one client binary
What Ursula keeps
Every other server we evaluated for the Durable Streams Protocol gives up at least one of these four. Keeping all four is Ursula's reason to exist.
- 01
Open-source self-hosting
Apache-2.0, the complete server. Deploys as one binary, a Docker image, or a Helm chart.
- 02
Low write latency
Appends commit in a quorum-replicated in-memory ring. No batching window, no S3 PUT on the write path.
- 03
Plain S3 economics
Cold tier on S3 Standard. No Express tier, no per-GB SaaS markup.
- 04
Quorum-replicated durability
Acknowledged writes survive a single-node failure.
Proven live
Survives single-node failure,
on record.
A 3-node cluster on EKS takes voter Pod failures around the clock. Kubernetes recreates each voter while every read is verified against a running checksum.
- --d--h--m--s
- running
- ----
- faults injected
- ------
- offsets verified
- -
- data corruptions
How
Replicate in memory,
flush to S3 in the background.
- Thread-per-core × multi-Raft
- Each stream hashes to one Raft group and one owner core. Hot-path requests touch that core only. A slow follower in one group never stalls another.
- Hot ring
- Writes commit at Raft quorum in memory, so appends take single-digit milliseconds with no separate broker and no batched commits.
- S3 cold tier
- Older segments flush to S3 in the background. One GET transparently spans hot and cold.
When it's the wrong shape
Where do your clients run,
and what is one stream?
These two questions decide the fit. In-network services pushing a few high-throughput pipelines is the wrong shape:
- Kafka / RedpandaA few high-throughput topics, in-network consumer groups
- S2Managed service, willing to adopt a vendor API
- S3 directlyImmutable blobs, no append or tail
- etcdSmall consistent cluster-wide state
- UrsulaMany small per-resource streams, open-internet HTTP clients, replay and live tail in one primitive→
Run it
# single in-memory node on :4437
$ docker run --rm -p 4437:4437 ghcr.io/tonbo-io/ursula:0.3.22