Durable Streams API https://s2.dev
  • Rust 99.3%
  • Shell 0.3%
  • Smarty 0.2%
  • Just 0.1%
Find a file
History 2026-01-20 20:07:14 -05:00
.claude/skills/release just: add release target, simplify skill 2026-01-20 18:24:07 +02:00
.github/workflows chore: update readme gif (#85) 2026-01-20 18:49:51 -05:00
api chore: updated GH workflow to sync specs submodule (#56) 2026-01-14 23:31:23 -05:00
assets chore: update readme gif (#85) 2026-01-20 18:49:51 -05:00
common shared version for all 3 crates (#44) 2026-01-11 08:46:17 -05:00
lite fix(lite): bound resolve_timestamp scan to stream (#88) 2026-01-20 19:29:15 -05:00
.gitignore init 2025-11-07 12:09:00 -08:00
.gitmodules chore: fix submodule sync workflow (#57) 2026-01-14 23:47:18 -05:00
.rustfmt.toml init 2025-11-07 12:09:00 -08:00
AGENTS.md claude skill for /release 2026-01-17 10:58:04 -05:00
Cargo.lock release: 0.3.4 2026-01-20 20:07:14 -05:00
Cargo.toml release: 0.3.4 2026-01-20 20:07:14 -05:00
CLAUDE.md s2-lite backend (#11) 2026-01-04 23:37:38 -05:00
justfile just: add release target, simplify skill 2026-01-20 18:24:07 +02:00
LICENSE update copyright line in license 2026-01-17 17:03:56 -05:00
README.md chore: update readme gif (#85) 2026-01-20 18:49:51 -05:00

S2, the durable streams API

s2.dev is a serverless datastore for real-time, streaming data.

s2-lite

s2-lite is an open source, self-hostable server implementation of the S2 API.

It uses SlateDB as its storage engine, which relies entirely on object storage for durability.

It is easy to run s2-lite against object stores like AWS S3 and Tigris. It is a single-node binary with no other external dependencies. Just like s2.dev, data is always durable on object storage before being acknowledged or returned to readers.

You can also simply not specify a --bucket, which makes it operate entirely in-memory. This is great for integration tests involving S2.

Quickstart

Note

Point CLI or SDKs at your lite instance like this:

export S2_ACCOUNT_ENDPOINT="http://localhost:8080"
export S2_BASIN_ENDPOINT="http://localhost:8080"
export S2_ACCESS_TOKEN="redundant"

Here's how you can run in-memory without any external dependency:

docker run -p 8080:80 ghcr.io/s2-streamstore/s2-lite
AWS S3 bucket example
docker run -p 8080:80 \
  -e AWS_PROFILE=${AWS_PROFILE} \
  -v ~/.aws:/root/.aws:ro \
  ghcr.io/s2-streamstore/s2-lite \
  --bucket ${S3_BUCKET} \
  --path s2lite
Static credentials example (Tigris, R2 etc)
docker run -p 8080:80 \
  -e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
  -e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
  -e AWS_ENDPOINT_URL_S3=${AWS_ENDPOINT_URL_S3} \
  ghcr.io/s2-streamstore/s2-lite \
  --bucket ${S3_BUCKET} \
  --path s2lite

Let's make sure the server is ready:

while ! curl -sf ${S2_ACCOUNT_ENDPOINT}/ping -o /dev/null; do echo Waiting...; sleep 2; done && echo Up!

Install the CLI or upgrade it if s2 --version is older than 0.23

Let's create a basin with auto-creation of streams enabled:

s2 create-basin liteness --create-stream-on-append --create-stream-on-read

Test your latencies:

s2 bench liteness -t 10 -d 5s -w 0s

S2 Ping Test

Now let's try streaming sessions. In one or more new terminals (make sure you re-export the env vars noted above),

s2 read s2://liteness/starwars 2> /dev/null

Now back from your original terminal, let's write to the stream:

nc starwars.s2.dev 23 | s2 append s2://liteness/starwars

S2 Star Wars Streaming

Monitoring

/ping will pong

/metrics returns Prometheus text format

Internals

SlateDB settings

Settings reference

Use SL8_ prefixed environment variables, e.g.:

# Defaults to 50ms for remote bucket / 5ms in-memory
SL8_FLUSH_INTERVAL=10ms

Design

Concepts

  • HTTP serving is implemented using axum
  • Each stream corresponds to a Tokio task called streamer that owns the current tail position, serializes appends, and broadcasts acknowledged records to followers
  • Appends are pipelined to improve performance against high-latency object storage
  • lite::backend::kv::Key documents the data modeling in SlateDB

Caveats

Compatibility

API Coverage

Tip

Complete specs are available: OpenAPI for the REST-ful core, Protobuf definitions, and S2S which is the streaming session protocol.

Fully supported

  • /basins
  • /streams
  • /streams/{stream}/records

Important

Unlike the cloud service where the basin is implicit as a subdomain, /streams/* requests must specify the basin using the S2-Basin header. The SDKs take care of this automatically.

Not supported