Skip to content

Commit 5c85e76

Browse files
authored
Add "Subscription Semantics" page (#278)
We've gotten several questions in the public Discord about the semantics of subscriptions in the SDKs. I did a brain-dump about them, Tyler fed it into ChatGPT, I touched up the result a bit, and here it is. Some day we probably want to rewrite this to read less like AI slop. But for now it's probably fine.
1 parent f873bb7 commit 5c85e76

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed

docs/nav.js

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const nav = {
3939
page('SQL Reference', 'sql', 'sql/index.md'),
4040
section('Subscriptions'),
4141
page('Subscription Reference', 'subscriptions', 'subscriptions/index.md'),
42+
page('Subscription Semantics', 'subscriptions/semantics', 'subscriptions/semantics.md'),
4243
section('Row Level Security'),
4344
page('Row Level Security', 'rls', 'rls/index.md'),
4445
section('How To'),

docs/subscriptions/semantics.md

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<!--This document was generated by ChatGPT based on a Discord discussion starting https://discord.com/channels/1037340874172014652/1138987509834059867/1354817326490325072 and then manually touched up.-->
2+
3+
# SpacetimeDB Subscription Semantics
4+
5+
This document describes the subscription semantics maintained by the SpacetimeDB host over WebSocket connections. These semantics outline message ordering guarantees, subscription handling, transaction updates, and client cache consistency.
6+
7+
## WebSocket Communication Channels
8+
9+
A single WebSocket connection between a client and the SpacetimeDB host consists of two distinct message channels:
10+
11+
- **Client → Server:** Sends requests such as reducer invocations and subscription queries.
12+
- **Server → Client:** Sends responses to client requests and database transaction updates.
13+
14+
### Ordering Guarantees
15+
16+
The server maintains the following guarantees:
17+
18+
1. **Sequential Response Ordering:**
19+
- Responses to client requests are always sent back in the same order the requests were received. If request A precedes request B, the response to A will always precede the response to B, even if A takes longer to process.
20+
21+
2. **Atomic Transaction Updates:**
22+
- Each database transaction (e.g., reducer invocation, INSERT, UPDATE, DELETE queries) generates exactly zero or one update message sent to clients. These updates are atomic and reflect the exact order of committed transactions.
23+
24+
3. **Atomic Subscription Initialization:**
25+
- When subscriptions are established, clients receive exactly one response containing all initially matching rows from a consistent database state snapshot taken between two transactions.
26+
- The state snapshot reflects a committed database state that includes all previous transaction updates received and excludes all future transaction updates.
27+
28+
## Subscription Workflow
29+
30+
When invoking `SubscriptionBuilder::subscribe(QUERIES)` from the client SDK:
31+
32+
1. **Client SDK → Host:**
33+
- Sends a `Subscribe` message containing the specified QUERIES.
34+
35+
2. **Host Processing:**
36+
- Captures a snapshot of the committed database state.
37+
- Evaluates the QUERIES against this snapshot to determine matching rows.
38+
39+
3. **Host → Client SDK:**
40+
- Sends a `SubscribeApplied` message containing the matching rows.
41+
42+
4. **Client SDK Processing:**
43+
- Receives and processes the message.
44+
- Locks the client cache and inserts all rows atomically.
45+
- Invokes relevant callbacks:
46+
- `on_insert` callback for each row.
47+
- `on_applied` callback for the subscription.
48+
> **Note:** No relative ordering guarantees are made regarding the invocation order of these callbacks.
49+
50+
## Transaction Update Workflow
51+
52+
Upon committing a database transaction:
53+
54+
1. **Transaction Results in a State Delta:**
55+
- The result of a transaction is a state delta, i.e. an unordered set of inserted and deleted rows.
56+
57+
2. **Host Evaluates Queries:**
58+
- Evaluates the QUERIES against the state delta to determine matching altered rows.
59+
60+
3. **Host → Client SDK:**
61+
- Sends a `TransactionUpdate` message if relevant updates exist, containing affected rows and transaction metadata.
62+
63+
4. **Client SDK Processing:**
64+
- Receives and processes the message.
65+
- Locks the client cache, applying deletions and insertions atomically.
66+
- Invokes relevant callbacks:
67+
- `on_insert`, `on_delete`, `on_update` callbacks for modified rows.
68+
- Reducer callbacks, if the transaction was the result of a reducer.
69+
> **Note:** No relative ordering guarantees are made regarding the invocation order of these callbacks.
70+
71+
## Multiple Subscription Sets
72+
73+
If multiple subscription sets are active, updates across these sets are bundled together into a single `TransactionUpdate` message.
74+
75+
## Client Cache Guarantees
76+
77+
- The client cache always maintains a consistent and correct subset of the committed database state.
78+
- Callback functions invoked due to events have guaranteed visibility into a fully updated cache state.
79+
- Reads from the client cache are effectively free as they access locally cached data.
80+
- During callback execution, the client cache accurately reflects the database state immediately following the event-triggering transaction.
81+
82+
### Pending Callbacks and Cache Consistency
83+
84+
While processing a `TransactionUpdate` message, callbacks are queued within the SDK and deferred until the cache updates (inserts/deletes) from a transaction are fully applied. This ensures all callbacks see the fully consistent state of the cache, preventing callbacks from observing an inconsistent intermediate state.

nav.ts

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ const nav: Nav = {
8989

9090
section('Subscriptions'),
9191
page('Subscription Reference', 'subscriptions', 'subscriptions/index.md'),
92+
page('Subscription Semantics', 'subscriptions/semantics', 'subscriptions/semantics.md'),
9293

9394
section('Row Level Security'),
9495
page('Row Level Security', 'rls', 'rls/index.md'),

0 commit comments

Comments
 (0)