返回博客

多租户架构:从租户到世界的 5 级隔离模型

TL;DR

Coomia发布于 2025年6月28日24 分钟阅读
分享本文Twitter / X

多租户架构:从租户到世界的 5 级隔离模型

系列:S2 架构全景 · 第 5 篇 | 难度:中级 | 阅读时间:18 分钟

TL;DR

  • coomia-dip 采用 Tenant → Org → Space → Project → World 的 5 级隔离模型,每一级都有明确的资源边界和权限作用域。相比扁平的"租户-用户"两级模型,5 级隔离更好地映射了企业组织结构的现实复杂度。
  • 数据隔离在 Project 级通过 Doris Database-per-Project 实现物理隔离,World 级通过 Nessie Branch 实现逻辑隔离。两者结合确保了跨租户的绝对隔离和同项目内的灵活版本管理。
  • 自动供应(Auto-Provisioning)机制让新层级创建时自动初始化所有必要资源——Doris 数据库、Nessie 分支、Kafka 主题、MinIO 桶——从"申请工单等 3 天"变为"API 调用等 3 秒"。

#1. 引言:为什么需要 5 级

大多数 SaaS 平台的多租户模型只有两级:租户(Tenant)用户(User)。这在简单场景下足够了,但当你的客户是大型企业集团时,问题就出现了。

考虑一个真实场景:

Code
华能集团(租户)
├── 华能国际(子公司 → Org)
│   ├── 华东分公司(业务单元 → Space)
│   │   ├── 智能运维项目(项目 → Project)
│   │   │   ├── 生产环境(世界 → World)
│   │   │   └── 测试环境(世界 → World)
│   │   └── 碳排放监控项目(项目 → Project)
│   └── 华南分公司(业务单元 → Space)
├── 华能新能源(子公司 → Org)
│   └── 风电事业部(业务单元 → Space)
│       └── 风机健康监测项目(项目 → Project)
└── 华能核电(子公司 → Org)

如果用两级模型硬套:

  • 华东分公司的运维工程师能看到华南分公司的数据吗?不应该。
  • 智能运维项目的测试环境数据会影响生产环境吗?绝对不能。
  • 华能国际的管理者能看到华能新能源的风电数据吗?取决于集团策略。

两级模型无法表达这些需求。 你要么写大量的自定义权限逻辑,要么接受安全漏洞。coomia-dip 的 5 级隔离模型正是为了解决这个问题。

#2. 5 级隔离模型详解

#2.1 层级定义

Code
Level 1: Tenant (租户)
  │  最高隔离边界。不同租户之间完全不可见。
  │  典型映射:集团公司、独立企业
  │
  ├─ Level 2: Org (组织)
  │  │  法律实体或独立核算单位。同一租户下的不同组织
  │  │  默认互相不可见,需要显式授权。
  │  │  典型映射:子公司、事业部
  │  │
  │  ├─ Level 3: Space (空间)
  │  │  │  业务协作单元。同一组织下的不同团队或部门。
  │  │  │  默认互相可见(同组织),权限可细化。
  │  │  │  典型映射:部门、区域分公司、业务线
  │  │  │
  │  │  ├─ Level 4: Project (项目)
  │  │  │  │  独立的数据域。拥有独立的 Doris Database。
  │  │  │  │  典型映射:业务项目、应用系统
  │  │  │  │
  │  │  │  ├─ Level 5: World (世界)
  │  │  │  │  项目的版本化实例。Nessie Branch 隔离。
  │  │  │  │  典型映射:生产环境、测试环境、模拟沙箱

#2.2 每一级的隔离机制

级别隔离类型技术实现跨级可见性
Tenant绝对隔离独立的身份域 + 独立的加密密钥永远不可跨租户
Org强隔离独立的权限树 + 独立的配额需租户管理员显式授权
Space逻辑隔离RBAC 角色域 + 资源标签同组织内默认可发现
Project数据隔离Doris Database-per-Project需 Space 级授权
World版本隔离Nessie Branch-per-World同 Project 内可切换

#2.3 WorldContext:贯穿 5 级的上下文对象

在 coomia-dip 中,每一个 gRPC 调用都必须携带 WorldContext,它是 5 级隔离模型的运行时表达:

PROTOBUF
message WorldContext {
  string tenant_id = 1;    // Level 1: 租户
  string org_id = 2;       // Level 2: 组织
  string space_id = 3;     // Level 3: 空间
  string project_id = 4;   // Level 4: 项目
  string world_id = 5;     // Level 5: 世界

  // 认证信息
  string principal_id = 6;        // 当前用户
  repeated string roles = 7;      // 用户角色列表
  map<string, string> claims = 8; // 额外声明 (ABAC 用)
}

这个对象从 API Gateway 开始构建,在整个请求链路中传递,确保每个组件都知道当前操作在哪个隔离级别中执行

#3. 数据隔离的技术实现

#3.1 Level 4 隔离:Doris Database-per-Project

每个 Project 拥有独立的 Doris Database。这不是在同一个 Database 中用 project_id 列来区分数据——那是逻辑隔离;而是真正的物理隔离

Code
Doris 实例 (共享)
├── database: proj_huaneng_ops_001
│   ├── entity_common    (对象实例)
│   ├── entity_edge      (关系实例)
│   ├── entity_event     (事件记录)
│   └── entity_metric    (指标数据)
│
├── database: proj_huaneng_carbon_002
│   ├── entity_common
│   ├── entity_edge
│   ├── entity_event
│   └── entity_metric
│
├── database: proj_wind_health_003
│   ├── entity_common
│   ├── entity_edge
│   ├── entity_event
│   └── entity_metric

为什么选择 Database-per-Project 而不是 Database-per-Tenant?

Code
方案对比:

Database-per-Tenant:
  ├── 优点: 更简单的管理,更少的 Database 数量
  ├── 缺点: 同一租户下不同项目共享 Database
  │         → 一个项目的大查询影响其他项目
  │         → DDL 变更需要协调同租户的所有项目
  │         → 无法为单个项目独立配置资源配额
  └── 结论: ❌ 隔离粒度不够

Database-per-Project:
  ├── 优点: 每个项目独立的资源配额和性能隔离
  │         → 大查询不影响其他项目
  │         → DDL 变更仅影响当前项目
  │         → 可以独立扩缩容
  ├── 缺点: Database 数量更多 (但 Doris 支持数千个 Database)
  └── 结论: ✅ 最佳平衡点

Database-per-World:
  ├── 优点: 最极致的隔离
  ├── 缺点: Database 数量爆炸 (每个项目可能有 10+ 个 World)
  │         → 资源浪费严重
  │         → 跨 World 查询几乎不可能
  └── 结论: ❌ 过度隔离

#3.2 Level 5 隔离:Nessie Branch-per-World

World 级别的隔离通过 Nessie 分支实现。Nessie 是 Git-like 的数据湖目录管理器,与 Iceberg 表格式配合工作。

Code
Nessie Repository (per Project)
├── Branch: world_production     ← 生产环境
│   └── Iceberg Tables:
│       ├── entity_common        (指向 Iceberg 快照 S-2024-001)
│       ├── entity_edge          (指向 Iceberg 快照 S-2024-002)
│       └── entity_event         (指向 Iceberg 快照 S-2024-003)
│
├── Branch: world_staging        ← 预发布环境
│   └── Iceberg Tables:
│       ├── entity_common        (指向 Iceberg 快照 S-2024-004)
│       ├── entity_edge          (从 production 继承)
│       └── entity_event         (从 production 继承)
│
├── Branch: world_sandbox_alice  ← Alice 的沙箱
│   └── Iceberg Tables:          (从 production 分叉)
│       └── ...
│
└── Branch: world_whatif_q3      ← Q3 计划模拟
    └── Iceberg Tables:          (从 production 分叉)
        └── ...

Nessie 分支的关键特性:

  • O(1) 创建:创建一个新 World 只需创建一个 Nessie 分支,这是一个元数据操作,耗时 < 100ms。不需要复制任何数据。
  • Copy-on-Write:新 World 共享父 World 的所有 Iceberg 数据文件,只有在新 World 中发生写入时才产生新的数据文件。
  • 可合并:沙箱中的修改可以通过 Nessie Merge 操作合并回生产环境,类似 Git 的 merge。
  • 时间旅行:每个 World 保留完整的版本历史,可以回溯到任意时间点的数据状态。

#3.3 Doris + Nessie 的协同工作

Code
读取操作的路由:

请求到达 → 提取 WorldContext
         → project_id → 确定 Doris Database
         → world_id → 确定 Nessie Branch

Case 1: 实时数据查询 (热数据)
  → 查询 Doris Database (project_id)
  → SQL 中注入 world_id 过滤条件
  → 返回结果

Case 2: 历史数据查询 / 时间旅行
  → 查询 Nessie Branch (world_id)
  → 获取指定时间点的 Iceberg 快照
  → 通过 Doris 的 Iceberg Catalog 查询
  → 返回结果

Case 3: 跨 World 对比
  → 同时查询两个 Nessie Branch
  → 比较两个 Iceberg 快照的差异
  → 返回 diff 结果

#4. 计算隔离

#4.1 资源配额模型

计算隔离确保一个租户/项目的工作负载不会影响其他租户/项目。coomia-dip 在多个级别实施资源配额:

Code
Tenant Level Quota:
  ├── max_orgs: 50
  ├── max_total_storage: 10TB
  ├── max_concurrent_queries: 1000
  └── max_api_calls_per_minute: 10000

Org Level Quota:
  ├── max_spaces: 20
  ├── max_storage: 2TB (不超过 Tenant 上限)
  └── max_concurrent_queries: 200

Space Level Quota:
  ├── max_projects: 10
  └── max_storage: 500GB

Project Level Quota:
  ├── max_worlds: 20
  ├── doris_database_size_limit: 100GB
  ├── max_concurrent_queries: 50
  ├── max_object_count: 10,000,000
  └── max_derived_property_computations_per_minute: 1000

World Level Quota:
  ├── max_nessie_branch_size: 50GB (Iceberg 数据)
  ├── max_concurrent_users: 100
  └── max_event_rate: 500/second

#4.2 查询资源隔离

Doris 的 Workload Group 功能用于实现查询级别的资源隔离:

SQL
-- 为每个 Project 创建 Workload Group
CREATE WORKLOAD GROUP proj_huaneng_ops_001
PROPERTIES (
  'cpu_share' = '10',
  'memory_limit' = '30%',
  'max_concurrency' = '50',
  'max_queue_size' = '100',
  'queue_timeout' = '5000'
);

-- 查询时自动绑定 Workload Group
SET workload_group = 'proj_huaneng_ops_001';
SELECT ... FROM entity_common WHERE ...;

这确保了即使某个项目的用户执行了一个非常耗资源的查询,也不会影响其他项目的查询性能。

#4.3 推理计算隔离

Reasoning & Decision Layer(推理与决策)的计算任务(规则引擎、派生属性计算、AI 推理)通过 Kubernetes 命名空间和资源限制实现隔离:

YAML
# 每个 Tenant 一个 Kubernetes Namespace
apiVersion: v1
kind: Namespace
metadata:
  name: tenant-huaneng
  labels:
    coomia-dip/tenant-id: "huaneng"

---
# ResourceQuota 限制租户的计算资源
apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: tenant-huaneng
spec:
  hard:
    requests.cpu: "32"
    requests.memory: "64Gi"
    limits.cpu: "64"
    limits.memory: "128Gi"
    pods: "100"

#5. 权限继承模型

#5.1 继承方向:自上而下

权限在 5 级模型中自上而下继承,高层级的权限自动传递到下层级:

Code
Tenant Admin (租户管理员)
  │  拥有该租户下所有资源的完全管理权限
  │
  ├── Org Admin (组织管理员)
  │   │  拥有该组织下所有 Space、Project、World 的管理权限
  │   │  继承自 Tenant Admin 的授权
  │   │
  │   ├── Space Admin (空间管理员)
  │   │   │  拥有该空间下所有 Project、World 的管理权限
  │   │   │
  │   │   ├── Project Member (项目成员)
  │   │   │   │  拥有该项目下所有 World 的操作权限
  │   │   │   │
  │   │   │   └── World Viewer (世界查看者)
  │   │   │       仅能查看特定 World 的数据

#5.2 权限覆盖规则

Code
规则 1: 高层级授权 → 低层级自动继承
  Org Admin 对 OrgA 授权 → 自动拥有 OrgA 下所有 Space 的权限

规则 2: 低层级可以收紧但不能放大
  Space Admin 不能授予超过 Org Admin 给予的权限

规则 3: 显式拒绝优先于继承允许
  即使 Org Admin 允许访问,Space 级别的显式 DENY 仍然生效

规则 4: 跨级授权需要管理员
  将 Org A 的用户添加到 Org B 的 Space 中,需要 Tenant Admin

优先级链:
  显式 DENY > 显式 ALLOW > 继承 DENY > 继承 ALLOW > 默认 DENY

#5.3 RBAC + ABAC 混合模型

RBAC 处理"角色能做什么",ABAC 处理"在什么条件下能做什么":

Code
RBAC 角色定义 (每个 Space 可自定义):

Role: DataEngineer
  Permissions:
    - ObjectType.* : READ
    - ObjectType.create : ALLOW (限 data-source 类型)
    - Pipeline.* : READ, WRITE, EXECUTE
    - World.switch : ALLOW

Role: DataAnalyst
  Permissions:
    - ObjectType.* : READ
    - DerivedProperty.* : READ, CREATE
    - Search.* : READ
    - Export.* : ALLOW (限 1000 行)

Role: BusinessUser
  Permissions:
    - Dashboard.* : READ
    - Action.execute : ALLOW (限 approved 类型)
    - World.switch : DENY (只能看到生产 World)
Code
ABAC 策略示例:

Policy: "sensitive-data-time-restriction"
  Condition:
    resource.classification == "CONFIDENTIAL"
    AND time.hour < 8 OR time.hour > 20
  Effect: DENY
  Reason: "机密数据仅在工作时间可访问"

Policy: "geo-restriction"
  Condition:
    resource.region != principal.department_region
  Effect: DENY
  Reason: "只能访问本区域的数据"

Policy: "data-volume-limit"
  Condition:
    request.type == "EXPORT"
    AND request.row_count > 10000
    AND principal.role != "DataEngineer"
  Effect: DENY
  Reason: "非工程师角色导出不超过 10000 行"

#6. 自动供应(Auto-Provisioning)

#6.1 传统流程的痛点

传统企业平台创建一个新项目环境的流程通常是这样的:

Code
Day 1: 提交工单 → "请创建新的数据库实例"
Day 2: DBA 审批 → 排队等待
Day 3: DBA 创建数据库 → 配置用户权限
Day 4: 提交工单 → "请创建新的 Kafka Topic"
Day 5: 消息中间件管理员审批 → 创建 Topic
Day 6: 提交工单 → "请创建 MinIO Bucket"
Day 7: 存储管理员审批 → 创建 Bucket
Day 8: 开始开发 → 发现缺少 Nessie 配置
Day 9: ...

总耗时: 1-2 周
涉及人员: 3-5 个不同团队
失败概率: 约 30% (某个环节遗漏或配置错误)

#6.2 Auto-Provisioning 架构

coomia-dip 通过 Auto-Provisioning 机制将上述流程自动化:

Code
创建新 Project:

API: POST /api/v1/admin/projects

ProjectProvisioner 自动执行:
  ├── Step 1: 验证配额 (1ms)
  │   ├── 检查 Space 下 Project 数量是否超限
  │   ├── 检查存储配额是否充足
  │   └── 检查计算资源是否可分配
  │
  ├── Step 2: 创建 Doris Database (200ms)
  │   ├── CREATE DATABASE proj_{tenant}_{name}_{id}
  │   ├── CREATE TABLE entity_common (...)
  │   ├── CREATE TABLE entity_edge (...)
  │   ├── CREATE TABLE entity_event (...)
  │   ├── CREATE WORKLOAD GROUP proj_{id} (...)
  │   └── GRANT SELECT, INSERT, UPDATE ON proj_{id}.* TO svc_account
  │
  ├── Step 3: 创建 Nessie Repository (100ms)
  │   ├── 创建 Nessie Namespace
  │   ├── 创建 main Branch (默认 World)
  │   └── 初始化 Iceberg 表元数据
  │
  ├── Step 4: 创建 Kafka Topics (300ms)
  │   ├── proj_{id}.data-change (数据变更事件, 12 partitions)
  │   ├── proj_{id}.audit.access (审计日志, 6 partitions)
  │   ├── proj_{id}.audit.mutation (审计日志, 6 partitions)
  │   ├── proj_{id}.subscription (订阅路由, 6 partitions)
  │   └── proj_{id}.derived-property (派生属性级联, 6 partitions)
  │
  ├── Step 5: 创建 MinIO Bucket (50ms)
  │   ├── 创建 bucket: proj-{id}
  │   ├── 设置生命周期策略
  │   └── 配置访问策略
  │
  ├── Step 6: 创建 Redis 命名空间 (10ms)
  │   ├── 配置 key 前缀: proj:{id}:
  │   └── 设置内存配额
  │
  ├── Step 7: 初始化元数据 (50ms)
  │   ├── 在 PostgreSQL 注册 Project 元数据
  │   ├── 创建默认角色和权限
  │   └── 记录供应审计日志
  │
  └── 完成! 总耗时: ~800ms

从"提交工单等 1 周"到"API 调用等 1 秒"

#6.3 World 的快速创建

创建新 World 比创建 Project 更快,因为 World 共享 Project 的 Doris Database:

Code
创建新 World:

WorldProvisioner 自动执行:
  ├── Step 1: 验证配额 (1ms)
  │   └── 检查 Project 下 World 数量是否超限
  │
  ├── Step 2: 创建 Nessie Branch (50ms)
  │   ├── 从源 Branch (通常是 production) 分叉
  │   └── 这是纯元数据操作,不复制数据
  │
  ├── Step 3: 初始化 World 上下文 (20ms)
  │   ├── 注册 World 元数据
  │   └── 创建 world_id 的权限记录
  │
  └── 完成! 总耗时: ~80ms

创建一个新的模拟沙箱: 80ms
沙箱拥有与生产环境完全相同的数据 (Copy-on-Write)
沙箱中的修改不影响生产环境

#6.4 自动清理(Auto-Deprovisioning)

当删除 Project 或 World 时,Auto-Deprovisioning 自动清理所有关联资源:

Code
删除 Project:

ProjectDeprovisioner 自动执行:
  ├── Step 1: 验证无活跃连接 (10ms)
  ├── Step 2: 创建最终快照到 MinIO (异步, 不阻塞)
  ├── Step 3: 删除 Kafka Topics (200ms)
  ├── Step 4: 删除 Nessie Repository (100ms)
  ├── Step 5: DROP DATABASE proj_{id} (500ms)
  ├── Step 6: 清理 Redis 命名空间 (10ms)
  ├── Step 7: 标记 MinIO Bucket 过期 (不立即删除, 30 天后自动清理)
  ├── Step 8: 更新 PostgreSQL 元数据 (10ms)
  └── Step 9: 记录清理审计日志 (5ms)

数据保留策略:
  - Doris 数据: 立即删除
  - MinIO 数据: 保留 30 天 (可恢复)
  - 审计日志: 根据主题保留期保留
  - PostgreSQL 元数据: 软删除, 保留 90 天

#7. 跨级查询与数据共享

#7.1 跨 World 查询

同一 Project 下的不同 World 之间可以进行对比查询,这是"模拟沙箱"功能的基础:

Code
场景: 对比生产环境和模拟沙箱的数据差异

API: POST /api/v1/ontology/worlds/compare
{
  "source_world": "world_production",
  "target_world": "world_sandbox_q3",
  "object_type": "InventoryItem",
  "compare_properties": ["quantity", "reorder_point"]
}

内部实现:
  1. 获取 production 的 Nessie Branch → Iceberg 快照 A
  2. 获取 sandbox_q3 的 Nessie Branch → Iceberg 快照 B
  3. Iceberg 提供快照级 diff:
     - 新增的数据文件
     - 删除的数据文件
     - 修改的行 (通过 position-delete 文件)
  4. 返回差异结果

#7.2 跨 Project 数据共享

不同 Project 之间默认不可直接查询,但可以通过 DataLink 机制共享特定的 ObjectType:

Code
DataLink 定义:

{
  "link_name": "shared_equipment_catalog",
  "source_project": "proj_asset_management",
  "source_object_type": "Equipment",
  "target_project": "proj_maintenance",
  "exposed_properties": ["name", "location", "model", "install_date"],
  "hidden_properties": ["purchase_price", "depreciation"],
  "access_mode": "READ_ONLY",
  "sync_mode": "REAL_TIME"   // 通过 Kafka CDC 实时同步
}

DataLink 确保:

  • 目标 Project 只能看到显式暴露的属性
  • 数据以只读方式提供
  • 访问仍然通过 OntologyRuntimeService,受权限检查和审计约束
  • 数据血缘自动记录跨 Project 的依赖关系

#7.3 跨 Tenant 隔离的绝对性

跨 Tenant 的数据共享是绝对禁止的。没有 DataLink,没有管理员后门,没有任何机制可以让 Tenant A 看到 Tenant B 的数据。

Code
隔离保证:

1. 身份隔离: Tenant A 的用户 Token 中的 tenant_id
   与 Tenant B 的资源 tenant_id 永远不会匹配

2. 网络隔离: 不同 Tenant 的 gRPC 请求携带不同的 TLS 证书

3. 存储隔离: 不同 Tenant 的 Doris Database 名称包含 tenant_id
   SQL 注入也无法跨 Database 查询 (Doris 权限控制)

4. 缓存隔离: Redis 的 key 前缀包含 tenant_id
   即使缓存穿透也不会返回其他租户的数据

5. 审计隔离: 审计日志中的 tenant_id 不可伪造
   由 API Gateway 从认证 Token 中提取并注入

#8. 实际部署拓扑

#8.1 小规模部署(< 10 租户)

Code
┌─────────────────────────────────────────┐
│          共享基础设施                      │
│                                          │
│  ┌─────────┐  ┌──────┐  ┌──────────┐   │
│  │  Doris   │  │Kafka │  │PostgreSQL│   │
│  │ (共享)   │  │(共享) │  │  (共享)  │   │
│  │          │  │      │  │          │   │
│  │ DB: t1p1 │  │      │  │          │   │
│  │ DB: t1p2 │  │      │  │          │   │
│  │ DB: t2p1 │  │      │  │          │   │
│  └─────────┘  └──────┘  └──────────┘   │
│                                          │
│  ┌──────┐  ┌──────┐  ┌──────────────┐  │
│  │Redis │  │MinIO │  │Nessie+Iceberg│  │
│  │(共享) │  │(共享) │  │   (共享)     │  │
│  └──────┘  └──────┘  └──────────────┘  │
│                                          │
│  隔离方式: Database / Branch / Namespace │
└─────────────────────────────────────────┘

#8.2 大规模部署(100+ 租户)

Code
┌──────────────────────────────────────────────┐
│          租户组 A (金融行业客户)                │
│                                               │
│  ┌─────────┐  ┌──────┐  ┌──────────────┐    │
│  │ Doris   │  │Kafka │  │Nessie+Iceberg│    │
│  │ Cluster │  │Cluster│  │   Cluster    │    │
│  │  (专属)  │  │ (专属) │  │   (专属)     │    │
│  └─────────┘  └──────┘  └──────────────┘    │
│                                               │
│  特点: 独立集群, 物理隔离, 合规认证            │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│          租户组 B (制造业客户, 共享资源)        │
│                                               │
│  ┌─────────┐  ┌──────┐  ┌──────────────┐    │
│  │ Doris   │  │Kafka │  │Nessie+Iceberg│    │
│  │ Cluster │  │Cluster│  │   Cluster    │    │
│  │  (共享)  │  │ (共享) │  │   (共享)     │    │
│  └─────────┘  └──────┘  └──────────────┘    │
│                                               │
│  特点: 共享集群, 逻辑隔离, 成本优化            │
└──────────────────────────────────────────────┘

#8.3 混合部署策略

Code
决策矩阵:

┌─────────────────┬─────────────┬─────────────┐
│ 客户类型         │ 隔离级别     │ 部署方式      │
├─────────────────┼─────────────┼─────────────┤
│ 金融/医疗        │ 物理隔离     │ 专属集群      │
│ 政府             │ 物理隔离     │ 专属集群      │
│ 大型制造业       │ 混合隔离     │ 共享集群+专属DB│
│ 中小企业         │ 逻辑隔离     │ 共享集群      │
│ 试用/POC        │ 逻辑隔离     │ 共享集群      │
└─────────────────┴─────────────┴─────────────┘

#9. 监控与告警

#9.1 多租户监控维度

Code
Level 1 监控 (Tenant):
  - API 调用量 / 分钟
  - 总存储使用量
  - 活跃用户数
  - 错误率

Level 4 监控 (Project):
  - Doris Database 大小
  - 查询 QPS 和延迟 P99
  - Workload Group 资源使用率
  - Kafka Topic 消费延迟

Level 5 监控 (World):
  - Nessie Branch 大小
  - 活跃连接数
  - 事件发布频率
  - 派生属性计算队列长度

#9.2 "吵闹邻居"检测

Code
Noisy Neighbor Detection:

触发条件:
  1. 某 Project 的 Doris 查询延迟 > P95 的 2 倍
  2. 某 Project 的 Kafka 生产速率 > 配额的 80%
  3. 某 Project 的 CPU 使用率 > Workload Group 限制的 90%

自动响应:
  1. 告警通知 → 项目负责人 + 平台运维
  2. 自动限流 → 降低该 Project 的并发限制
  3. 查询日志 → 记录导致高负载的查询语句
  4. 建议优化 → 自动分析慢查询并给出索引建议

#10. 与 Palantir Foundry 的多租户对比

维度Palantir Foundrycoomia-dip
隔离层级3 级 (Enrollment → Org → Project)5 级 (+Space +World)
版本化Dataset 级 TransactionWorld 级 Nessie Branch
数据隔离共享 HDFS + ACLDatabase-per-Project
计算隔离Spark 资源池Doris Workload Group + K8s Namespace
模拟沙箱Branch (AIP 功能)World (原生支持)
自动供应Enrollment 流程 (人工审批)API 驱动 (< 1 秒)

coomia-dip 的 5 级模型比 Foundry 多了 Space 和 World 两个层级。Space 更好地映射了企业内部的部门结构,World 则提供了原生的"模拟沙箱"能力——这在 Foundry 中需要 AIP(AI Platform)的高级功能支持。

#11. 常见问题与最佳实践

#11.1 何时创建新的 Org vs Space

Code
创建新 Org:
  - 独立法律实体(子公司)
  - 需要独立计费
  - 需要完全独立的管理员
  - 数据默认不互通

创建新 Space:
  - 同一法律实体内的部门
  - 共享计费
  - 可能需要跨部门协作
  - 数据在组织内默认可发现

#11.2 何时创建新的 World

Code
必须创建新 World:
  - 测试/预发布环境 → 避免影响生产数据
  - What-if 模拟 → 修改数据不影响真实环境
  - 数据迁移验证 → 在沙箱中测试 Schema 变更
  - 培训环境 → 为新用户提供安全的练习空间

不需要创建新 World:
  - 简单的权限隔离 → 用 RBAC/ABAC 即可
  - 不同的视图/仪表板 → 用 Dashboard 配置即可
  - 历史数据查看 → 用时间旅行(Nessie 快照)即可

#Key Takeaways

  1. 5 级隔离模型映射了企业组织的真实复杂度。 Tenant → Org → Space → Project → World 的层级结构,比扁平的"租户-用户"模型多了 3 个关键维度:Org 映射法律实体、Space 映射业务单元、World 映射版本化环境。每增加一级隔离,权限管理和资源控制的粒度就更细一层。

  2. 数据隔离的"双保险"设计是关键。 Doris Database-per-Project 提供物理级别的数据隔离,Nessie Branch-per-World 提供逻辑级别的版本隔离。前者确保不同项目之间没有任何数据泄露的可能,后者让同一项目内的多版本管理变得轻量且零成本(O(1) 分支创建,Copy-on-Write 数据共享)。

  3. Auto-Provisioning 将环境创建从"周"级降到"秒"级。 传统企业需要 1-2 周和 3-5 个团队的协调才能创建一个新项目环境。coomia-dip 通过自动化供应,在 < 1 秒内完成 Doris Database、Nessie Repository、Kafka Topics、MinIO Bucket 的创建。这不只是效率提升——它改变了团队使用环境的方式:从"珍惜地使用稀缺环境"变为"随时创建用完即弃的沙箱"。

下一篇预告: [S2-06] 事件驱动架构:Kafka 在平台中的 7 种角色——深入理解 Kafka 如何承担 CDC、审计、订阅路由、管道触发、派生属性级联、推理触发和跨 Layer 协调共 7 种不同的角色。

Tags: #multi-tenant #isolation #world #nessie #doris #auto-provisioning #rbac #abac #coomia-dip #智策平台