跳转至

Vertica 多租户实现最佳实践

编译:JiangChong

原文:Implement Multi-Tenancy Using Vertica(2023-06-28)

📝 文章说明:本文基于 Vertica 官方 KB 原文翻译整理。原文发布于 2023 年,仅讨论了 Enterprise 模式下三种通用多租户方案(多 Schema / 单 Schema+tenant_id / 多集群),未涉及 Eon 模式。译者根据当前 Vertica 技术架构,补充了 Eon 模式专属的 Subcluster 隔离和 Sandbox 隔离两种方案,并更新了方案对比表、选型决策树和结论。新增内容位于「Eon 模式下的多租户方案」章节。

概述

Vertica 允许您设计和实现多租户系统,以服务多个客户。评估您的性能、存储和安全需求,选择最适合您数据设计和工作负载的模型。仔细考虑每个租户随时间所需的表数和列数。表和列会转换为编录对象。如果编录对象的存储需求增加,内存大小也会增加。此外,更大的编录会对数据库性能产生负面影响。

本文档提供了 Vertica 多租户环境的最佳实践。文档描述了为什么要实现多租户,以及五种配置 Vertica 数据库以支持多个租户的方法——其中三种是通用方案(Enterprise / Eon 模式均适用),两种是 Eon 模式专属方案,利用子集群机制实现更高的性价比。

  • 关于多租户
  • 多租户的优缺点
  • 使用 Vertica 实现多租户(通用方案)
    • 多 Schema:每个租户一个 Schema
    • 单 Schema:向基表添加 tenant_id 字段
    • 多集群:每个租户一个集群
  • Eon 模式下的多租户方案(Eon 专属)
    • Subcluster 隔离:每个租户一个子集群(计算隔离 + 存储共享)
    • Sandbox 隔离:完全数据+计算隔离(快照后独立演进)

多租户简介

在 Vertica 多租户环境中,一个 Vertica 数据库实例支持多个租户访问数据。每个租户可以访问自己的数据集,或多个租户可以共享特定数据集。

软件即服务(SaaS)环境使用多租户。Vertica 客户拥有一个为多个客户提供服务的数据库。通常,该数据库包含多个数据集,每个数据集对应提供给每个客户(租户)的定制服务。

考虑一个示例:XYZ 公司拥有域名 www.xyz.com。 XYZ 公司有多个客户共享该网站空间,但使用自己的 URL。ABC 公司拥有 www.abc.com,DEF 公司拥有 www.def.com。XYZ 公司配置了一个 Vertica 数据库,使每个域在该数据库中拥有自己的数据集(定义为 Schema)。每个客户只能加载、操作和分析自己的数据。他们无法访问其他 Schema 中的数据。

这种配置为 XYZ 公司的客户节省了成本。XYZ 公司承担购买 Vertica 数据库和雇佣数据库管理员的成本,而不是其客户。XYZ 公司管理数据库并为其客户提供服务。

使用 Vertica 实现多租户的优缺点

如果多租户适合您的业务需求,请使用 Vertica 多租户。

优点 缺点
使用 Vertica,您可以通过以下功能创建多租户环境:

- 用户定义的资源池允许租户共享内存资源。工作负载较高或有嘈杂邻居的租户需要自己的资源池。资源池无法帮助管理多个租户的 CPU、I/O 和网络需求。
- Schema 用于隔离每个租户,允许每个租户访问其指定 Schema 中的数据。
- 对象级备份可以单独备份每个租户,并按不同的时间表进行。
- 存储位置指定数据的存储位置。您可以出于 I/O 或安全目的将每个租户的数据存储在不同的存储位置。
- 如果只有一个数据库实例,只需执行一次数据库升级即可影响所有租户。
- 使用 EXPORT TO 和 COPY FROM 在 Schema 之间复制数据。这些语句支持在表级或 Schema 级复制数据。
如果您的环境需要 SaaS 多租户模型,请注意 Vertica 和多租户的以下问题:

- 如果租户数量很大,您可能需要复杂的数据库配置,以确保没有租户资源匮乏。
- 某些 Vertica 数据库操作具有系统范围的影响,并且会绕过 Schema 隔离。例如,如果您为特定租户创建投影而 Vertica 未刷新该投影,则可能会阻碍所有租户的 Ancient History Mark (AHM)。此外,DDL 操作可能导致系统范围的冲突。添加新租户在创建数据库对象时可能会影响现有租户。
- 租户数量的增加可能导致数据库编录庞大。编录锁持有时间增加,从而减慢所有依赖编录锁的操作。

Vertica 中三种通用多租户方法

以下三种方法在 Enterprise 和 Eon 模式下均适用,是 Vertica 多租户的基础方案。每种方法都有其优缺点。Vertica 建议您仔细评估每种方法,以确定哪种方法最适合您的环境。

注意:如果你运行的是 Eon 模式,请务必同时阅读下一节「Eon 模式下的多租户方案」——Eon 的子集群机制提供了更优的隔离选项。

多 Schema:每个租户一个 Schema

在这种方法中,您为每个租户创建一个 Schema。您配置角色级和用户级安全性,以确保每个租户只能访问其特定数据。

多 Schema:每个租户一个 Schema

适用场景

在真正需要多租户时使用每个租户一个 Schema 的方法。这种方法在风险、灵活性、性能和成本之间取得了平衡,适用于 Vertica 多租户环境。它易于设置和管理。由于租户工作负载可预测,您可以使用用户定义的资源池。当租户需要从其应用程序或客户端直接安全访问数据库时,此方法适用。

优缺点

优点 缺点
- 每个租户拥有自己的 Schema 和表,数据库管理简单。
- Schema 相互隔离,无需手动过滤数据。
- 无限数量的 Schema、投影和系统范围的 ROS 容器数量。
- 易于单独备份和恢复每个租户的数据。
- 可以为每个租户定义单独的数据保留策略。
- 大量租户及其 Schema 和表会导致编录庞大。编录过大导致编录锁持有时间变长,影响所有数据库操作。
- 当每个租户有独立 Schema 时,跨所有租户的数据分析变得复杂。
- 需要为每个租户创建资源池来管理嘈杂邻居。
- 集群宕机时会影响所有租户。如果一个节点故障,另一个节点需为两个租户提供服务,从而影响数据库性能。
- 将数据分离到每个客户的表中,压缩时间比单表更长。
- 需要限制租户规模和数量,以避免编录过大问题。
- Vertica 使用横向扩展集群,并将查询分发到所有节点。这种设计限制了核心数量。例如,假设有 40 个并发线程。您必须为所有租户提供资源和连接。40 个小租户可能占用集群连接,而大租户可能需要等待很长时间才能连接。

单 Schema:向基表添加 tenant_id 字段

在这种方法中,您为每个租户分配一个唯一 ID。每个表包含一个 tenant_id 字段,并存储所有租户的数据。由于每个查询都指定了 tenant_id,租户可以访问其具有读取权限的数据。您必须在记录级或角色级实现安全过滤器,以维护跨租户的数据隐私。

以下查询返回 click_data 表中租户 ABC 的所有行:

SELECT * FROM click_data WHERE tenant_id="ABC";

单 Schema:向基表添加 tenant_id 字段

适用场景

当 Vertica 作为托管租户的后端数据库时,使用单 Schema 方法。在这种方法中,同一张表存储相关记录,无论 tenant_id 如何。如果您想分析来自所有租户的所有数据,这些数据都在单张表中可用。

例如,对于 XYZ 公司,click_data 表记录每个租户每天的点击次数。您只需访问 click_data 表即可跨所有租户分析此数据。对于少于 10 个租户,通过实施 RLE 编码并使用 ORDER BY tenant_id,可以获得出色的性能。这种方法可以容纳许多租户。但是,您必须评估工作负载和单个租户编录大小的影响。

当租户工作负载使用内存小于 5 GB 的查询时,单 Schema 方法有效。位于不同地理时区的租户不需要同时使用资源。具有相同物理投影的租户可能会影响其他租户的服务级别协议 (SLA)。共享相同 Schema 和表的租户安全性较低。单 Schema 方法适用于固定应用程序,其中数据和报告由单一应用程序安全控制。

优缺点

优点 缺点
- 基于角色的配置访问确保租户隐私。
- 较少的 Schema 和表设计避免了大编录,并确保较少的全局锁影响性能。
- 与每个租户拥有独立 Schema 相比,跨所有租户的数据分析更容易。
- 设计可扩展,没有架构上的租户数量上限。
- 能够以相同效率管理不同数据库占用规模的租户。
- BI 平台能够通过对所有查询使用安全过滤器来支持多租户。
- 在单张表中,由于所有租户的数据类型可比,压缩效果更好,而不是将数据分散到特定于租户的表中。
- 能够使用 tenant_id 和基于时间的表达式创建分区。
- 使用 tenant_id 可以在查询执行期间实现分区裁剪,因为 tenant_id 出现在每个查询的 WHERE 子句中。分区裁剪基于日期/时间范围(如当前月份)进行评估。每个分区包含该月所有租户的所有数据。
- 使用基于时间的表达式可以创建旧分区的离线归档。离线归档可以备份、删除,并在以后需要时恢复。使用基于时间的表达式可减少 Tuple Mover 操作的影响。新数据在活动分区中处理。
- 大量租户会导致分区过多。过多的分区会导致 ROS 反压和磁盘碎片。
- 跨分区批量加载新数据会导致 Tuple Mover moveout 问题。
- 同一张表中所有租户的数据若要使用分区,必须遵循相同的数据保留策略。
- 备份和恢复单个租户数据较为困难,因为表包含多个租户的数据。
- 管理挑战包括:为每个租户设置访问权限、创建和分配 tenant_id,以及使用 tenant_id 支持数据库操作。
- 租户的查询会垄断系统资源。如果存在工作负载较重的租户,需要为每个租户创建资源池。
- 按租户许可证审核原始数据较为困难。
- 每个租户使用的磁盘空间有限。
- 租户之间可能因 Schema 变更产生冲突。
- 如果一个集群宕机,会影响所有租户。

多集群:每个租户一个集群

在这种方法中,您为每个租户设置多个集群。每个集群上运行一个 Vertica 实例。每个租户与其他租户完全隔离,并可完全访问其集群上的资源。

使用多个集群可以自动化集群配置并有效扩展集群。在云中添加新集群既简单又便宜。每个租户拥有独立集群,提供了完全的隔离。隔离租户消除了嘈杂邻居的影响。

多集群:每个租户一个集群

适用场景

当 Vertica 托管在云中时,使用多集群方法实现多租户。当租户工作负载多样化且不可预测时,此方法较为有利。

优缺点

优点 缺点
- 每个租户拥有自己的 Schema 和表,数据库管理简单。Schema 相互隔离,无需手动过滤数据。可以实现基于用户和基于角色的安全性。
- 大型编录在单个集群上也可管理。
- 每个租户独占系统资源。租户之间互不影响。
- 易于单独备份和恢复每个租户的数据。
- 每个租户拥有独立的数据保留策略。如果某个集群故障,只会影响一个租户。
- 需要额外的硬件基础设施成本,如网络、交换机、存储和服务器来支持多个集群。
- 系统和数据库管理成本随集群数量增加而增加。数据库管理员需要监控软件、执行备份和恢复,并在所有集群上执行软件升级。
- 多个 Vertica 实例需要重复的脚本和流程用于数据加载、ETL 和报表生成。

Eon 模式下的多租户方案

上述三种方案在 Enterprise 和 Eon 模式下均适用。但 Eon 模式的计算-存储分离架构引入了子集群(Subcluster) 机制,从根本上改变了多租户的设计空间——它提供了介于「多 Schema」和「多集群」之间的新选项:计算节点物理隔离,但存储共享

Eon 模式为何改变多租户格局

Enterprise 模式下,每个节点既存数据也算数据,增加一个「集群」意味着要复制一份完整的数据存储(通过 rebalance 或新建集群)。这导致「多集群」方案虽然隔离性最强,但存储成本和管理成本都很高。

Eon 模式打破了这种耦合:

维度 Enterprise 模式 Eon 模式
数据存储 节点本地磁盘,每节点持有一部分数据 公共存储(S3/MinIO/HDFS),节点只做计算
创建新"集群" 需要独立数据副本 + rebalance 只需创建子集群,数据已在公共存储中
计算隔离成本 高(独立的存储+服务器+管理) 低(只增加计算节点,共享存储)
弹性 数小时~数天 分钟级

这意味着 Eon 模式天然支持一种新方案:为每个租户分配独立子集群,但所有租户共享同一份公共存储数据

方案四:Eon Subcluster 隔离(每个租户一个子集群)

在这种方法中,你在一个 Eon 数据库中创建多个 secondary 子集群,每个子集群服务于一个租户。所有租户的数据存储在同一份公共存储中,但各自的查询在独立的计算节点上执行。

                    ┌─────────────────────────────┐
                    │    公共存储 (S3/MinIO/HDFS)    │
                    │  所有租户数据共享同一份存储       │
                    └──────┬──────────┬───────────┘
                           │          │
              ┌────────────┴──┐  ┌────┴──────────────┐
              │ Subcluster A  │  │  Subcluster B      │
              │ (租户 ABC)    │  │  (租户 DEF)         │
              │ 3 节点        │  │  2 节点             │
              └───────────────┘  └────────────────────┘
                     ↑                    ↑
              租户 ABC 的客户端      租户 DEF 的客户端
           (通过 Routing Rule 路由)  (通过 Routing Rule 路由)

适用场景

  • 租户之间存在不可预测的工作负载,需要计算层物理隔离防止「嘈杂邻居」
  • 租户数量适中(5~20),每个租户值得独立分配计算资源
  • 需要弹性伸缩:在业务高峰期临时为某租户增加节点数,低谷期缩减
  • 租户数据量大,但不需要完全独立的存储副本(数据隔离通过 Schema + Access Policy 实现)
  • 云环境部署,计算节点按需付费

优势

  • 存储不复制:所有子集群共享公共存储,无需为每个租户维护独立数据副本。存储成本显著低于「多集群」方案
  • 计算物理隔离:每个租户的查询在独立节点上执行,CPU、内存、网络互不干扰。这比资源池隔离(仅内存维度)强大得多
  • 弹性伸缩:secondary 子集群可随时启停,不影响数据库稳定性。租户 A 的业务高峰只影响子集群 A,与租户 B 完全无关
  • 管理成本低:所有租户在同一个数据库实例中,只需执行一次升级、一次备份(对象级备份可选择性备份特定租户 Schema)
  • 独立 SLA:每个子集群可配置不同的节点规格和数量,匹配各租户的 SLA 需求
  • 部署快速:创建子集群是分钟级操作(admintools -t db_add_subcluster),无需 rebalance

劣势

  • 编录瓶颈仍在:所有子集群共享同一个 Catalog。租户数量过大时,编录锁争夺仍会影响所有租户
  • 主子集群单点风险:primary 子集群故障会影响整个数据库(包括所有租户的子集群)。建议 primary 子集群保持 K-safe 配置
  • Schema 级别的安全隔离仍需配置:子集群提供的是计算隔离而非数据隔离。如果租户 A 和 B 的数据在同一数据库中,需要通过 Schema + 权限 + Access Policy 确保数据安全
  • 版本升级影响全局:数据库升级会同时影响所有租户,无法按租户灰度升级

配置示例

1. 创建租户专属子集群:

# 为租户 ABC 创建 3 节点的 secondary 子集群
admintools -t db_add_subcluster -d mydb \
  -c sc_tenant_abc \
  --hosts=node04,node05,node06 \
  --is-secondary

# 为租户 DEF 创建 2 节点的 secondary 子集群
admintools -t db_add_subcluster -d mydb \
  -c sc_tenant_def \
  --hosts=node07,node08 \
  --is-secondary

2. 配置子集群级别资源池覆盖(可选,精细控制每个租户的计算资源):

-- 为租户 ABC 子集群覆盖 General 资源池的内存限制
ALTER RESOURCE POOL general FOR SUBCLUSTER sc_tenant_abc
  MEMORYSIZE '80%'
  MAXMEMORYSIZE '90%'
  MAXQUERYMEMORYSIZE '50GB';

3. 配置连接路由,将租户客户端定向到对应子集群:

-- 创建负载均衡组,绑定子集群
CREATE LOAD BALANCE GROUP lbg_tenant_abc
  WITH SUBCLUSTER sc_tenant_abc
  FILTER '0.0.0.0/0'
  POLICY 'ROUNDROBIN';

-- 创建路由规则:租户 ABC 的客户端 IP 段路由到对应组
CREATE ROUTING RULE rr_tenant_abc
  ROUTE '10.1.1.0/24' TO LOAD BALANCE GROUP lbg_tenant_abc;

从 Vertica 23.3 开始,还可以使用 WORKLOAD_ROUTING_RULES 按工作负载类型(--workload 参数)路由,而不依赖客户端 IP。例如:CREATE WORKLOAD ROUTING RULE wrr_etl ROUTE WORKLOAD 'etl' TO SUBCLUSTER sc_etl;

4. 弹性伸缩操作(子集群启停):

-- 租户 ABC 业务低谷期,优雅关闭其子集群(等待活跃会话排空,超时 300 秒)
SELECT SHUTDOWN_WITH_DRAIN('sc_tenant_abc', 300);

-- 或立即强制关闭(不等待活跃会话)
SELECT SHUTDOWN_SUBCLUSTER('sc_tenant_abc');
# 高峰期恢复(启动子集群需通过 admintools,无对应 SQL 函数)
admintools -t restart_subcluster -c sc_tenant_abc -d mydb

⚠️ SHUTDOWN_SUBCLUSTER() 仅适用于 secondary 子集群;对 primary 子集群执行会导致数据库进入只读状态。建议生产环境优先使用 SHUTDOWN_WITH_DRAIN() 优雅关闭。

方案五:Eon Sandbox 隔离(完全数据+计算隔离)

Sandbox 是 Eon 模式的独特功能:将一个 secondary 子集群「沙箱化」,创建时继承主集群的 Catalog 和数据快照,之后沙箱内的数据变更与主集群完全隔离,互不影响。

                    创建 Sandbox 时
        主集群 Catalog + 数据 ──快照──→ Sandbox 子集群
                    之后各自独立演进              │
        主集群:DROP TABLE ➔ 不影响 ──→   Sandbox:表仍存在
        主集群:LOAD 新数据 ➔ 不影响 ──→   Sandbox:看不到新数据
        Sandbox:DELETE 数据 ➔ 不影响 ──→   主集群:数据仍在

适用场景

  • 版本升级验证:先 sandbox 一个子集群出来,在沙箱中升级并测试,确认无问题后再对主集群执行升级
  • 数据共享给外部团队:将数据快照 sandbox 出去,外部团队可在沙箱中自由操作(删表、改数据、加载新数据)而不会污染主集群
  • 开发/测试环境:从生产数据库 sandbox 出一份数据,开发测试结束后可直接丢弃,无需单独维护测试集群
  • 需要数据完全隔离的租户:某些租户要求「我的数据和你的数据物理上不在同一份存储中」,Sandbox 可以提供比 Subcluster 更强的隔离保证

优势

  • 数据完全隔离:Sandbox 后各自的数据变更互不影响。租户删除表、修改数据都不会传播到主集群或其他 Sandbox
  • 单数据库管理:仍在同一数据库实例中,无需管理多个独立数据库
  • 快速创建:Sandbox 创建是快照操作,比新建一个独立数据库快得多
  • 可逆操作:Sandbox 可以 unsandbox 回到主集群(前提是沙箱中的数据变更可以被接受或放弃)

劣势

  • 存储成本增加:Sandbox 后数据独立演进,公共存储中会逐渐产生两份数据
  • 数据同步困难:Sandbox 后不再自动同步。如果主集群数据更新了,沙箱不会感知
  • Sandbox 数量有限:大量 Sandbox 意味着大量独立数据副本,存储成本和管理复杂度都会上升
  • Catalog 独立演进:DDL 变更不会跨 Sandbox 传播。如果需要在所有租户中同步 Schema 变更,需在每个 Sandbox 中单独执行

操作示例

# 1. 创建 Sandbox:将子集群 sandbox 化(第一个子集群用 admintools)
admintools -t sandbox_subcluster -d mydb -c sc_tenant_abc -b sand_tenant_abc
-- 2. 将其他子集群加入已有 Sandbox(SQL 函数,先在沙箱集群执行,再在主集群执行)
SELECT SANDBOX_SUBCLUSTER('sand_tenant_abc', 'sc_tenant_def', '');
# 3. 解除 Sandbox,子集群回归主集群
admintools -t unsandbox_subcluster -d mydb -c sc_tenant_abc
-- 4. 清除 Sandbox 在公共存储中遗留的数据
SELECT CLEAN_COMMUNAL_STORAGE('true');

以上语法来自 Vertica 26.2.x 官方文档。SANDBOX_SUBCLUSTER() 用于向已有 Sandbox 中添加额外子集群,而非创建 Sandbox 本身。

Eon 模式下的方案对比

维度 多 Schema 单 Schema + tenant_id Subcluster 隔离 Sandbox 隔离 多集群
计算隔离 ❌ 共享所有节点 ❌ 共享所有节点 ✅ 独立计算节点 ✅ 独立计算节点 ✅ 独立集群
存储隔离 ❌ 同库 ❌ 同表 ❌ 共享公共存储 ✅ 快照后独立 ✅ 完全独立
存储成本 最低 中(逐渐增长) 高(N 份)
管理成本 高(N 个集群)
数据共享/跨租户分析 需跨 Schema 查询 ✅ 天然支持 需跨 Schema 查询 ❌ 不可行 ❌ 不可行
弹性伸缩 ❌ 需集群级扩缩 ❌ 需集群级扩缩 ✅ 子集群级启停 ✅ 子集群级启停 ✅ 集群级启停
嘈杂邻居防护 差(仅资源池) 差(仅资源池) ✅ 物理隔离 ✅ 物理隔离 ✅ 物理隔离
适合租户数 10~50 100+ 5~20 3~10 3~10
升级影响 影响所有租户 影响所有租户 影响所有租户 沙箱可独立升级 可按租户灰度
Vertica 模式要求 Enterprise / Eon Enterprise / Eon 仅 Eon 仅 Eon 均可

多租户方案选型决策树(更新)

需要多租户
├── 所有租户在同一个 Vertica 数据库实例中?
│   ├── 是
│   │   ├── 运行在 Eon 模式?
│   │   │   ├── 是
│   │   │   │   ├── 租户需要数据完全隔离(独立副本)?
│   │   │   │   │   ├── 是 → Sandbox 隔离
│   │   │   │   │   └── 否 → 租户需要计算物理隔离?
│   │   │   │   │       ├── 是 → Subcluster 隔离
│   │   │   │   │       └── 否 → 租户数量?
│   │   │   │   │           ├── < 50 → 多 Schema
│   │   │   │   │           └── 50+ → 单 Schema + tenant_id
│   │   │   └── 否(Enterprise 模式)
│   │   │       ├── 租户数量 < 50?→ 多 Schema
│   │   │       └── 租户数量 50+?→ 单 Schema + tenant_id
│   └── 否
│       └── 多集群(每个租户独立数据库)

结论

我们看到了五种多租户方案,覆盖了从轻量级逻辑隔离到完全物理隔离的全谱系:

  • 单 Schema + tenant_id:最轻量,适合大量小租户,跨租户分析最方便,但安全和隔离最弱
  • 多 Schema:经典方案,Schema 级隔离易管理,适合中小规模租户(10~50)
  • Eon Subcluster 隔离(⭐ 推荐 Eon 用户优先评估):Eon 模式最佳性价比方案。计算物理隔离 + 存储共享,兼顾隔离性和成本。适合 5~20 个中等规模租户
  • Eon Sandbox 隔离:数据+计算完全隔离但仍保持单数据库管理。适合需要独立数据副本、或用作测试/升级验证的场景
  • 多集群:最强隔离,最高成本。适合 3~10 个大型租户、或安全合规要求极高的场景

关键选择逻辑

  • 如果你在 Enterprise 模式下,选择仍是三种:多 Schema / 单 Schema+tenant_id / 多集群
  • 如果你在 Eon 模式下,强烈建议评估 Subcluster 隔离方案——它在不增加存储成本的前提下提供了计算物理隔离,是多租户场景下 Eon 架构的核心价值之一
  • Subcluster 隔离可以与多 Schema 或单 Schema+tenant_id 组合使用:例如 Subcluster 提供计算隔离,Schema 提供数据隔离,Access Policy 提供行级安全——三层防护

请务必审视这些信息并评估您的数据库需求,以实施最适合您使用场景的设计。

扩展阅读