Building a Low‑Latency WebSocket Push Layer with SurrealDB SUBSCRIBE
Deploy a lightweight real‑time push layer with SurrealDB’s SUBSCRIBE feature: design a single‑node architecture, enforce role‑based ACLs, monitor health and latency, and handle reconnections with event IDs. This guide walks through configuration, operational checks, and failure‑mode recovery.
12 Nov 2025, 16:49 UTC

Problem Statement & Takeaway
You need a real‑time dashboard that pushes data to browser clients as soon as it arrives in the database. Polling is too slow and wasteful, so you want to use SurrealDB’s built‑in SUBSCRIBE command. The key challenges are: keeping the push path lightweight, enforcing clear trust boundaries between data producers and consumers, and ensuring the system can recover from node or network failures without data loss or duplicate events.
Minimum Viable Architecture
The smallest deployment that satisfies these constraints is a single SurrealDB node running a WebSocket gateway that forwards subscription events to connected clients. The gateway sits behind a reverse proxy (e.g., Nginx) that terminates TLS and provides connection limits. The architecture looks like this:
| Component | Role |
|---|---|
| SurrealDB Node | Stores data, streams changes via SUBSCRIBE |
| WebSocket Gateway | Accepts client connections, forwards events, handles reconnection logic |
| Reverse Proxy | TLS termination, rate limiting, health‑check routing |
| Prometheus | Scrapes metrics from SurrealDB and the gateway |
All components run in the same data center or region to keep latency low. If your use case grows, you’ll need to move to a multi‑node cluster, but for now a single node is sufficient.
Deploying the SurrealDB Node
# Start SurrealDB in database mode
surreal start -s "memory" -p 8000
# Create database and table
surreal sql -d test_db -u root -p root -q \
"CREATE TABLE telemetry;"
Replace memory with postgres://… or another backend if persistence is required.
Trust & ACL Design
SurrealDB’s ACL lets you separate roles so that write services never see subscription data and vice versa. Define two roles:
CREATE ROLE writer WITH PERMISSIONS
SELECT * FROM telemetry;
CREATE ROLE subscriber WITH PERMISSIONS
SELECT * FROM telemetry;
Assign the writer role to your micro‑service that inserts telemetry. Assign subscriber to the WebSocket gateway. The gateway then opens a SUBSCRIBE telemetry; session under that role. Because the roles share the same table, the gateway only receives events it is allowed to read, and the writer cannot subscribe.
Enforcing Role Separation in Code
// Node.js example using surrealdb-js
const db = new Surreal({ url: 'ws://localhost:8000/rpc' });
await db.signin({ user: 'gateway', pass: 'secret' });
await db.use('test_db', 'subscriber');
await db.subscribe('telemetry', (event) => {
// forward to WebSocket client
});
Operational Checks & Monitoring
To keep the system healthy you need three layers of checks:
- SurrealDB Health:
/healthendpoint returns200 OKwhen the node is ready. Expose it on a non‑public port and scrape it with Prometheus.GET http://localhost:8000/health - WebSocket Heartbeat: The gateway should send a ping every 30 s and expect a pong. If no pong arrives within 10 s, close the connection and trigger a reconnect.
ws.ping(); // on interval ws.on('pong', () => { /* reset timer */ }); - Subscription Latency: SurrealDB emits an
event_idwith each change. Record the time difference between event generation (timestamp in the row) and receipt. Export this as a Prometheus gauge.let lastSeen = 0; ws.on('message', (msg) => { const { event_id, data } = JSON.parse(msg); const latency = Date.now() - data.timestamp; promGauge.set({ event_id }, latency); lastSeen = event_id; });
Failure Modes & Reconnect Strategy
SurrealDB guarantees ordered delivery only per node. A node crash or network partition can drop events. Your gateway must:
- Persist the last
event_idlocally (e.g., in Redis or a small file). - On reconnect, issue
SUBSCRIBE telemetry FROM EVENT_ID <lastSeen>;to resume from the next event. - Handle duplicates by checking the stored
event_idbefore forwarding.
Example reconnection logic:
async function resubscribe() {
const startId = await getLastEventId();
const cursor = await db.subscribe(`telemetry FROM EVENT_ID ${startId}`);
cursor.on('message', (msg) => {
const { event_id, data } = JSON.parse(msg);
if (event_id > startId) {
forwardToClient(data);
storeLastEventId(event_id);
}
});
}
When to Expand: Design Change Triggers
Keep the single‑node design until one of these thresholds is met:
- Write throughput > 10 kWPS or CPU > 80% on sustained periods.
- Fan‑out > 500 concurrent WebSocket clients causing the gateway to hit memory limits.
- Requirement for multi‑region low‑latency or disaster recovery, necessitating a SurrealDB cluster with replication.
- Need for strict ACID guarantees across multiple tables or cross‑node ordering.
When any of these conditions arise, migrate to a multi‑node cluster, enable SurrealDB’s replication mode, and add a load balancer in front of the WebSocket gateway to distribute traffic. Remember that cross‑node ordering is not guaranteed; you’ll need additional sequencing logic if strict order is required.
Verification Checklist
Before deploying to production, run the following tests:
- Create a test database, insert rows, and start a client with
SUBSCRIBE telemetry;Verify events arrive. - Restart the SurrealDB node and confirm the client reconnects automatically, resubscribes from the last event ID, and receives any missed events.
- Simulate a client disconnect (close the WebSocket) and ensure the heartbeat logic triggers a reconnect within 30 s.
- Scrape the
/healthendpoint and confirm Prometheus metrics for latency and uptime are populated. - Load test with 200 concurrent WebSocket clients and 5 kWPS writes; monitor CPU, memory, and disk I/O.
If all checks pass, you can confidently roll out the architecture to a small production environment. Keep an eye on the metrics; if any thresholds are exceeded, trigger the expansion path described above.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.