Multi-Server Clusters¶
How QS-Bridge manages N game servers from a single SpacetimeDB instance.
Overview¶
Multi-server clustering is the centrepiece of QS-Bridge. A single SpacetimeDB module serves the platform layer of an entire cluster. Platform tables like ServerConfig carry a server_id: String column for per-server partitioning; global tables like BanEntry are shared across all servers.
Game-specific tables (positions, vitals, buildings, AI, etc.) live in separate sub-databases owned by each game module — not in the platform module.
Topology¶
graph TD
subgraph Servers["Game Servers"]
A["Game Server A<br/>Game Process + libqsbridge.so 8 MB<br/>QSB_SERVER_ID=us-east-pvp-1"]
B["Game Server B<br/>Game Process + libqsbridge.so 8 MB<br/>QSB_SERVER_ID=eu-west-pve-1"]
N["Game Server N<br/>Game Process + libqsbridge.so 8 MB<br/>QSB_SERVER_ID=au-east-pvp-2"]
end
subgraph STDB["SpacetimeDB Instance"]
SR["ServerRegistry — one row per server, the glue table"]
subgraph Global["Global Tables"]
G1["BanEntry"]
G2["WhitelistEntry"]
G3["AdminRole"]
G4["ModuleConfig"]
G5["PanelSession"]
G6["PanelAuditLog"]
G7["PlayerServerPresence"]
G8["GlobalVault"]
G9["ServerTransferQueue"]
G10["CrossServerEvent"]
end
subgraph Local["Local Tables (server_id column)"]
L1["ServerConfig"]
end
subgraph GameDB["Game Module Sub-Databases"]
GDB1["Each game module gets its own STDB<br/>sub-database with game-specific tables"]
end
end
A -->|"WebSocket (BSATN binary)"| STDB
B -->|"WebSocket (BSATN binary)"| STDB
N -->|"WebSocket (BSATN binary)"| STDB
STDB -->|"WebSocket"| Panel
STDB -->|"HTTPS"| Bisect
Panel["React Panel<br/>panel.qs-zuq.com<br/>Cluster Dash / Server Detail / Player Passport"]
Bisect["BisectHosting Starbase API<br/>server lifecycle<br/>start/stop/restart/wipe/auto-scale"]
Server Identity Strategy¶
Two identifiers per server, both stored in ServerRegistry:
| Field | Type | Purpose | Example |
|---|---|---|---|
server_id |
String (PK) |
User-facing display name, set via QSB_SERVER_ID env var |
"us-east-pvp-1" |
stdb_identity |
Identity (unique) |
Backend STDB identity of the connecting bridge process | Identity(0xabcd…) |
The bridge reads server_id from the QSB_SERVER_ID environment variable. On startup, it calls server_register which upserts the ServerRegistry row, binding the human-readable name to the cryptographic identity.
Table Classification¶
Every platform table is classified by its multi-server scope:
🌍 Global Tables¶
Global tables have no server_id column. All servers see the same data. Changes are instantly visible cluster-wide.
- BanEntry — ban on any server → visible everywhere
- WhitelistEntry — whitelist is cluster-wide
- AdminRole — admin permissions span all servers
- ModuleConfig — platform singleton
- PanelSession — panel sessions are global
- PanelAuditLog — audit trail spans all servers
- PlayerServerPresence — tracks which server each player is on
- GlobalVault — cross-server item bank
- ServerTransferQueue — character transfers between servers
- CrossServerEvent — cluster-spanning meta-events
- ServerRegistry — the glue table itself
🔵 Local Tables¶
Local tables have a server_id column. Game hooks subscribe with WHERE server_id = '{self.server_id}' to see only their own data.
- ServerConfig — each server has its own name, MOTD, password, whitelist setting
Cross-Server Capabilities¶
| # | Capability | Description | Tables Used |
|---|---|---|---|
| 1 | Global ban/whitelist/admin sync | Ban on any server → instantly visible cluster-wide | BanEntry, WhitelistEntry, AdminRole |
| 2 | Player presence tracking | Know which server every player is on, prevent dual-login | PlayerServerPresence |
| 3 | Zero-downtime character transfer | Move characters between servers with data intact | ServerTransferQueue, GlobalVault, PlayerServerPresence |
| 4 | Cross-server item vault | Deposit/withdraw items across servers (game-agnostic JSON blobs) | GlobalVault |
| 5 | Cross-server events | Meta-events spanning multiple servers | CrossServerEvent |
| 6 | Hosting API integration | Start/stop/restart/wipe from panel or reducer | ServerRegistry, BisectHosting API |
| 7 | Live cluster dashboard | Real-time server grid with status, player counts, health | ServerRegistry (panel subscription) |
| 8 | Population flow analysis | Track player movement between servers over time | PlayerServerPresence, ServerRegistry |
Bridge Registration Flow¶
flowchart TD
Start["Bridge Process Start"] --> S1
S1["1. Read QSB_SERVER_ID from environment"] --> S2
S2["2. Connect to SpacetimeDB via WebSocket"] --> S3
S3["3. Receive Identity from STDB (ctx.sender)"] --> S4
S4["4. Call server_register reducer<br/>Upsert ServerRegistry row (server_id ↔ stdb_identity)<br/>Set status = online"] --> S5
S5["5. Subscribe to platform tables<br/>ServerConfig WHERE server_id = self<br/>BanEntry (full), AdminRole (full), WhitelistEntry (full)"] --> S6
S6["6. Start heartbeat timer (30s interval)<br/>Calls server_heartbeat reducer<br/>Updates last_heartbeat + online_count"] --> S7
S7["7. Begin processing game events"]
One Account → Many Servers¶
The primary deployment target is community server operators running multiple game servers under a single BisectHosting account. The BisectHosting Starbase API key maps to one account that owns all servers. ServerRegistry.hosting_id links each STDB server row to its BisectHosting server UUID for lifecycle API calls.
graph TD
Account["BisectHosting Account"] --> S1["Server UUID: abc-123<br/>ServerRegistry: us-east-pvp-1"]
Account --> S2["Server UUID: def-456<br/>ServerRegistry: eu-west-pve-1"]
Account --> S3["Server UUID: ghi-789<br/>ServerRegistry: au-east-pvp-2"]
Per-Server Mod Selection¶
Each server independently chooses which mods to run. The InstalledMod table in the Mod Registry tracks (server_id, project_id, version, status). The same mod can be installed on multiple servers with different versions and configurations.
Related Pages¶
- Platform Overview — table summary
- ServerRegistry Table — the glue table
- Subscriptions — how data flows in real-time
- Servers Page — panel cluster dashboard