ably-server

module
v0.0.0-...-668d940 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 19, 2026 License: Apache-2.0

README

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.

Work in progress. This is an experimental implementation — interesting to look at, not yet something to depend on. 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

Some of DESIGN.md is implemented; some is still to come. The code is not feature-complete and the protocol coverage is partial. Treat it as a sketch you can run, not a product.

This is an experimental server whose protocol internals will progressively be replaced by code extracted from Ably's production realtime stack. Interfaces, wire coverage, and behaviour may change without notice, and there are no stability or support guarantees.

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.

Directories

Path Synopsis
cmd
ably-bench command
Command ably-bench drives pub/sub load against a running ably-server (a single node or the docker-compose cluster), verifies delivery correctness, measures end-to-end latency, and — in search mode — finds the highest sustained throughput that stays within a p50/p99 budget.
Command ably-bench drives pub/sub load against a running ably-server (a single node or the docker-compose cluster), verifies delivery correctness, measures end-to-end latency, and — in search mode — finds the highest sustained throughput that stays within a p50/p99 budget.
ably-local-sandbox command
Command ably-local-sandbox is a disposable-instance provisioner for the Ably SDK test suites (DESIGN.md §15).
Command ably-local-sandbox is a disposable-instance provisioner for the Ably SDK test suites (DESIGN.md §15).
ably-server command
Command ably-server is the open-source Ably-compatible server.
Command ably-server is the open-source Ably-compatible server.
compat-gate command
Command compat-gate diffs an SDK compatibility harness's per-test JSON results against a checked-in known-failures list (internal/compatgate), so CI can fail a PR on a genuine regression while staying green on already-tracked gaps.
Command compat-gate diffs an SDK compatibility harness's per-test JSON results against a checked-in known-failures list (internal/compatgate), so CI can fail a PR on a genuine regression while staying green on already-tracked gaps.
internal
auth
Package auth implements credential parsing and verification for realtime and REST requests.
Package auth implements credential parsing and verification for realtime and REST requests.
compatgate
Package compatgate implements the known-failures gate for the SDK compatibility harnesses (ably-go, ably-js, ably-ai-transport-js): diffing a harness's per-test JSON results against a checked-in ignore list of tests currently expected to fail, so CI can fail on a genuine regression while staying green on already-tracked gaps.
Package compatgate implements the known-failures gate for the SDK compatibility harnesses (ably-go, ably-js, ably-ai-transport-js): diffing a harness's per-test JSON results against a checked-in ignore list of tests currently expected to fail, so CI can fail on a genuine regression while staying green on already-tracked gaps.
config
Package config supports ably-server's optional TOML config file (--config, DESIGN.md §9).
Package config supports ably-server's optional TOML config file (--config, DESIGN.md §9).
core
Package core implements the per-channel state and per-process channel manager — DESIGN.md §5.1.
Package core implements the per-channel state and per-process channel manager — DESIGN.md §5.1.
fixtures
Package fixtures pre-seeds channels with presence members declared in the config file's [[channels]] section (DESIGN.md §9, §12.5).
Package fixtures pre-seeds channels with presence members declared in the config file's [[channels]] section (DESIGN.md §9, §12.5).
id
Package id generates the identifiers used on the wire (connection IDs, message IDs).
Package id generates the identifiers used on the wire (connection IDs, message IDs).
logging
Package logging defines the slog conventions shared by the server and its sibling tools: a TRACE level below slog's DEBUG, level-name parsing for the --log-level flag, and a handler hook that renders the custom level with a readable name.
Package logging defines the slog conventions shared by the server and its sibling tools: a TRACE level below slog's DEBUG, level-name parsing for the --log-level flag, and a handler hook that renders the custom level with a readable name.
metrics
Package metrics exposes ably-server's process-wide Prometheus metrics (DESIGN.md §10).
Package metrics exposes ably-server's process-wide Prometheus metrics (DESIGN.md §10).
realtime
Package realtime implements the WebSocket realtime endpoint.
Package realtime implements the WebSocket realtime endpoint.
rest
Package rest implements the REST API handlers exposed by ably-server: channel message publish/history/mutation/versions, presence and presence history, annotations, token requests, health/readiness checks, and gated stats stubs.
Package rest implements the REST API handlers exposed by ably-server: channel message publish/history/mutation/versions, presence and presence history, annotations, token requests, health/readiness checks, and gated stats stubs.
serial
Package serial implements Ably's lexicographically-sortable timeserial format used for the canonical channel ordering identifier (`ChannelMessage.ChannelSerial`, `Message.Serial`).
Package serial implements Ably's lexicographically-sortable timeserial format used for the canonical channel ordering identifier (`ChannelMessage.ChannelSerial`, `Message.Serial`).
server
Package server assembles the ably-server process: flag/env/config parsing, storage/realtime/REST wiring, HTTP routing, and graceful shutdown.
Package server assembles the ably-server process: flag/env/config parsing, storage/realtime/REST wiring, HTTP routing, and graceful shutdown.
storage
Package storage defines the persistence boundary for channel data.
Package storage defines the persistence boundary for channel data.
storage/bbolt
Package bbolt is the on-disk storage backend (DESIGN.md §6.2).
Package bbolt is the on-disk storage backend (DESIGN.md §6.2).
storage/memory
Package memory is an in-process storage backend for ably-server.
Package memory is an in-process storage backend for ably-server.
storage/postgres
Package postgres is the database-backed storage backend used by ably-server's cluster mode (DESIGN.md §6.3, §7.2).
Package postgres is the database-backed storage backend used by ably-server's cluster mode (DESIGN.md §6.3, §7.2).
storage/storagetest
Package storagetest exposes a contract-test suite that every storage.ChannelStore implementation should satisfy.
Package storagetest exposes a contract-test suite that every storage.ChannelStore implementation should satisfy.
tracing
Package tracing wires optional OpenTelemetry trace export, configured entirely through the standard OTEL_* environment variables (DESIGN.md §10).
Package tracing wires optional OpenTelemetry trace export, configured entirely through the standard OTEL_* environment variables (DESIGN.md §10).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL