spacetimedb-concepts
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSpacetimeDB Core Concepts
SpacetimeDB核心概念
SpacetimeDB is a relational database that is also a server. It lets you upload application logic directly into the database via WebAssembly modules, eliminating the traditional web/game server layer entirely.
SpacetimeDB是一款兼具服务器功能的关系型数据库。它允许你通过WebAssembly模块将应用逻辑直接上传至数据库中,彻底省去了传统的Web/游戏服务器层。
Critical Rules (Read First)
重要规则(请先阅读)
These five rules prevent the most common SpacetimeDB mistakes:
- Reducers are transactional — they do not return data to callers. Use subscriptions to read data.
- Reducers must be deterministic — no filesystem, network, timers, or random. All state must come from tables.
- Read data via tables/subscriptions — not reducer return values. Clients get data through subscribed queries.
- Auto-increment IDs are not sequential — gaps are normal, do not use for ordering. Use timestamps or explicit sequence columns.
- is the authenticated principal — never trust identity passed as arguments. Always use
ctx.sender()for authorization.ctx.sender()
以下五条规则可避免最常见的SpacetimeDB使用错误:
- Reducer是事务性的 —— 它们不会向调用者返回数据,请通过订阅读取数据。
- Reducer必须是确定性的 —— 禁止访问文件系统、网络、定时器或生成随机数。所有状态必须来自表。
- 通过表/订阅读取数据 —— 不要依赖Reducer的返回值。客户端通过订阅的查询获取数据。
- 自增ID并非连续的 —— 存在间隙是正常现象,请勿将其用于排序。请使用时间戳或显式的序列列。
- 是已认证的主体 —— 永远不要信任作为参数传递的身份信息。授权时请始终使用
ctx.sender()。ctx.sender()
Feature Implementation Checklist
功能实现检查清单
When implementing a feature that spans backend and client:
- Backend: Define table(s) to store the data
- Backend: Define reducer(s) to mutate the data
- Client: Subscribe to the table(s)
- Client: Call the reducer(s) from UI — do not skip this step
- Client: Render the data from the table(s)
Common mistake: Building backend tables/reducers but forgetting to wire up the client to call them.
当实现跨后端与客户端的功能时:
- 后端:定义用于存储数据的表
- 后端:定义用于修改数据的Reducer
- 客户端:订阅相关表
- 客户端:从UI调用Reducer —— 请勿跳过此步骤
- 客户端:渲染来自表的数据
常见错误:创建了后端表/Reducer,但忘记在客户端中调用它们。
Debugging Checklist
调试检查清单
When things are not working:
- Is SpacetimeDB server running? ()
spacetime start - Is the module published? ()
spacetime publish - Are client bindings generated? ()
spacetime generate - Check server logs for errors ()
spacetime logs <db-name> - Is the reducer actually being called from the client?
当功能无法正常工作时:
- SpacetimeDB服务器是否正在运行?(执行)
spacetime start - 模块是否已发布?(执行)
spacetime publish - 是否生成了客户端绑定?(执行)
spacetime generate - 检查服务器日志中的错误(执行)
spacetime logs <db-name> - 客户端是否真的调用了Reducer?
CLI Commands
CLI命令
bash
spacetime start
spacetime publish <db-name> --module-path <module-path>
spacetime publish <db-name> --clear-database -y --module-path <module-path>
spacetime generate --lang <lang> --out-dir <out> --module-path <module-path>
spacetime logs <db-name>bash
spacetime start
spacetime publish <db-name> --module-path <module-path>
spacetime publish <db-name> --clear-database -y --module-path <module-path>
spacetime generate --lang <lang> --out-dir <out> --module-path <module-path>
spacetime logs <db-name>What SpacetimeDB Is
什么是SpacetimeDB
SpacetimeDB combines a database and application server into a single deployable unit. Clients connect directly to the database and execute application logic inside it. The system is optimized for real-time applications requiring maximum speed and minimum latency.
Key characteristics:
- In-memory execution: Application state is served from memory for very low-latency access
- Persistent storage: Data is automatically persisted to a write-ahead log (WAL) for durability
- Real-time synchronization: Changes are automatically pushed to subscribed clients
- Single deployment: No separate servers, containers, or infrastructure to manage
SpacetimeDB将数据库与应用服务器整合为一个可部署的独立单元。客户端直接连接到数据库,并在其中执行应用逻辑。该系统专为需要极致速度与最低延迟的实时应用优化。
核心特性:
- 内存内执行:应用状态从内存中读取,实现极低延迟的访问
- 持久化存储:数据自动持久化到预写日志(WAL)中,确保数据安全性
- 实时同步:数据变更会自动推送给已订阅的客户端
- 单一部署单元:无需管理单独的服务器、容器或基础设施
The Five Zen Principles
五大核心原则
- Everything is a Table: Your entire application state lives in tables. No separate cache layer, no Redis, no in-memory state to synchronize.
- Everything is Persistent: SpacetimeDB persists state by default (for example via WAL-backed durability).
- Everything is Real-Time: Clients are replicas of server state. Subscribe to data and it flows automatically.
- Everything is Transactional: Every reducer runs atomically. Either all changes succeed or all roll back.
- Everything is Programmable: Modules are real code (Rust, C#, TypeScript) running inside the database.
- 一切皆为表:你的整个应用状态都存储在表中。无需单独的缓存层、Redis或需要同步的内存状态。
- 一切皆持久化:SpacetimeDB默认持久化状态(例如通过WAL实现的持久性)。
- 一切皆实时:客户端是服务器状态的副本。订阅数据后,数据会自动同步至客户端。
- 一切皆事务性:每个Reducer都以原子方式运行。要么所有变更都成功,要么全部回滚。
- 一切皆可编程:模块是运行在数据库内部的真实代码(Rust、C#、TypeScript)。
Tables
表
Tables store all data in SpacetimeDB. They use the relational model and support SQL queries for subscriptions.
表是SpacetimeDB中所有数据的存储载体。它们采用关系模型,并支持通过SQL查询进行订阅。
Defining Tables
定义表
Tables are defined using language-specific attributes. In 2.0, use (not ) for the API name:
accessornameRust:
rust
#[spacetimedb::table(accessor = player, public)]
pub struct Player {
#[primary_key]
#[auto_inc]
id: u32,
#[index(btree)]
name: String,
#[unique]
email: String,
}C#:
csharp
[SpacetimeDB.Table(Accessor = "Player", Public = true)]
public partial struct Player
{
[SpacetimeDB.PrimaryKey]
[SpacetimeDB.AutoInc]
public uint Id;
[SpacetimeDB.Index.BTree]
public string Name;
[SpacetimeDB.Unique]
public string Email;
}TypeScript:
typescript
const players = table(
{ name: 'players', public: true },
{
id: t.u32().primaryKey().autoInc(),
name: t.string().index('btree'),
email: t.string().unique(),
}
);表通过特定语言的特性进行定义。在2.0版本中,请使用(而非)作为API名称:
accessornameRust:
rust
#[spacetimedb::table(accessor = player, public)]
pub struct Player {
#[primary_key]
#[auto_inc]
id: u32,
#[index(btree)]
name: String,
#[unique]
email: String,
}C#:
csharp
[SpacetimeDB.Table(Accessor = "Player", Public = true)]
public partial struct Player
{
[SpacetimeDB.PrimaryKey]
[SpacetimeDB.AutoInc]
public uint Id;
[SpacetimeDB.Index.BTree]
public string Name;
[SpacetimeDB.Unique]
public string Email;
}TypeScript:
typescript
const players = table(
{ name: 'players', public: true },
{
id: t.u32().primaryKey().autoInc(),
name: t.string().index('btree'),
email: t.string().unique(),
}
);Table Visibility
表的可见性
- Private tables (default): Only accessible by reducers and the database owner
- Public tables: Exposed for client read access through subscriptions. Writes still require reducers.
- 私有表(默认):仅Reducer和数据库所有者可访问
- 公共表:开放给客户端通过订阅读取数据。写入操作仍需通过Reducer完成。
Table Design Principles
表设计原则
Organize data by access pattern, not by entity:
Decomposed approach (recommended):
Player PlayerState PlayerStats
id <-- player_id player_id
name position_x total_kills
position_y total_deaths
velocity_x play_timeBenefits: reduced bandwidth, cache efficiency, schema evolution, semantic clarity.
按访问模式组织数据,而非按实体:
推荐的分解方式:
Player PlayerState PlayerStats
id <-- player_id player_id
name position_x total_kills
position_y total_deaths
velocity_x play_time优势:减少带宽占用、提升缓存效率、便于Schema演进、语义清晰。
Reducers
Reducer
Reducers are transactional functions that modify database state. They are the primary client-invoked mutation path; procedures can also mutate tables by running explicit transactions.
Reducer是用于修改数据库状态的事务性函数。它们是客户端触发状态变更的主要途径;过程(Procedures)也可通过显式事务修改表。
Key Properties
核心特性
- Transactional: Run in isolated database transactions
- Atomic: Either all changes succeed or all roll back
- Isolated: Cannot interact with the outside world (no network, no filesystem)
- Callable: Clients invoke reducers as remote procedure calls
- 事务性:在独立的数据库事务中运行
- 原子性:要么所有变更都成功,要么全部回滚
- 隔离性:无法与外部交互(无网络、无文件系统访问)
- 可调用:客户端通过远程过程调用(RPC)触发Reducer
Critical Reducer Rules
Reducer重要规则
- No global state: Relying on static variables is undefined behavior
- No side effects: Reducers cannot make network requests or access files
- Store state in tables: All persistent state must be in tables
- No return data: Reducers do not return data to callers — use subscriptions
- Must be deterministic: No random, no timers, no external I/O
- 无全局状态:依赖静态变量属于未定义行为
- 无副作用:Reducer无法发起网络请求或访问文件
- 状态存储于表中:所有持久化状态必须存储在表内
- 无返回数据:Reducer不会向调用者返回数据 —— 请使用订阅
- 必须是确定性的:不能包含随机操作、定时器或外部I/O
Defining Reducers
定义Reducer
Rust:
rust
#[spacetimedb::reducer]
pub fn create_user(ctx: &ReducerContext, name: String, email: String) -> Result<(), String> {
if name.is_empty() {
return Err("Name cannot be empty".to_string());
}
ctx.db.user().insert(User { id: 0, name, email });
Ok(())
}C#:
csharp
[SpacetimeDB.Reducer]
public static void CreateUser(ReducerContext ctx, string name, string email)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("Name cannot be empty");
ctx.Db.User.Insert(new User { Id = 0, Name = name, Email = email });
}Rust:
rust
#[spacetimedb::reducer]
pub fn create_user(ctx: &ReducerContext, name: String, email: String) -> Result<(), String> {
if name.is_empty() {
return Err("Name cannot be empty".to_string());
}
ctx.db.user().insert(User { id: 0, name, email });
Ok(())
}C#:
csharp
[SpacetimeDB.Reducer]
public static void CreateUser(ReducerContext ctx, string name, string email)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("Name cannot be empty");
ctx.Db.User.Insert(new User { Id = 0, Name = name, Email = email });
}ReducerContext
ReducerContext
Every reducer receives a providing:
ReducerContext- Database: (Rust field, TS property) /
ctx.db(C# property)ctx.Db - Sender: (Rust method) /
ctx.sender()(C# property) /ctx.Sender(TS property)ctx.sender - Connection ID: (Rust method) /
ctx.connection_id()(C# property) /ctx.ConnectionId(TS property)ctx.connectionId - Timestamp: (Rust field, TS property) /
ctx.timestamp(C# property)ctx.Timestamp
每个Reducer都会接收一个,提供以下功能:
ReducerContext- 数据库实例:(Rust字段、TS属性)/
ctx.db(C#属性)ctx.Db - 调用者身份:(Rust方法)/
ctx.sender()(C#属性)/ctx.Sender(TS属性)ctx.sender - 连接ID:(Rust方法)/
ctx.connection_id()(C#属性)/ctx.ConnectionId(TS属性)ctx.connectionId - 时间戳:(Rust字段、TS属性)/
ctx.timestamp(C#属性)ctx.Timestamp
Event Tables (2.0)
事件表(2.0版本)
Event tables are the preferred way to broadcast reducer-specific data to clients.
rust
#[table(accessor = damage_event, public, event)]
pub struct DamageEvent {
pub target: Identity,
pub amount: u32,
}
#[reducer]
fn deal_damage(ctx: &ReducerContext, target: Identity, amount: u32) {
ctx.db.damage_event().insert(DamageEvent { target, amount });
}Clients subscribe to event tables and use callbacks. Event tables must be subscribed explicitly and are excluded from .
on_insertsubscribe_to_all_tables()事件表是向客户端广播Reducer特定数据的首选方式。
rust
#[table(accessor = damage_event, public, event)]
pub struct DamageEvent {
pub target: Identity,
pub amount: u32,
}
#[reducer]
fn deal_damage(ctx: &ReducerContext, target: Identity, amount: u32) {
ctx.db.damage_event().insert(DamageEvent { target, amount });
}客户端订阅事件表并使用回调。事件表必须显式订阅,且不会被包含。
on_insertsubscribe_to_all_tables()Subscriptions
订阅
Subscriptions replicate database rows to clients in real-time.
订阅功能可将数据库中的行实时同步至客户端。
How Subscriptions Work
订阅的工作原理
- Subscribe: Register SQL queries describing needed data
- Receive initial data: All matching rows are sent immediately
- Receive updates: Real-time updates when subscribed rows change
- React to changes: Use callbacks (,
onInsert,onDelete)onUpdate
- 订阅:注册描述所需数据的SQL查询
- 接收初始数据:所有匹配的行将立即发送至客户端
- 接收更新:当订阅的行发生变更时,实时推送更新
- 响应变更:使用回调函数(、
onInsert、onDelete)onUpdate
Subscription Best Practices
订阅最佳实践
- Group subscriptions by lifetime: Keep always-needed data separate from temporary subscriptions
- Subscribe before unsubscribing: When updating subscriptions, subscribe to new data first
- Avoid overlapping queries: Distinct queries returning overlapping data cause redundant processing
- Use indexes: Queries on indexed columns are efficient; full table scans are expensive
- 按生命周期分组订阅:将始终需要的数据与临时订阅的数据分开
- 先订阅再取消旧订阅:更新订阅时,先订阅新数据,再取消旧订阅
- 避免重叠查询:返回重叠数据的不同查询会导致冗余处理
- 使用索引:针对带索引列的查询效率更高;全表扫描开销较大
Modules
模块
Modules are WebAssembly bundles containing application logic that runs inside the database.
模块是包含应用逻辑的WebAssembly包,运行在数据库内部。
Module Components
模块组件
- Tables: Define the data schema
- Reducers: Define callable functions that modify state
- Views: Define read-only computed queries
- Event Tables: Broadcast reducer-specific data to clients (2.0)
- Procedures: (Beta) Functions that can have side effects (HTTP requests)
- 表:定义数据Schema
- Reducer:定义可调用的状态变更函数
- 视图:定义只读的计算查询
- 事件表:向客户端广播Reducer特定数据(2.0版本)
- 过程:(测试版)可产生副作用的函数(如HTTP请求)
Module Languages
模块支持的语言
Server-side modules can be written in: Rust, C#, TypeScript (beta)
服务器端模块可使用以下语言编写:Rust、C#、TypeScript(测试版)
Module Lifecycle
模块生命周期
- Write: Define tables and reducers in your chosen language
- Compile: Build to WebAssembly using the SpacetimeDB CLI
- Publish: Upload to a SpacetimeDB host with
spacetime publish - Hot-swap: Republish to update code without disconnecting clients
- 编写:使用你选择的语言定义表和Reducer
- 编译:使用SpacetimeDB CLI编译为WebAssembly
- 发布:通过命令上传至SpacetimeDB主机
spacetime publish - 热替换:重新发布即可更新代码,无需断开客户端连接
Identity
身份认证
Identity is SpacetimeDB's authentication system based on OpenID Connect (OIDC).
- Identity: A long-lived, globally unique identifier for a user.
- ConnectionId: Identifies a specific client connection.
rust
#[spacetimedb::reducer]
pub fn do_something(ctx: &ReducerContext) {
let caller_identity = ctx.sender(); // Who is calling?
// NEVER trust identity passed as a reducer argument
}身份认证是SpacetimeDB基于OpenID Connect(OIDC)的认证系统。
- Identity:用户的长期全局唯一标识符。
- ConnectionId:标识特定的客户端连接。
rust
#[spacetimedb::reducer]
pub fn do_something(ctx: &ReducerContext) {
let caller_identity = ctx.sender(); // 获取调用者身份
// 永远不要信任作为Reducer参数传递的身份信息
}Authentication Providers
认证提供商
SpacetimeDB works with many OIDC providers, including SpacetimeAuth (built-in), Auth0, Clerk, Keycloak, Google, and GitHub.
SpacetimeDB支持多种OIDC提供商,包括内置的SpacetimeAuth、Auth0、Clerk、Keycloak、Google以及GitHub。
When to Use SpacetimeDB
何时使用SpacetimeDB
Ideal Use Cases
理想适用场景
- Real-time games: MMOs, multiplayer games, turn-based games
- Collaborative applications: Document editing, whiteboards, design tools
- Chat and messaging: Real-time communication with presence
- Live dashboards: Streaming analytics and monitoring
- 实时游戏:大型多人在线游戏(MMO)、多人协作游戏、回合制游戏
- 协作类应用:文档编辑、白板、设计工具
- 聊天与消息:带在线状态的实时通讯
- 实时仪表盘:流分析与监控
Key Decision Factors
核心决策因素
Choose SpacetimeDB when you need:
- Sub-10ms latency for reads and writes
- Automatic real-time synchronization
- Transactional guarantees for all operations
- Simplified architecture (no separate cache, queue, or server)
当你需要以下特性时,选择SpacetimeDB:
- 读写延迟低于10毫秒
- 自动实时同步
- 所有操作的事务性保障
- 简化的架构(无需单独的缓存、队列或服务器)
Less Suitable For
不太适用的场景
- Batch analytics: Optimized for OLTP, not OLAP
- Large blob storage: Better suited for structured relational data
- Stateless APIs: Traditional REST APIs do not need real-time sync
- 批量分析:针对OLTP优化,而非OLAP
- 大对象存储:更适合结构化的关系型数据
- 无状态API:传统REST API无需实时同步
Common Patterns
常见模式
Authentication check in reducer:
rust
#[spacetimedb::reducer]
fn admin_action(ctx: &ReducerContext) -> Result<(), String> {
let admin = ctx.db.admin().identity().find(&ctx.sender())
.ok_or("Not an admin")?;
Ok(())
}Scheduled reducer:
rust
#[spacetimedb::table(accessor = reminder, scheduled(send_reminder))]
pub struct Reminder {
#[primary_key]
#[auto_inc]
id: u64,
scheduled_at: ScheduleAt,
message: String,
}
#[spacetimedb::reducer]
fn send_reminder(ctx: &ReducerContext, reminder: Reminder) {
log::info!("Reminder: {}", reminder.message);
}Reducer中的身份认证检查:
rust
#[spacetimedb::reducer]
fn admin_action(ctx: &ReducerContext) -> Result<(), String> {
let admin = ctx.db.admin().identity().find(&ctx.sender())
.ok_or("Not an admin")?;
Ok(())
}定时Reducer:
rust
#[spacetimedb::table(accessor = reminder, scheduled(send_reminder))]
pub struct Reminder {
#[primary_key]
#[auto_inc]
id: u64,
scheduled_at: ScheduleAt,
message: String,
}
#[spacetimedb::reducer]
fn send_reminder(ctx: &ReducerContext, reminder: Reminder) {
log::info!("Reminder: {}", reminder.message);
}Editing Behavior
编辑规范
When modifying SpacetimeDB code:
- Make the smallest change necessary
- Do NOT touch unrelated files, configs, or dependencies
- Do NOT invent new SpacetimeDB APIs — use only what exists in docs or this repo
修改SpacetimeDB代码时:
- 仅做必要的最小变更
- 请勿修改无关文件、配置或依赖
- 请勿自创SpacetimeDB API —— 仅使用文档或本仓库中已有的API