agentic_huge_data_base / wiki
页面 Onyx · 9.2 多租户架构·DeepWiki 中文全文译文

9.2 · 多租户架构(Multi-Tenant Architecture)

企业连接器与统一搜索 · 聚焦本章的模块关系、源码依据与实现要点。

项目Onyx 章节9.2 状态全文译文 模块界面与交互、文档对象与元数据、系统架构、安装与启动
源码线索
  • CONTRIBUTING.md
  • README.md
  • README.zh-CN.md
  • backend/alembic/README.md
  • backend/alembic/versions/6a804aeb4830_duplicated_no_harm_user_file_migration.py
  • backend/alembic/versions/9aadf32dfeb4_add_user_files.py
  • backend/alembic_tenants/versions/3b45e0018bf1_add_new_available_tenant_table.py
  • backend/alembic_tenants/versions/ac842f85f932_new_column_user_tenant_mapping.py
  • backend/ee/onyx/background/celery/tasks/tenant_provisioning/tasks.py
  • backend/ee/onyx/db/license.py
模块标签
  • 界面与交互
  • 文档对象与元数据
  • 系统架构
  • 安装与启动
  • 测试、发布与运维

章节正文

多租户架构

多租户架构

相关源文件

本章引用的主要源码文件:

  • CONTRIBUTING.md
  • README.md
  • README.zh-CN.md
  • backend/alembic/README.md
  • backend/alembic/versions/6a804aeb4830_duplicated_no_harm_user_file_migration.py
  • backend/alembic/versions/9aadf32dfeb4_add_user_files.py
  • backend/alembic_tenants/versions/3b45e0018bf1_add_new_available_tenant_table.py
  • backend/alembic_tenants/versions/ac842f85f932_new_column_user_tenant_mapping.py
  • backend/ee/onyx/background/celery/tasks/tenant_provisioning/tasks.py
  • backend/ee/onyx/db/license.py
  • backend/ee/onyx/server/middleware/license_enforcement.py
  • backend/ee/onyx/server/scim/auth.py
  • backend/ee/onyx/server/settings/api.py
  • backend/ee/onyx/server/tenants/admin_api.py
  • backend/ee/onyx/server/tenants/schema_management.py
  • backend/ee/onyx/server/tenants/user_mapping.py
  • backend/onyx/connectors/README.md
  • backend/onyx/db/tag.py
  • backend/onyx/server/features/build/db/sandbox.py
  • backend/tests/external_dependency_unit/db/test_tag_race_condition.py
  • backend/tests/integration/common_utils/managers/scim_client.py
  • backend/tests/integration/common_utils/managers/scim_token.py
  • backend/tests/integration/multitenant_tests/tenants/test_tenant_provisioning_rollback.py
  • backend/tests/integration/tests/indexing/file_connector/test_files/.onyx_metadata.json
  • backend/tests/integration/tests/scim/test_scim_tokens.py
  • backend/tests/integration/tests/scim/test_scim_users.py
  • backend/tests/unit/ee/onyx/db/test_license.py
  • backend/tests/unit/ee/onyx/server/middleware/test_license_enforcement.py
  • backend/tests/unit/ee/onyx/server/settings/test_license_enforcement_settings.py
  • backend/tests/unit/ee/onyx/server/tenants/test_schema_management.py
  • backend/tests/unit/onyx/background/celery/tasks/tenant_provisioning/__init__.py
  • backend/tests/unit/onyx/background/celery/tasks/tenant_provisioning/test_check_available_tenants.py
  • backend/tests/unit/onyx/server/scim/test_auth.py
  • deployment/README.md
  • deployment/docker_compose/README.md
  • web/src/components/errorPages/AccessRestrictedPage.tsx
  • web/src/components/errorPages/CloudErrorPage.tsx
  • web/src/components/errorPages/ErrorPage.tsx
  • web/src/components/errorPages/ErrorPageLayout.tsx

本文档描述了 Onyx 的多租户架构,该架构支持单个部署服务于多个隔离的组织(租户)。每个租户拥有独立的数据、用户和配置,同时共享相同的应用基础设施。

关于跨租户的认证方法,请参见 9.1 认证方法。关于租户内的用户角色和权限,请参见 9.3 用户角色和权限

概述

多租户架构由 MULTI_TENANT 环境标志控制 shared_configs/configs.py:61。启用后,Onyx 会为每个组织提供隔离的运行环境,具体方式如下:

  • PostgreSQL 模式级隔离:每个租户获得一个专用的数据库模式 ee/onyx/server/tenants/schema_management.py:17-19
  • Redis 键命名空间:通过 TenantRedis 类,所有 Redis 键都带有租户 ID 前缀 onyx/redis/redis_pool.py:42-146
  • 上下文变量:在请求生命周期内,使用 CURRENT_TENANT_ID_CONTEXTVAR 实现线程安全的租户跟踪 shared_configs/contextvars.py:64
  • 动态任务调度:自定义的 Celery Beat 调度器 DynamicTenantScheduler 通过将模板分发到所有活跃的租户 ID,生成每个租户的后台任务 onyx/background/celery/apps/beat.py:27-145

来源:shared_configs/configs.py:61, onyx/redis/redis_pool.py:42-146, shared_configs/contextvars.py:64, onyx/background/celery/apps/beat.py:27-145

租户隔离架构

租户隔离在 API、后台工作线程和数据存储层中实现。

代码到系统的映射:隔离组件

下图将逻辑隔离概念映射到代码库中使用的具体类和变量。

Onyx · 代码到系统的映射:隔离组件 · 图 1
Onyx · 代码到系统的映射:隔离组件 · 图 1

来源:shared_configs/contextvars.py:64, onyx/background/celery/apps/app_base.py:82-101, onyx/db/engine/sql_engine.py:43, onyx/redis/redis_pool.py:42-146, onyx/redis/redis_pool.py:169-170

PostgreSQL 模式级隔离

每个租户获得一个包含所有应用表的完整 PostgreSQL 模式。模式命名格式为 tenant_{uuid},其中 UUID 唯一标识该组织 shared_configs/configs.py:63

  • 共享模式public):包含与租户无关的表,例如 UserTenantMappingAvailableTenant ee/onyx/server/tenants/provisioning.py:39-42
  • 租户模式tenant_*):每个模式包含所有应用表的完整副本(用户、连接器、文档等)。

模式管理函数:

函数用途位置
create_schema_if_not_exists()在 Postgres 中创建新的租户模式ee/onyx/server/tenants/schema_management.py:17
run_alembic_migrations()schema_private 迁移应用到特定模式ee/onyx/server/tenants/schema_management.py:19
get_session_with_tenant()返回限定到特定租户 ID 的数据库会话onyx/db/engine/sql_engine.py:33
get_session_with_current_tenant()使用 CURRENT_TENANT_ID_CONTEXTVAR 限定会话范围onyx/db/engine/sql_engine.py:43

来源:ee/onyx/server/tenants/schema_management.py:17-19, shared_configs/configs.py:63, onyx/db/engine/sql_engine.py:33-43

Redis 命名空间隔离

TenantRedis 类确保共享同一 Redis 实例的租户之间不会发生键冲突。它会拦截标准的 Redis 命令,并在键前添加租户 ID 前缀 onyx/redis/redis_pool.py:42-45

实现细节:

  • 键前缀_prefixed 方法将键转换为 tenant_id:original_key 格式 onyx/redis/redis_pool.py:47-68
  • 方法包装:常见的 Redis 方法(例如 getsetlockdelete)会自动被包装以应用此前缀 onyx/redis/redis_pool.py:105-146
  • 扫描过滤scan_itersscan_iter 被重写,以按租户前缀过滤,并从返回的键中去除前缀,确保租户只能看到自己的数据 onyx/redis/redis_pool.py:81-103

来源:onyx/redis/redis_pool.py:42-146

租户预配逻辑

预配处理创建新组织环境的整个生命周期,从模式创建到初始数据填充。

预配流程:代码实体

下图将预配逻辑映射到 provisioning.py 中的具体函数和后台任务。

Onyx · 预配流程:代码实体 · 图 2
Onyx · 预配流程:代码实体 · 图 2

来源:ee/onyx/server/tenants/provisioning.py:71-133, ee/onyx/server/tenants/provisioning.py:145-164, ee/onyx/background/celery/tasks/tenant_provisioning/tasks.py:48-115

get_or_provision_tenant 逻辑

get_or_provision_tenant 函数 ee/onyx/server/tenants/provisioning.py:71-133 遵循以下优先级:

  1. 查找:检查用户邮箱是否已映射到 UserTenantMapping 中的某个租户 ee/onyx/server/tenants/provisioning.py:91
  2. 预预配池:尝试从 AvailableTenant 表中获取一个租户 ee/onyx/server/tenants/provisioning.py:99
  3. 按需创建:如果池为空,则调用 create_tenant() 从头构建一个新的环境 ee/onyx/server/tenants/provisioning.py:127
AvailableTenant 预预配

为了减少用户注册时的延迟,Onyx 维护了一个"可用"租户池。这些模式已经创建并迁移完成,但尚未分配给任何用户 ee/onyx/server/tenants/provisioning.py:39

  • 后台维护check_available_tenants Celery 任务会定期运行,以确保可用租户数量达到 TARGET_AVAILABLE_TENANTS 的要求 ee/onyx/background/celery/tasks/tenant_provisioning/tasks.py:79-82
  • 陈旧迁移_migrate_stale_pool_tenants 函数确保池中的租户始终处于最新的 Alembic "head" 状态,防止用户最终注册时出现模式不匹配 ee/onyx/background/celery/tasks/tenant_provisioning/tasks.py:127-160
  • 获取:当获取一个预预配的租户时,会立即执行 run_alembic_migrations,以确保在分配之前模式与当前应用版本保持一致 ee/onyx/server/tenants/provisioning.py:102-110

来源:ee/onyx/server/tenants/provisioning.py:98-110, ee/onyx/server/tenants/provisioning.py:39, ee/onyx/background/celery/tasks/tenant_provisioning/tasks.py:39-160

上下文管理

CURRENT_TENANT_ID_CONTEXTVAR

系统使用 contextvars 在异步任务和应用的不同层之间维护租户状态,而无需在每个函数签名中传递 tenant_id shared_configs/contextvars.py:64

  • API 中间件:从请求中提取租户 ID 并设置上下文变量 ee/onyx/server/middleware/tenant_tracking.py:24-26
  • Celery 工作线程TenantAwareTask 拦截任务执行,从任务参数中提取 tenant_id,并在任务逻辑运行之前设置上下文变量 onyx/background/celery/apps/app_base.py:82-101
  • 数据库会话get_session_with_current_tenant 从上下文变量中检索 ID,以配置 SQLAlchemy 会话 onyx/db/engine/sql_engine.py:43

来源:shared_configs/contextvars.py:64, onyx/background/celery/apps/app_base.py:82-101, onyx/db/engine/sql_engine.py:43

租户模式迁移

在多租户环境中,迁移采用两层方法:

  1. 公共迁移:针对 public 模式的标准 Alembic 迁移。
  2. 私有迁移:使用 schema_private 名称应用于每个租户模式的迁移 ee/onyx/server/tenants/schema_management.py:19

函数 run_alembic_migrations(tenant_id) ee/onyx/server/tenants/schema_management.py:19 以编程方式调用 Alembic,将特定租户模式升级到最新的 "head" 版本。

CLI 迁移管理

管理员可以通过 CLI 同时运行多个租户的迁移:

  • 全部升级alembic -x upgrade_all_tenants=true upgrade head backend/alembic/README.md:34
  • 范围过滤:使用 tenant_range_starttenant_range_end 按字母顺序批量迁移 backend/alembic/README.md:49

来源:ee/onyx/server/tenants/schema_management.py:19, ee/onyx/server/tenants/provisioning.py:102-109, backend/alembic/README.md:30-68