|
| 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. |
0 commit comments