per-tenant-database

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Database per Tenant

按租户划分数据库

IMPORTANT: Before doing anything, you MUST read
BASE_SKILL.md
in this skill's directory. It contains essential guidance on debugging, error handling, state management, deployment, and project setup. Those rules and patterns apply to all RivetKit work. Everything below assumes you have already read and understood it.
重要提示:在进行任何操作之前,您必须阅读本技能目录下的
BASE_SKILL.md
文件。其中包含调试、错误处理、状态管理、部署和项目设置的关键指南。这些规则和模式适用于所有RivetKit相关工作。以下所有内容均假设您已阅读并理解该文件。

Working Examples

示例实现

If you need a reference implementation, read the raw working example code in these templates:
Patterns for database-per-tenant architectures with RivetKit. Instead of one shared database with a
tenant_id
column on every table, each tenant gets its own Rivet Actor, and that actor owns the tenant's entire dataset.
如果您需要参考实现,请查看以下模板中的完整示例代码:
基于RivetKit的按租户划分数据库架构模式。不再使用单共享数据库并在每张表中添加
tenant_id
列,而是为每个租户分配一个独立的Rivet Actor,由该Actor管理租户的整个数据集。

Starter Code

起始代码

Start with the working example on GitHub and adapt it. The example stores each tenant's dataset in JSON actor state and serves a React dashboard with live event updates.
TopicSummary
IsolationOne
companyDatabase
actor per tenant, keyed by company name. Switching tenants swaps the entire dataset.
StateJSON actor state holding
employees
and
projects
arrays plus timestamps. No SQLite, no queues, no scheduling.
RealtimeEvery write action mutates state, then broadcasts a typed event (
employeeAdded
,
projectAdded
) to all connected clients of that tenant.
AuthNone. The sign-in screen is cosmetic. Production guidance is in the security checklist.
您可以从GitHub上的示例项目开始进行适配:GitHub。该示例将每个租户的数据集存储在JSON Actor状态中,并提供一个支持实时事件更新的React仪表盘。
主题概述
隔离性每个租户对应一个
companyDatabase
Actor,以公司名称作为键。切换租户时会替换整个数据集。
状态使用JSON Actor状态存储
employees
projects
数组及时间戳。无需SQLite、队列或调度。
实时性每次写入操作都会修改状态,然后向该租户的所有连接客户端广播类型化事件(
employeeAdded
projectAdded
)。
认证无认证功能。示例中的登录界面仅作展示。生产环境的安全指导请查看安全检查清单

The Isolation Model

隔离模型

The actor key is the tenant id. The client connects with
useActor({ name: "companyDatabase", key: [companyName] })
and the actor reads
c.key[0]
in
createState
to seed that tenant's dataset. This gives you:
  • One actor per tenant:
    companyDatabase[tenantId]
    addresses exactly one actor instance. Two tenants can never share an actor.
  • One dataset per tenant: All reads and writes go through that actor's state, so there is no shared table with a
    tenant_id
    column to filter incorrectly. Cross-tenant leaks require constructing the wrong key, not forgetting a
    WHERE
    clause.
  • No key injection: Keys are arrays, not interpolated strings.
    key: [tenantId]
    cannot be escaped the way
    "tenant:" + tenantId
    string concatenation can. See Keys.
The example's test (tests/per-tenant-database.test.ts) proves the isolation: data written to
companyDatabase["Alpha Co"]
never appears in
companyDatabase["Beta Co"]
.
Actor的键即为租户ID。客户端通过
useActor({ name: "companyDatabase", key: [companyName] })
建立连接,Actor在
createState
中读取
c.key[0]
来初始化该租户的数据集。这一模型可实现:
  • 每个租户一个Actor
    companyDatabase[tenantId]
    指向唯一的Actor实例。两个租户永远不会共享同一个Actor。
  • 每个租户一个数据集:所有读写操作均通过该Actor的状态完成,因此不存在需要过滤
    tenant_id
    列的共享表。跨租户数据泄露仅可能因构造错误的键导致,而非遗忘
    WHERE
    子句。
  • 无键注入风险:键为数组类型,而非插值字符串。
    key: [tenantId]
    不会像
    "tenant:" + tenantId
    字符串拼接那样存在逃逸风险。详情请见
示例中的测试代码(tests/per-tenant-database.test.ts)验证了隔离性:写入
companyDatabase["Alpha Co"]
的数据永远不会出现在
companyDatabase["Beta Co"]
中。

Choosing a State Backend

选择状态后端

The example uses plain JSON actor state. The same key-equals-tenant model works with any actor state backend.
BackendUse WhenDocsWorking Code
JSON actor stateSmall datasets, simple reads, whole dataset fits comfortably in memory. What the example uses.StateGitHub
Actor SQLite (
rivetkit/db
)
Tables, indexes, SQL queries, larger-than-memory data, per-tenant relational schema.SQLiteGitHub
SQLite + DrizzleTyped schema, query builder, and generated migration files on top of actor SQLite.SQLite + DrizzleGitHub
With either SQLite option, every tenant gets its own embedded SQLite database, since the database is scoped to the actor and the actor is scoped to the tenant.
示例使用纯JSON Actor状态。同样的“键=租户”模型适用于任何Actor状态后端。
后端使用场景文档示例代码
JSON Actor状态小型数据集、简单读取操作、整个数据集可轻松放入内存。示例所使用的方案。状态GitHub
Actor SQLite (
rivetkit/db
)
需要表、索引、SQL查询、超内存数据、按租户划分的关系型架构。SQLiteGitHub
SQLite + Drizzle在Actor SQLite之上提供类型化架构、查询构建器和生成的迁移文件。SQLite + DrizzleGitHub
无论选择哪种SQLite方案,每个租户都会拥有独立的嵌入式SQLite数据库,因为数据库的作用域与Actor绑定,而Actor的作用域与租户绑定。

Migrations

迁移

The per-tenant example has no migrations because JSON state has no schema. When you adopt SQLite, migrations run per tenant database:
  • Raw SQL:
    db({ onMigrate })
    runs your migration SQL inside a SQLite savepoint before the actor serves traffic. If
    onMigrate
    throws, all migration SQL rolls back atomically and the actor does not start. See SQLite.
  • Drizzle:
    drizzle-kit
    generates migration files from your typed schema, and
    db({ schema, migrations })
    applies them when the actor wakes. See SQLite + Drizzle.
Because each tenant has its own database, migrations roll out per actor as each tenant's actor wakes, rather than as one large migration against a shared database.
按租户划分的示例中没有迁移流程,因为JSON状态无需 schema。当您采用SQLite时,迁移将在每个租户的数据库上运行:
  • 原生SQL
    db({ onMigrate })
    会在Actor开始处理请求前,在SQLite保存点内执行您的迁移SQL。如果
    onMigrate
    抛出异常,所有迁移SQL会自动回滚,Actor不会启动。详情请见SQLite
  • Drizzle
    drizzle-kit
    会从您的类型化架构生成迁移文件,
    db({ schema, migrations })
    会在Actor启动时应用这些迁移。详情请见SQLite + Drizzle
由于每个租户都有独立的数据库,迁移会在每个租户的Actor启动时逐个执行,而非针对共享数据库进行一次性大规模迁移。

Tenant Id Must Come From Auth

租户ID必须来自认证系统

The example's sign-in is cosmetic: the client picks any company string and that string becomes the actor key, so any visitor can read and write any tenant's data. Do not ship this. As a required production extension (not implemented by the example):
  • Derive the tenant id from a verified credential, such as a JWT claim, never from user input.
  • Validate the credential against
    c.key
    in
    onBeforeConnect
    (pass/fail) or
    createConnState
    (store the verified user on connection state). See Authentication and Connections.
  • Add per-action permission checks on top of connection-level auth. See Access Control.
示例中的登录功能仅作展示:客户端可随意输入公司名称作为Actor键,因此任何访客都能读写任意租户的数据。请勿直接部署该示例。生产环境必须添加以下扩展(示例未实现):
  • 从经过验证的凭证(如JWT声明)中获取租户ID,绝不要使用用户输入。
  • onBeforeConnect
    (通过/拒绝)或
    createConnState
    (将已验证用户存储到连接状态)中验证凭证与
    c.key
    是否匹配。详情请见认证连接
  • 在连接级认证之上添加操作级权限检查。详情请见访问控制

Actors

Actors

  • Key:
    companyDatabase[companyName]
    (single-element array key;
    c.key[0]
    is the company name)
  • Responsibility: One actor per tenant. Holds that company's employees and projects in persistent state, serves reads and writes via actions, and broadcasts mutations to connected clients.
  • Actions
    • addEmployee
    • listEmployees
    • addProject
    • listProjects
    • getStats
  • Queues
    • None
  • Events
    • employeeAdded
    • projectAdded
  • State
    • JSON
    • company_name
    • employees
    • projects
    • created_at
    • updated_at
Every write action follows the same mutate-then-broadcast shape: push the record into
c.state
, bump
updated_at
, broadcast the typed event, return the record. See Actions and Events.
  • companyDatabase[companyName]
    (单元素数组键;
    c.key[0]
    为公司名称)
  • 职责:每个租户对应一个Actor。在持久化状态中存储公司的员工和项目数据,通过操作提供读写服务,并向连接客户端广播变更事件。
  • 操作
    • addEmployee
    • listEmployees
    • addProject
    • listProjects
    • getStats
  • 队列
  • 事件
    • employeeAdded
    • projectAdded
  • 状态
    • JSON格式
    • company_name
    • employees
    • projects
    • created_at
    • updated_at
每个写入操作都遵循“修改后广播”的流程:将记录添加到
c.state
,更新
updated_at
,广播类型化事件,返回记录。详情请见操作事件

Lifecycle

生命周期

mermaid
sequenceDiagram
	participant A as Tenant A client
	participant DA as companyDatabase A
	participant B as Tenant B client
	participant DB as companyDatabase B

	Note over A: authenticate and derive tenant id
	A->>DA: connect with key [tenantA]
	Note over DA: createState seeds company_name, employees, projects
	A->>DA: listEmployees() + listProjects() + getStats()
	A->>DA: addEmployee(name, role)
	DA-->>A: employeeAdded event
	B->>DB: connect with key [tenantB]
	Note over DB: separate actor, separate dataset
	B->>DB: listEmployees()
	DB-->>B: tenant B data only
In the example, the "authenticate" step is a free-text company picker. The rest of the flow matches the diagram:
createState
seeds the dataset on first creation, the dashboard loads with
listEmployees
,
listProjects
, and
getStats
, and every connected client of the same tenant receives
employeeAdded
and
projectAdded
events.
mermaid
sequenceDiagram
	participant A as Tenant A client
	participant DA as companyDatabase A
	participant B as Tenant B client
	participant DB as companyDatabase B

	Note over A: authenticate and derive tenant id
	A->>DA: connect with key [tenantA]
	Note over DA: createState seeds company_name, employees, projects
	A->>DA: listEmployees() + listProjects() + getStats()
	A->>DA: addEmployee(name, role)
	DA-->>A: employeeAdded event
	B->>DB: connect with key [tenantB]
	Note over DB: separate actor, separate dataset
	B->>DB: listEmployees()
	DB-->>B: tenant B data only
在示例中,“认证”步骤是一个自由输入的公司选择器。其余流程与图中一致:
createState
会在首次创建时初始化数据集,仪表盘通过
listEmployees
listProjects
getStats
加载数据,同一租户的所有连接客户端都会收到
employeeAdded
projectAdded
事件。

Security Checklist

安全检查清单

The example ships with none of these. Apply all of them before production.
  • Tenant identity: Derive the tenant id from a verified JWT claim, never from a client-supplied string.
  • Connection validation: In
    onBeforeConnect
    or
    createConnState
    , verify the credential's tenant claim matches
    c.key
    and reject mismatches.
  • Per-action authorization: Check the caller's role before mutating actions (
    addEmployee
    ,
    addProject
    ), not just at connect time. See Access Control.
  • Input validation: Clamp name and role lengths and validate enums. The example only trims input and substitutes fallback defaults.
  • Key construction: Always pass the tenant id as an array element (
    key: [tenantId]
    ). Never interpolate tenant ids into key strings, and never build keys from one tenant's input to address another tenant's actor.
  • Growth limits: As a recommended extension, cap or paginate the
    employees
    and
    projects
    arrays. The example lets them grow unboundedly in JSON state; move to SQLite when the dataset outgrows memory.
示例未实现以下任何一项。部署到生产环境前必须全部应用:
  • 租户身份:从经过验证的JWT声明中获取租户ID,绝不要使用客户端提供的字符串。
  • 连接验证:在
    onBeforeConnect
    createConnState
    中验证凭证的租户声明与
    c.key
    是否匹配,拒绝不匹配的连接。
  • 操作级授权:在执行修改操作(
    addEmployee
    addProject
    )前检查调用者的角色,而非仅在连接时检查。详情请见访问控制
  • 输入验证:限制名称和角色的长度,验证枚举值。示例仅对输入进行修剪并使用默认值。
  • 键构造:始终将租户ID作为数组元素传递(
    key: [tenantId]
    )。绝不要将租户ID插值到键字符串中,也不要根据一个租户的输入构建键来访问另一个租户的Actor。
  • 增长限制:建议添加扩展,对
    employees
    projects
    数组设置上限或分页。示例允许它们在JSON状态中无限增长;当数据集超出内存时,请迁移到SQLite

Reference Map

参考映射

Actors

Actors

  • Access Control
  • Actions
  • Actor Keys
  • Actor Scheduling
  • Actor Statuses
  • AI and User-Generated Rivet Actors
  • Authentication
  • Communicating Between Actors
  • Connections
  • Custom Inspector Tabs
  • Debugging
  • Design Patterns
  • Destroying Actors
  • Errors
  • Fetch and WebSocket Handler
  • Helper Types
  • Icons & Names
  • Input Parameters
  • Lifecycle
  • Limits
  • Low-Level HTTP Request Handler
  • Low-Level KV Storage
  • Low-Level WebSocket Handler
  • Metadata
  • Next.js Quickstart
  • Node.js & Bun Quickstart
  • Queues & Run Loops
  • React Quickstart
  • Realtime
  • Rust Quickstart (Preview)
  • Sandbox Actor
  • Scaling & Concurrency
  • Sharing and Joining State
  • SQLite
  • SQLite + Drizzle
  • State & Storage
  • Testing
  • Troubleshooting
  • Types
  • Vanilla HTTP API
  • Versions & Upgrades
  • Workflows
  • 访问控制
  • 操作
  • Actor键
  • Actor调度
  • Actor状态
  • AI与用户生成的Rivet Actors
  • 认证
  • Actor间通信
  • 连接
  • 自定义检查器标签页
  • 调试
  • 设计模式
  • 销毁Actors
  • 错误处理
  • Fetch与WebSocket处理器
  • 辅助类型
  • 图标与名称
  • 输入参数
  • 生命周期
  • 限制
  • 底层HTTP请求处理器
  • 底层KV存储
  • 底层WebSocket处理器
  • 元数据
  • Next.js快速入门
  • Node.js & Bun快速入门
  • 队列与运行循环
  • React快速入门
  • 实时功能
  • Rust快速入门(预览版)
  • 沙箱Actor
  • 扩展与并发
  • 状态共享与合并
  • SQLite
  • SQLite + Drizzle
  • 状态与存储
  • 测试
  • 故障排除
  • 类型
  • 原生HTTP API
  • 版本与升级
  • 工作流

Agent Os

Agent Os

  • Agent-to-Agent Communication
  • agentOS vs Sandbox
  • Authentication
  • Benchmarks
  • Configuration
  • Core Package
  • Cron Jobs
  • Deployment
  • Embedded LLM Gateway
  • Events
  • Filesystem
  • Limitations
  • LLM Credentials
  • Multiplayer
  • Networking & Previews
  • Overview
  • Permissions
  • Persistence & Sleep
  • Pi
  • Processes & Shell
  • Queues
  • Quickstart
  • Sandbox Mounting
  • Security & Auth
  • Security Model
  • Sessions
  • Software
  • SQLite
  • System Prompt
  • Tools
  • Webhooks
  • Workflow Automation
  • Agent间通信
  • agentOS vs 沙箱
  • 认证
  • 基准测试
  • 配置
  • 核心包
  • 定时任务
  • 部署
  • 嵌入式LLM网关
  • 事件
  • 文件系统
  • 限制
  • LLM凭证
  • 多人协作
  • 网络与预览
  • 概述
  • 权限
  • 持久化与休眠
  • Pi
  • 进程与Shell
  • 队列
  • 快速入门
  • 沙箱挂载
  • 安全与认证
  • 安全模型
  • 会话
  • 软件
  • SQLite
  • 系统提示词
  • 工具
  • Webhooks
  • 工作流自动化

Clients

客户端

  • Node.js & Bun
  • React
  • Swift
  • SwiftUI
  • Node.js & Bun
  • React
  • Swift
  • SwiftUI

Connect

连接部署

  • Deploy To Amazon Web Services Lambda
  • Deploying to AWS ECS
  • Deploying to Cloudflare Workers
  • Deploying to Freestyle
  • Deploying to Google Cloud Run
  • Deploying to Hetzner
  • Deploying to Kubernetes
  • Deploying to Railway
  • Deploying to Rivet Compute
  • Deploying to Supabase Functions
  • Deploying to Vercel
  • Deploying to VMs & Bare Metal
  • 部署到Amazon Web Services Lambda
  • 部署到AWS ECS
  • 部署到Cloudflare Workers
  • 部署到Freestyle
  • 部署到Google Cloud Run
  • 部署到Hetzner
  • 部署到Kubernetes
  • 部署到Railway
  • 部署到Rivet Compute
  • 部署到Supabase Functions
  • 部署到Vercel
  • 部署到虚拟机与裸金属服务器

Cookbook

实战指南

  • AI Agent
  • AI Agent Workspaces
  • Chat Room
  • Collaborative Text Editor
  • Cron Jobs and Scheduled Tasks
  • Database per Tenant
  • Deploying Rivet in a VPC or Air-Gapped Network
  • Live Cursors and Presence
  • Multiplayer Game
  • AI Agent
  • AI Agent工作区
  • 聊天室
  • 协作文本编辑器
  • 定时任务与计划任务
  • 按租户划分数据库
  • 在VPC或隔离网络中部署Rivet
  • 实时光标与在线状态
  • 多人游戏

General

通用

  • Actor Configuration
  • Architecture
  • Cross-Origin Resource Sharing
  • Documentation for LLMs & AI
  • Edge Networking
  • Endpoints
  • Environment Variables
  • HTTP Server
  • Logging
  • Pool Configuration
  • Production Checklist
  • Registry Configuration
  • Runtime Modes
  • Actor配置
  • 架构
  • 跨域资源共享
  • 面向LLM与AI的文档
  • 边缘网络
  • 端点
  • 环境变量
  • HTTP服务器
  • 日志
  • 池配置
  • 生产环境检查清单
  • 注册表配置
  • 运行时模式

Self Hosting

自托管

  • Configuration
  • Docker Compose
  • Docker Container
  • File System
  • FoundationDB (Enterprise)
  • Installing Rivet Engine
  • Kubernetes
  • Multi-Region
  • PostgreSQL
  • Production Checklist
  • Railway Deployment
  • Render Deployment
  • TLS & Certificates
  • 配置
  • Docker Compose
  • Docker容器
  • 文件系统
  • FoundationDB(企业版)
  • 安装Rivet引擎
  • Kubernetes
  • 多区域部署
  • PostgreSQL
  • 生产环境检查清单
  • Railway部署
  • Render部署
  • TLS与证书