Core concepts · 06
Streaming queries
A streaming query is a long-lived subscription against a lattice. Instead of returning a snapshot and closing, it holds the connection open and emits a delta frame every time a matching node or edge changes.
The delta model#
Every lattice keeps an append-only log of committed transactions. A streaming query registers a predicate against that log; the coordinator replays history up to the requested watermark, then tails the log indefinitely. Clients therefore receive two phases from a single subscription.
- Backfill — ordered frames reconstructing current state, terminated by a
syncmarker. - Live tail — one frame per committed transaction touching the predicate.
Because both phases share a frame envelope, application code does not need a separate loading branch. Render the first frame you get and keep rendering.
Backfill and tail frames are indistinguishable except for the phase field. Treat the marker as an optional optimisation, never as a correctness requirement.
Opening a stream#
The TypeScript SDK exposes streams as async iterables. Cancellation is cooperative: breaking out of the loop tears down the socket and releases the server-side cursor within one heartbeat interval.
import { connect } from "@lattice/client";
const lattice = await connect({ region: "eu-west-1" });
const stream = lattice.query(`
match (order:Order)-[:PLACED_BY]->(c:Customer)
where order.status in ["pending","picking"]
emit order.id, order.status, c.tier
`, { from: "watermark:latest-24h" });
for await (const frame of stream) {
if (frame.phase === "sync") ui.markReady();
ui.apply(frame.deltas); // idempotent by (id, lsn)
}
Connection parameters
| Parameter | Type | Default | Notes |
|---|---|---|---|
from | watermark | now | Replay origin. Accepts LSN, timestamp, or alias. |
batch | integer | 256 | Max deltas per frame during backfill. |
heartbeat | duration | 15s | Empty keepalive frame interval. |
compression | enum | zstd | One of none, gzip, zstd. |
maxLag | duration | none | Server closes the stream if the client falls behind. |
Frame envelope#
Frames are newline-delimited JSON over HTTP/2, or CBOR over the binary transport. The envelope is stable across both and versioned independently of the payload schema.
{
"v": 2,
"phase": "tail",
"lsn": "0000-4c1a-9f30",
"emittedAt": "2026-06-11T09:14:02.418Z",
"deltas": [
{ "op": "upsert", "id": "order_81f2",
"fields": { "status": "picking", "tier": "gold" } }
]
}
Ordering guarantees
Within a single partition, frames arrive in commit order and lsn increases monotonically. Across partitions, only causal order is guaranteed — two unrelated writes may be observed in either sequence. Applications that need a global order should request consistency: "linearizable" and accept the added latency.
Backpressure#
The coordinator maintains a per-subscription buffer of 64 MiB. When a slow consumer fills it, the server applies one of three strategies, selected at open time.
- Block — stop reading the log until the client drains. Safest, but a single slow client can pin resources.
- Coalesce — collapse repeated updates to the same key into the newest value. Default for UI workloads.
- Drop & resync — abandon the buffer and force the client to reopen from a fresh watermark.
Coalescing is lossy for event-sourced consumers. If you need every intermediate transition, choose block and provision a dead-letter sink.
Reconnection#
Persist the last applied lsn. On reconnect, pass it as from and the coordinator resumes exactly where you stopped, provided the log segment is still retained — 7 days on standard plans, 30 on enterprise.
If the watermark has aged out, the server responds with 410 Gone and a suggested fallback watermark. Rebuild from a full backfill rather than assuming continuity.
Retry budget
Clients should use exponential backoff with full jitter, capped at 30 seconds, and abandon after 12 consecutive failures. The SDK implements this by default; override with retry: { cap, attempts }.
Limits & quotas#
| Limit | Standard | Enterprise |
|---|---|---|
| Concurrent streams per key | 250 | 10,000 |
| Predicate complexity units | 40 | 200 |
| Log retention | 7 days | 30 days |
| Frame size | 1 MiB | 8 MiB |
| Sustained delta rate | 5k/s | 120k/s |
Quota headers are returned on the initial handshake response and refreshed on every heartbeat frame, so long-lived clients see quota changes without reconnecting.