Skip to content

Repository files navigation

ably-server

CI

A single-binary, Ably-compatible server. Speaks Ably's realtime WebSocket protocol and the core REST pub/sub endpoints, so existing Ably client SDKs can connect with only a host/port override.

This is work in progress; see Status below.

Why this exists

Ably's cloud is the production answer for realtime messaging, but there are situations where running a local, self-contained server is more convenient:

  • Local development — no internet, no shared sandbox app, no shared rate limits. Point your SDK at localhost and go.
  • CI — a deterministic, disposable broker spun up per test run.
  • Self-hosting — single-region deployments where the operator does not need (or want) Ably's cloud.

The goal is "drop-in for the use cases above": the SDK doesn't change, only the endpoint does.

How it works

One Go binary, three storage modes selected by --mode:

Mode State Pub/sub Use case
memory in-process in-process tests, local dev, ephemeral
disk embedded KV in-process single-node with persistence
cluster Postgres LISTEN/NOTIFY N stateless nodes, shared DB

Server processes are stateless: any node can serve any connection. There's no peer-to-peer membership or gossip — in cluster mode, the database is the coordination point.

Surface area (subset of Ably's protocol — see DESIGN.md for the full spec):

  • WebSocket at GET /ATTACH / DETACH / MESSAGE with channelSerial-based attachment continuity and rewind.
  • RESTPOST/GET /channels/{name}/messages, GET /channels/{name}/presence[/history], GET /time, GET /healthz, GET /readyz.
  • Presence — enter/update/leave, sync on attach, presence history.
  • Mutable messages — message update/delete/append with version history.
  • Auth — API key (Basic) or JWT (HS256) with Ably-style capabilities.

Out of scope: push, integrations, multi-region, Spaces, Chat, LiveObjects, and the rest of the cloud-only product surface.

Quickstart

Requires Go 1.26+.

# Pick any key in the Ably format: <appId>.<keyId>:<secret>
export ABLY_SERVER_KEYS=app.key:secret

go run ./cmd/ably-server --listen :8080

Publish via REST:

curl -u "$ABLY_SERVER_KEYS" \
  -H 'Content-Type: application/json' \
  -d '{"name":"greeting","data":"hello"}' \
  http://localhost:8080/channels/test/messages

Connect with an Ably SDK by pointing it at the local host:

client, _ := ably.NewRealtime(
    ably.WithKey("app.key:secret"),
    ably.WithRealtimeHost("localhost"),
    ably.WithEnvironment(""),
    ably.WithPort(8080),
    ably.WithTLS(false),
)

Modes

# In-memory (default)
ably-server --mode memory

# On-disk (bbolt) persistence
ably-server --mode disk --data-dir ./data

# Clustered against Postgres
export ABLY_SERVER_POSTGRES_DSN='postgres://user:pw@host:5432/db?sslmode=disable'
ably-server --mode cluster

Run ably-server --help for the full flag list. Every option can also be set in a TOML config file passed via --config; see config.example.toml for every key documented with its default.

Local cluster (Docker Compose)

To run the full cluster topology locally — one PostgreSQL instance and three stateless ably-server nodes sharing it for both state and pub/sub:

docker compose up --build

The nodes auto-migrate the empty database on boot (under a Postgres advisory lock), so there's no manual setup. Each node is reachable on its own host port and all three share the key app.key:secret, so a client can attach to any of them:

Node Endpoint
node1 http://localhost:8081
node2 http://localhost:8082
node3 http://localhost:8083

Publish to one node and read it back from another (the shared DB carries the message across):

curl -u app.key:secret -H 'Content-Type: application/json' \
  -d '{"name":"greeting","data":"hello"}' \
  http://localhost:8081/channels/test/messages

curl -u app.key:secret http://localhost:8082/channels/test/history

Benchmarking

cmd/ably-bench drives pub/sub load against a running server (a single node or the Compose cluster above), checks delivery correctness, measures end-to-end latency, and can search for the highest throughput that stays within a latency budget.

# Fixed-rate run against the local cluster (default endpoints):
go run ./cmd/ably-bench --rate 5000 --duration 10s

# Find the max throughput within p50<=20ms, p99<=100ms:
go run ./cmd/ably-bench --search --p50 20ms --p99 100ms --max-rate 100000

# Target a single in-memory node instead:
go run ./cmd/ably-bench --endpoints localhost:8090 --rate 20000

Each message carries its publisher id, a per-publisher sequence number, and a publish timestamp; the same process publishes and subscribes, so latency is measured against one clock with no skew. Correctness is a hard check — any loss, duplication, or per-channel reordering fails the run. --search ramps the offered load until the budget breaks, then binary- searches for the highest sustained rate that still meets it. Run go run ./cmd/ably-bench --help for the full flag list.

Sandbox provisioner

cmd/ably-local-sandbox is a test-app provisioner for the Ably SDK test suites (see DESIGN.md §15). ably-server itself is strictly single-app, but SDK test suites expect a sandbox host that hands out a fresh app per run; ably-local-sandbox bridges the gap by spawning one isolated, in-memory ably-server child per provisioned app:

  • POST /apps takes an Ably test-app-setup post_apps body (keys, namespaces, channels) and boots a child for it, returning the app JSON extended with endpoint/port/tls so a client can connect straight to the child.
  • DELETE /apps/{appId} tears that child down (idempotent).
go build -o ably-server ./cmd/ably-server
go build -o ably-local-sandbox ./cmd/ably-local-sandbox

./ably-local-sandbox --listen :9080 --server-bin ./ably-server

Children are always booted with the stats stub enabled (--enable-stats-stub), since compat suites POST stats fixtures before reading them back, but the core server leaves that stub off by default. Run ably-local-sandbox --help for the full flag list (idle-TTL, log directory/level, etc).

Point an SDK test suite's sandbox host at http://localhost:9080 to run it against local infrastructure instead of Ably's hosted sandbox.

Status

This is an preview of a project under active development. The code is not feature-complete and the protocol coverage is partial.Expect interfaces, feature coverage and conformance to change as the project progresses towards a formal release.

See DESIGN.md for the target functionality and scope.

Feedback and bug reports are welcome via GitHub Issues; see CONTRIBUTING.md for how to build, test, and open a pull request.

Further reading

License

Licensed under the Apache License, Version 2.0.

About

A single-binary, Ably compatible server.

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages