Using SurrealDB Live Queries for Real‑Time Data Sync
Learn how SurrealDB’s LIVE keyword turns a SELECT into a low‑overhead push subscription, see a concrete WebSocket example, and understand the trade‑offs you need to manage.
12 Dec 2025, 18:25 UTC

Problem: Keeping UI in Sync Without Polling
Applications that need to show the latest data often resort to polling the database every few seconds. This approach wastes bandwidth, adds latency, and complicates client code when the data changes infrequently.
Thesis: Live Queries Deliver Minimal‑Overhead Updates
SurrealDB’s LIVE modifier turns a regular SELECT into a persistent subscription. The server pushes only the changed rows as compact JSON deltas over a WebSocket (or HTTP) connection, giving you reactive updates with far less traffic than polling.
How Live Queries Work
When you prefix a query with LIVE, SurrealDB maintains a snapshot of the result set for each client and watches the underlying tables for mutations. On insert, update, or delete, the server computes a diff and sends a minimal message containing the affected rows. The feature respects the existing permission system, so each subscriber sees only data they are allowed to read.
Worked Example: Subscribing to a Person Table
- Start the server (run in a terminal with sufficient permissions to bind the port):
surreal start --log trace --bind localhost:8000 - Define a table and insert a seed record** (you can use the SurrealDB CLI or any HTTP client):
surreal use --root --pass root http://localhost:8000 DB> DEFINE TABLE person SCHEMAFULL; DB> CREATE person SET name = 'Alice'; - Open a WebSocket client** (e.g.,
wscat) and subscribe with a live query:
The server will reply with an initial result set (a JSON array) and then keep the connection open.wscat -c ws://localhost:8000/rpc # After connection opens, send: {"id":1,"method":"query","params":{"sql":"LIVE SELECT * FROM person"}} - Make a change from another client** (e.g., insert a new person):
surreal use --root --pass root http://localhost:8000 DB> CREATE person SET name = 'Bob';- Observe the push** – the WebSocket client receives a message similar to:
Only the newly inserted row is sent; no full table resend occurs.{"id":1,"result":[{"id":"person:xxxxx","name":"Bob"}]}- Clean up** – close the WebSocket (Ctrl+C in wscat) or let the client disconnect. The server logs show the subscription being removed, confirming no leaked resources.
- Observe the push** – the WebSocket client receives a message similar to:
Trade‑offs and Limitations
- Memory usage – each active live query holds a copy of its result set in server memory. Large result sets or many concurrent subscribers can increase RAM consumption noticeably.
- Network reliability – if the WebSocket drops, intermediate changes may be lost. Clients must implement reconnection logic and, upon reconnection, re‑issue the live query to receive a fresh snapshot and continue receiving deltas.
- Query complexity – live queries work best with simple selects. Aggregations, multi‑table joins, or heavy filtering can prevent incremental diffing, causing the server to fall back to sending the full result set on each change.
Practical way to verify the memory impact: after establishing a few live queries, check the server’s memory metrics (e.g., via surreal info --bind localhost:8000 or monitoring tools) and compare to the baseline with no live subscriptions.
Actionable Next Steps
1. Add exponential‑backoff reconnection and resubscription logic in your client library.
2. Test with realistic data volumes to ensure memory stays within your infrastructure limits.
3. Prefer simple projections (LIVE SELECT id, name FROM person WHERE active = true) to keep diffing efficient.
4. Monitor server logs for subscription creation and cleanup messages during load testing.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.