per-tenant-database
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDatabase per Tenant
按租户划分数据库
IMPORTANT: Before doing anything, you MUST read 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相关工作。以下所有内容均假设您已阅读并理解该文件。
BASE_SKILL.mdWorking 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 column on every table, each tenant gets its own Rivet Actor, and that actor owns the tenant's entire dataset.
tenant_id如果您需要参考实现,请查看以下模板中的完整示例代码:
基于RivetKit的按租户划分数据库架构模式。不再使用单共享数据库并在每张表中添加列,而是为每个租户分配一个独立的Rivet Actor,由该Actor管理租户的整个数据集。
tenant_idStarter 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.
| Topic | Summary |
|---|---|
| Isolation | One |
| State | JSON actor state holding |
| Realtime | Every write action mutates state, then broadcasts a typed event ( |
| Auth | None. The sign-in screen is cosmetic. Production guidance is in the security checklist. |
您可以从GitHub上的示例项目开始进行适配:GitHub。该示例将每个租户的数据集存储在JSON Actor状态中,并提供一个支持实时事件更新的React仪表盘。
| 主题 | 概述 |
|---|---|
| 隔离性 | 每个租户对应一个 |
| 状态 | 使用JSON Actor状态存储 |
| 实时性 | 每次写入操作都会修改状态,然后向该租户的所有连接客户端广播类型化事件( |
| 认证 | 无认证功能。示例中的登录界面仅作展示。生产环境的安全指导请查看安全检查清单。 |
The Isolation Model
隔离模型
The actor key is the tenant id. The client connects with and the actor reads in to seed that tenant's dataset. This gives you:
useActor({ name: "companyDatabase", key: [companyName] })c.key[0]createState- One actor per tenant: addresses exactly one actor instance. Two tenants can never share an actor.
companyDatabase[tenantId] - One dataset per tenant: All reads and writes go through that actor's state, so there is no shared table with a column to filter incorrectly. Cross-tenant leaks require constructing the wrong key, not forgetting a
tenant_idclause.WHERE - No key injection: Keys are arrays, not interpolated strings. cannot be escaped the way
key: [tenantId]string concatenation can. See Keys."tenant:" + tenantId
The example's test (tests/per-tenant-database.test.ts) proves the isolation: data written to never appears in .
companyDatabase["Alpha Co"]companyDatabase["Beta Co"]Actor的键即为租户ID。客户端通过建立连接,Actor在中读取来初始化该租户的数据集。这一模型可实现:
useActor({ name: "companyDatabase", key: [companyName] })createStatec.key[0]- 每个租户一个Actor:指向唯一的Actor实例。两个租户永远不会共享同一个Actor。
companyDatabase[tenantId] - 每个租户一个数据集:所有读写操作均通过该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.
| Backend | Use When | Docs | Working Code |
|---|---|---|---|
| JSON actor state | Small datasets, simple reads, whole dataset fits comfortably in memory. What the example uses. | State | GitHub |
Actor SQLite ( | Tables, indexes, SQL queries, larger-than-memory data, per-tenant relational schema. | SQLite | GitHub |
| SQLite + Drizzle | Typed schema, query builder, and generated migration files on top of actor SQLite. | SQLite + Drizzle | GitHub |
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 ( | 需要表、索引、SQL查询、超内存数据、按租户划分的关系型架构。 | SQLite | GitHub |
| SQLite + Drizzle | 在Actor SQLite之上提供类型化架构、查询构建器和生成的迁移文件。 | SQLite + Drizzle | GitHub |
无论选择哪种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: runs your migration SQL inside a SQLite savepoint before the actor serves traffic. If
db({ onMigrate })throws, all migration SQL rolls back atomically and the actor does not start. See SQLite.onMigrate - Drizzle: generates migration files from your typed schema, and
drizzle-kitapplies them when the actor wakes. See SQLite + Drizzle.db({ schema, migrations })
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:会在Actor开始处理请求前,在SQLite保存点内执行您的迁移SQL。如果
db({ onMigrate })抛出异常,所有迁移SQL会自动回滚,Actor不会启动。详情请见SQLite。onMigrate - Drizzle:会从您的类型化架构生成迁移文件,
drizzle-kit会在Actor启动时应用这些迁移。详情请见SQLite + Drizzle。db({ schema, migrations })
由于每个租户都有独立的数据库,迁移会在每个租户的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 in
c.key(pass/fail) oronBeforeConnect(store the verified user on connection state). See Authentication and Connections.createConnState - Add per-action permission checks on top of connection-level auth. See Access Control.
Actors
Actors
- Key: (single-element array key;
companyDatabase[companyName]is the company name)c.key[0] - 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
addEmployeelistEmployeesaddProjectlistProjectsgetStats
- Queues
- None
- Events
employeeAddedprojectAdded
- State
- JSON
company_nameemployeesprojectscreated_atupdated_at
- 键:(单元素数组键;
companyDatabase[companyName]为公司名称)c.key[0] - 职责:每个租户对应一个Actor。在持久化状态中存储公司的员工和项目数据,通过操作提供读写服务,并向连接客户端广播变更事件。
- 操作
addEmployeelistEmployeesaddProjectlistProjectsgetStats
- 队列
- 无
- 事件
employeeAddedprojectAdded
- 状态
- JSON格式
company_nameemployeesprojectscreated_atupdated_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 onlyIn the example, the "authenticate" step is a free-text company picker. The rest of the flow matches the diagram: seeds the dataset on first creation, the dashboard loads with , , and , and every connected client of the same tenant receives and events.
createStatelistEmployeeslistProjectsgetStatsemployeeAddedprojectAddedmermaid
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在示例中,“认证”步骤是一个自由输入的公司选择器。其余流程与图中一致:会在首次创建时初始化数据集,仪表盘通过、和加载数据,同一租户的所有连接客户端都会收到和事件。
createStatelistEmployeeslistProjectsgetStatsemployeeAddedprojectAddedSecurity 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 or
onBeforeConnect, verify the credential's tenant claim matchescreateConnStateand reject mismatches.c.key - Per-action authorization: Check the caller's role before mutating actions (,
addEmployee), not just at connect time. See Access Control.addProject - 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 (). Never interpolate tenant ids into key strings, and never build keys from one tenant's input to address another tenant's actor.
key: [tenantId] - Growth limits: As a recommended extension, cap or paginate the and
employeesarrays. The example lets them grow unboundedly in JSON state; move to SQLite when the dataset outgrows memory.projects
示例未实现以下任何一项。部署到生产环境前必须全部应用:
- 租户身份:从经过验证的JWT声明中获取租户ID,绝不要使用客户端提供的字符串。
- 连接验证:在或
onBeforeConnect中验证凭证的租户声明与createConnState是否匹配,拒绝不匹配的连接。c.key - 操作级授权:在执行修改操作(、
addEmployee)前检查调用者的角色,而非仅在连接时检查。详情请见访问控制。addProject - 输入验证:限制名称和角色的长度,验证枚举值。示例仅对输入进行修剪并使用默认值。
- 键构造:始终将租户ID作为数组元素传递()。绝不要将租户ID插值到键字符串中,也不要根据一个租户的输入构建键来访问另一个租户的Actor。
key: [tenantId] - 增长限制:建议添加扩展,对和
employees数组设置上限或分页。示例允许它们在JSON状态中无限增长;当数据集超出内存时,请迁移到SQLite。projects
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与证书