Skip to content

Add "Subscription Semantics" page #278

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const nav = {
page('SQL Reference', 'sql', 'sql/index.md'),
section('Subscriptions'),
page('Subscription Reference', 'subscriptions', 'subscriptions/index.md'),
page('Subscription Semantics', 'subscriptions/semantics', 'subscriptions/semantics.md'),
section('Row Level Security'),
page('Row Level Security', 'rls', 'rls/index.md'),
section('How To'),
Expand Down
84 changes: 84 additions & 0 deletions docs/subscriptions/semantics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<!--This document was generated by ChatGPT based on a Discord discussion starting https://discord.com/channels/1037340874172014652/1138987509834059867/1354817326490325072 and then manually touched up.-->

# SpacetimeDB Subscription Semantics

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.

## WebSocket Communication Channels

A single WebSocket connection between a client and the SpacetimeDB host consists of two distinct message channels:

- **Client → Server:** Sends requests such as reducer invocations and subscription queries.
- **Server → Client:** Sends responses to client requests and database transaction updates.

### Ordering Guarantees

The server maintains the following guarantees:

1. **Sequential Response Ordering:**
- 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.

2. **Atomic Transaction Updates:**
- 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.

3. **Atomic Subscription Initialization:**
- When subscriptions are established, clients receive exactly one response containing all initially matching rows from a consistent database state snapshot taken between two transactions.
- The state snapshot reflects a committed database state that includes all previous transaction updates received and excludes all future transaction updates.

## Subscription Workflow

When invoking `SubscriptionBuilder::subscribe(QUERIES)` from the client SDK:

1. **Client SDK → Host:**
- Sends a `Subscribe` message containing the specified QUERIES.

2. **Host Processing:**
- Captures a snapshot of the committed database state.
- Evaluates the QUERIES against this snapshot to determine matching rows.

3. **Host → Client SDK:**
- Sends a `SubscribeApplied` message containing the matching rows.

4. **Client SDK Processing:**
- Receives and processes the message.
- Locks the client cache and inserts all rows atomically.
- Invokes relevant callbacks:
- `on_insert` callback for each row.
- `on_applied` callback for the subscription.
> **Note:** No relative ordering guarantees are made regarding the invocation order of these callbacks.

## Transaction Update Workflow

Upon committing a database transaction:

1. **Transaction Results in a State Delta:**
- The result of a transaction is a state delta, i.e. an unordered set of inserted and deleted rows.

2. **Host Evaluates Queries:**
- Evaluates the QUERIES against the state delta to determine matching altered rows.

3. **Host → Client SDK:**
- Sends a `TransactionUpdate` message if relevant updates exist, containing affected rows and transaction metadata.

4. **Client SDK Processing:**
- Receives and processes the message.
- Locks the client cache, applying deletions and insertions atomically.
- Invokes relevant callbacks:
- `on_insert`, `on_delete`, `on_update` callbacks for modified rows.
- Reducer callbacks, if the transaction was the result of a reducer.
> **Note:** No relative ordering guarantees are made regarding the invocation order of these callbacks.

## Multiple Subscription Sets

If multiple subscription sets are active, updates across these sets are bundled together into a single `TransactionUpdate` message.

## Client Cache Guarantees

- The client cache always maintains a consistent and correct subset of the committed database state.
- Callback functions invoked due to events have guaranteed visibility into a fully updated cache state.
- Reads from the client cache are effectively free as they access locally cached data.
- During callback execution, the client cache accurately reflects the database state immediately following the event-triggering transaction.

### Pending Callbacks and Cache Consistency

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.
1 change: 1 addition & 0 deletions nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const nav: Nav = {

section('Subscriptions'),
page('Subscription Reference', 'subscriptions', 'subscriptions/index.md'),
page('Subscription Semantics', 'subscriptions/semantics', 'subscriptions/semantics.md'),

section('Row Level Security'),
page('Row Level Security', 'rls', 'rls/index.md'),
Expand Down