Vertica 分区表常见问题¶
编译:JiangChong
原文:Vertica Partitions: The FAQs(2023-06-01)
📝 文章说明:本文基于 Vertica 官方 KB 原文翻译整理。原文发布于 2023 年,仅讨论了 Enterprise 模式下的分区行为,未涉及 Eon 模式。译者根据当前 Vertica 技术架构(v26.2),在各章节中补充了 Eon 模式差异提示、分区组(GROUP BY)、分层分区(CALENDAR_HIERARCHY_DAY)、分区管理函数完整列表、DUMP 查看函数,并在文末附 Enterprise vs Eon 分区行为速查表。
概述¶
Vertica 的分区(Partitioning)功能根据一列或多列的值将大表分割成小块。分区可以有效简化数据生命周期管理,并加速包含分区键谓词的查询性能。
本文涵盖以下主题:
- 分区基础(Partition Basics)
- 分区与存储裁剪(Partitions and Storage Pruning)
- 分区与 ROS 文件及 ROS 容器(Partitions and ROS Files and ROS Containers)
- 重分区与重组(Repartitioning and Reorganizing)
- 分区数量限制(Partition Limit Considerations)
一、分区基础¶
1.1 分区工作原理¶
假设需要实现数据保留策略(如仅保留 5 年),传统方式需要逐行删除旧数据。Vertica 分区让这一过程变得高效。
示例:创建一个按年份分区的 trade 表
=> CREATE TABLE trade (
tdate DATE NOT NULL,
tsymbol VARCHAR(8) NOT NULL,
ttime TIME)
PARTITION BY EXTRACT(year FROM tdate);
加载数据时,Vertica 根据分区表达式(此处为日历年 EXTRACT(year FROM tdate))自动将数据分离到不同 ROS 容器中。
分区创建过程:
- 首次加载 2008 年数据 → Vertica 创建第一个分区(称为活跃分区 active partition),数据存储在 ROS 容器中
- 首次加载 2009 年数据 → Vertica 创建新分区作为活跃分区,旧分区变为非活跃分区(inactive partition)
- Tuple Mover 对非活跃分区合并所有 ROS 容器为一个 ROS 容器;对活跃分区使用 strata 算法合并
1.2 分区与分段(Segmentation)的区别¶
- 分段(Segmentation):将数据均匀分配到集群各节点,利用 MPP 架构并行处理。通常用 HASH(trans_id) 进行分段。
- 分区(Partitioning):在每个节点内部将数据组织到不同存储容器中,用于减少 I/O 和提升查询性能。
实践中,大表应同时使用分段和分区:
- 分段:
SEGMENTED BY HASH(trans_id) ALL NODES - 分区:
PARTITION BY EXTRACT(month FROM trans_date)
1.3 活跃分区(Active Partitions)¶
活跃分区是最后创建的分区(而非最后更新的分区),通常存放频繁加载的新数据。
可通过配置参数 ActivePartitionCount 调整活跃分区数量(默认为 1):
- ActivePartitionCount=1:Tuple Mover 对非活跃分区合并为单个 ROS 容器,对活跃分区使用 strata 算法
- ActivePartitionCount=2:最后两个创建的分区都被视为活跃分区,全部使用 strata 算法
增加 ActivePartitionCount 可减少 Tuple Mover 操作次数,但可能导致每个投影的 ROS 容器过多。
按表设置 ActivePartitionCount:如果不同表的更新模式不同,可为单表单独设置活跃分区数,优先级高于全局参数:
-- 全局设置(影响所有未单独设置的表)
=> ALTER DATABASE DEFAULT SET ActivePartitionCount = 2;
-- 按表设置(覆盖全局参数)
=> ALTER TABLE trade SET ACTIVEPARTITIONCOUNT 3;
例如:表 A 仅加载当天数据(ActivePartitionCount=1 足够),表 B 同时回填过去 3 天数据(需 ActivePartitionCount=3)。按表设置避免了全局调大参数导致表 A 产生多余的 ROS 容器。
Eon 模式特别注意:ActivePartitionCount 在 Eon 模式下比 Enterprise 更为关键。向非活跃分区写入数据在 Enterprise 模式中仅产生本地 I/O,但在 Eon 模式下会触发完整的 S3 下载→合并→上传→删除循环。因此加载模式涉及多天历史分区时,务必调大 ActivePartitionCount(如按天分区且回填 3 天 → 设为 3),避免每次加载都引发 S3 mergeout 成本。详见 Eon 模式下 Vertica 的 ETL 最佳实践。
查询活跃分区:
=> SELECT DISTINCT partition_key FROM strata
WHERE projection_name ILIKE '%sktest%'
AND schema_name ILIKE '%public%';
partition_key
---------------
5
8
9
(3 rows)
1.4 哪些表应该分区¶
Vertica 建议仅对大事实表(large fact tables)分区。不要对小表或维度表分区,否则会产生大量 ROS 容器,导致 catalog 膨胀并影响查询性能。
1.5 如何归档冷数据¶
可使用存储策略(Storage Policy)将历史分区迁移到低速廉价存储,为热分区腾出高速存储空间。参考 Vertica 文档 Creating Storage Policies。
1.6 分区组(Partition Grouping)¶
PARTITION BY 后可配合 GROUP BY 子句将多个分区分组合并到同一个 ROS 容器中,从而在不牺牲裁剪精度的前提下大幅减少 ROS 容器数。
基本语法:
-- 不带 GROUP BY:每个分区一个 ROS 容器
PARTITION BY order_date::DATE
-- 带 GROUP BY:按年分组合并,一年的所有日期合并到同一 ROS 容器
ALTER TABLE store_orders
PARTITION BY order_date::DATE
GROUP BY DATE_TRUNC('year', (order_date)::DATE) REORGANIZE;
以上示例中,store_orders 有四年的数据(2014-2017),Tuple Mover 创建 4 个分区组键,每个组对应的 ROS 容器内存储该年所有日期的数据。
分区组与分区管理函数:使用 GROUP BY 后,DROP_PARTITIONS 等函数仍可指定组内的具体日期范围,但需要将 force_split 参数设为 true 以拆分 ROS 容器:
-- 删除跨年的日期范围,force_split=true 拆分对应的 ROS 容器
=> SELECT DROP_PARTITIONS('store_orders', '2015-05-30', '2016-01-16', 'true');
注意:
GROUP BY将所有分组合并为大容量 ROS 容器后,频繁的 DML 操作(尤其是数据加载)可能带来显著的 merge 开销。推荐使用分层分区函数CALENDAR_HIERARCHY_DAY作为 GROUP BY 表达式,动态创建年/月/日的层级分组——详见下方「分层分区」章节。
1.7 分层分区(Hierarchical Partitioning)¶
这是 GROUP BY 的最佳实践实现。CALENDAR_HIERARCHY_DAY 函数按时间远近自动构建三层动态分组:
- 非活跃年份(超过
active-years阈值的旧数据)→ 按年合并 - 非活跃月份(活跃年内但超过
active-months阈值)→ 按月合并 - 活跃月份内(近期数据)→ 保留按天裁剪精度
-- 按 order_date 分区,动态分组:近 2 月按天,之后 2 年按月,更早按年
=> ALTER TABLE store_orders
PARTITION BY order_date::DATE
GROUP BY CALENDAR_HIERARCHY_DAY(order_date::DATE, 2, 2) REORGANIZE;
-- CALENDAR_HIERARCHY_DAY(col, active_months, active_years)
效果对比:809 个 ROS 容器(按天分区无分组)→ 40 个 ROS 容器(分层分区),且近 2 月数据仍保留按天裁剪精度。
随着日历推进,Tuple Mover 会自动重新评估分区组键:旧月份被合并为月容器、旧年份被合并为年容器——无需人工干预。
Vertica 推荐
active-months和active-years均 ≥ 2(默认值 2),切勿设为 0。Eon 模式下的额外收益:分层分区不仅降低 catalog 大小,还能减少 Tuple Mover 在 S3 上的 mergeout 往返频率、提升 Depot 缓存效率(旧数据合成大文件后更利于 LRU 管理)。Eon 与 Enterprise 语法完全一致。
1.8 分区方案设计考虑¶
定义分区表达式时应考虑:
- 数据保留策略
- 常用的查询谓词
- 分区粒度对每节点每投影的 ROS 容器数和总 ROS 文件数的影响
分区选型决策树:
flowchart TD
A["需要分区吗?"]
A -->|"小表(小于 100 万行)或维度表"| A1["❌ 不建议分区"]
A -->|"中大型表,但查询不按时间过滤"| A2["⚠️ 谨慎:分区不加速查询<br/>反而增加 ROS 容器"]
A -->|"大事实表 + 查询/删除依赖时间维度"| B["大事实表 + 时间维度"]
B -->|"需要按时间维度删除历史数据"| B1["✅ 分区<br/>DROP_PARTITIONS 秒级完成"]
B -->|"查询总带日期范围过滤"| B2["✅ 分区<br/>存储裁剪跳过无关 ROS 容器"]
B -->|"加载模式涉及多天历史分区"| C["加载模式?"]
C -->|"同时更新过去 N 天"| C1["按天分区<br/>ActivePartitionCount = N"]
C -->|"同时更新过去 N 周"| C2["按周分区<br/>ActivePartitionCount = max(N, 2)"]
C -->|"仅加载最新数据"| C3["按天或按周<br/>ActivePartitionCount = 1"]
B -->|"Eon 模式"| D["Eon 模式"]
D -->|"需要冷热分层"| D1["SET_OBJECT_STORAGE_POLICY<br/>指定冷分区到低成本 S3 bucket"]
D -->|"分区数偏多,如超过 1 年按天分区"| D2["ROS 容器仍在公共存储<br/>1024 上限同样适用<br/>监控 catalog + Reaper"]
D -->|"向非活跃分区写入"| D3["务必避免<br/>否则每次触发 S3 下载→合并→上传循环"]
B -->|"Enterprise 模式 + 分区数偏多"| E["Enterprise 模式<br/>分区数偏多,如超过 1 年按天分区"]
E -->|"是"| E1["使用分层分区<br/>CALENDAR_HIERARCHY_DAY(col, 2, 2)"]
E -->|"否"| E2["从按天改为按周/月<br/>或归档旧分区"]
二、分区与存储裁剪¶
2.1 分区如何影响数据生命周期管理¶
Vertica 提供以下能力支持数据生命周期管理:
- 删除分区(DROP_PARTITIONS,旧函数
DROP_PARTITION自 Vertica 9.0 起废弃) - 移动冷分区到归档表(MOVE_PARTITIONS_TO_TABLE)
- 分区交换(SWAP_PARTITIONS_BETWEEN_TABLES)
- 分区复制(COPY_PARTITIONS_TO_TABLE)
- 移动冷分区到廉价存储(Storage Policy)
- 从归档恢复分区
模式差异:MOVE_PARTITIONS_TO_TABLE / SWAP / COPY 在两模式下均为纯 catalog 操作(不移动数据文件)。DROP_PARTITIONS 在 Enterprise 模式下同步删除本地 ROS 文件;在 Eon 模式下仅更新 catalog,由 Reaper 进程异步清理公共存储文件。
注意:Eon 模式下执行 DROP_PARTITIONS / DROP TABLE / TRUNCATE 后,公共存储文件由 Reaper 进程异步删除,非即时生效。如果 Reaper 队列异常堆积,可能导致死文件泄漏(有真实案例 67TB)。监控命令:
详见 Eon 公共存储文件不删除问题。
2.2 什么是存储裁剪(Storage Pruning)¶
分区数据被隔离到独立的存储容器中。查询计划阶段,优化器根据每个容器的分区列 min/max 值,只扫描包含相关数据的容器,大幅减少 I/O。
四种场景分析:
| 查询条件 | 存储裁剪效果 |
|---|---|
| 查询特定某周/月的数据 | 裁剪剩余 35 个分区的存储容器(以按月分区、3 年数据为例) |
| 查询一个季度(3 个月)的数据 | 仅使用 3 个分区的存储容器 |
| 谓词列与分区列无关(如 transaction_date 分区 + zip_code 过滤) | 无效裁剪,数据可能跨所有分区 |
| 谓词列与分区列相关(如 transaction_date 分区 + ship_date 过滤) | 有效裁剪,可缩小到相邻分区范围 |
2.3 如何确认查询利用了分区裁剪¶
Step 1:Profile 查询获取 transaction_id 和 statement_id
=> PROFILE SELECT * FROM <table_name> WHERE <column_name> BETWEEN 5 AND 7;
NOTICE 4788: Statement is being profiled
HINT: Select * from v_monitor.execution_engine_profiles where
transaction_id=54043195528458555 and statement_id=1;
Step 2:查询 QUERY_EVENTS 系统表
=> SELECT node_name, event_details FROM query_events
WHERE event_type = 'PARTITIONS_ELIMINATED'
AND transaction_id = 54043195528458555
AND statement_id = 1;
node_name | event_details
------------------+-----------------------------------------------------------
v_vmart_node0003 | Using only 1 stores out of 3 for projection public.tab_b0
v_vmart_node0002 | Using only 1 stores out of 5 for projection public.tab_b0
v_vmart_node0001 | Using only 1 stores out of 2 for projection public.tab_b0
(3 rows)
Using only 1 stores out of N 表示存储裁剪生效。
三、分区与 ROS 文件及 ROS 容器¶
3.1 ROS 文件 vs ROS 容器¶
- ROS 文件:执行 COPY DIRECT 时 Vertica 为每列创建一个 ROS 文件
- ROS 容器:ROS 文件的逻辑分组,由 COPY DIRECT 或 Tuple Mover 操作创建
3.2 每投影每节点的 ROS 容器数量问题¶
Vertica 将分区数据隔离到不同存储容器中,Tuple Mover 不会跨分区组合并数据(分层分区中的跨分区合并发生在同一分区组内部)。过多小分区可能导致存储容器数接近上限(1024),此时加载数据将失败并报 "Too many ROS containers"。
解决方案:
- 使用 ALTER TABLE ... PARTITION BY 改为粒度更粗的分区方案(配合
REORGANIZE或PARTITION_TABLE()) - 使用
MOVE_PARTITIONS_TO_TABLE将冷分区移出 - 使用分层分区
CALENDAR_HIERARCHY_DAY动态合并旧分区(见上文)
Eon 模式特别注意:Enterprise 模式中向非活跃分区加载数据仅涉及本地 I/O,代价可控。但在 Eon 模式下,每次向非活跃分区写入都会触发完整 S3 往返——下载旧 ROS 容器 → 本地合并 → 上传新容器 → Reaper 删除旧文件。因此 Eon 模式应严格避免向非活跃分区加载数据。如果必须回填历史分区,务必调大
ActivePartitionCount使目标分区保持活跃状态。详见 Eon 模式下 Vertica 的 ETL 最佳实践。
3.3 总 ROS 容器数对数据库的影响¶
总 ROS 文件数计算公式:(# storage containers) x (# columns per projection)
大量 ROS 文件会导致 catalog 膨胀(1M ROS 文件/节点约 3-4GB),消耗系统内存并拖慢以下操作:
- 系统表查询
- 数据库启动
- 数据库备份
- 故障恢复
Use Case 1:大型 Catalog
1,000 张表、每表 2 个投影、平均 50 列、50 ROS 容器/节点 → 约 500 万 ROS 文件/节点
若 300 张表按天分区(365 天),则增加约 1,090 万 ROS 文件。建议:
- 仅少量大表按天分区,其余按周/月分区
- 合并相似表(避免从 OLTP 继承的按地理位置拆分设计)
Use Case 2:ROS 容器过多
100 张表、每表 2 投影、50 列、50 ROS 容器 → 50 万 ROS 文件
若 10 张表按天分区保留 3 年:1 表 x (365 x 3) = 1095 个非活跃分区 ROS 容器,超过 1024 上限,导致新数据无法加载。
若改为按周分区:1 表 x (52 x 3) = 156 个非活跃分区 ROS 容器,大幅降低容器数。
四、重分区与重组¶
4.1 为已有表分区或修改分区表达式¶
执行后,已有存储容器的分区键信息立即清除,需要使用 REORGANIZE 关键字或 PARTITION_TABLE() 函数按新表达式重建。
注意: 节点宕机时不要执行 ALTER TABLE PARTITION。
完整语法:
ALTER TABLE table-name PARTITION BY partition-expression [ GROUP BY group-expression ] [ REORGANIZE ];
4.2 REORGANIZE 的作用¶
该操作:
- 删除现有分区键
- 按新表达式重分区
- 重组织表数据
REORGANIZE 是 Tuple Mover 的变体操作,在后台分批读取数据,按新分区方案写入 ROS 容器并添加分区键,减少对数据库运行性能的影响。
PARTITION_TABLE() 函数与 ALTER TABLE ... REORGANIZE 行为完全等价——两者都会拆分分区键不符合新分区子句的 ROS 容器,等待 Tuple Mover 下一次 mergeout 时合并到正确的容器中:
4.3 延迟 REORGANIZE 的后果¶
延迟后会导致:
- 无法在已修改分区表达式但未重组(reorganized)的表上执行分区管理函数
- 无分区键的 ROS 容器不参与 Tuple Mover mergeout,可能导致 ROS pushback
应尽快执行 REORGANIZE,并监控其进度直到完成。
4.4 监控 REORGANIZE 进度¶
查询以下系统表:
- VS_TUPLE_MOVER_OPERATIONS
- PARTITION_STATUS
- PARTITION_REORGANIZE_ERRORS
查询 CATALOG_EVENTS 表查看表的分区历史。
4.5 移除分区¶
移除后 Vertica 将该表视为普通非分区表,使用 strata 算法合并 ROS 容器。
五、分区数量限制¶
5.1 单表分区数量上限¶
无显式分区数量限制,但 Vertica 限制每投影每节点最多 1024 个 ROS 容器,因此实际分区上限约为 1024。Vertica 也阻止单个 COPY DIRECT 语句加载超过 1024 个分区。
超过 365 个分区(约上限 1/3)时,应密切监控以下指标:
- ROS 容器每投影计数
- Tuple Mover mergeout 操作
最佳实践:未分组分区数控制在 10-20 个,不超过 50 个。此范围与大多数业务需求兼容。可通过分区组(GROUP BY)或分层分区(CALENDAR_HIERARCHY_DAY)在不牺牲裁剪精度的前提下大幅降低 ROS 容器数。
必要时考虑:
- 重新设计分区方案(如从按天改为按月/按周)
- 使用分层分区
CALENDAR_HIERARCHY_DAY自动合并旧分区 - 将不常查询的分区移出到归档表
5.2 查看分区存储分布(DUMP 函数)¶
除查询 PARTITIONS 系统表外,还提供三个级别的分区键查看函数:
-- 查看所有投影的分区键分布
=> SELECT DUMP_PARTITION_KEYS();
-- 查看指定表所有投影的分区键
=> SELECT DUMP_TABLE_PARTITION_KEYS('trade');
-- 查看指定投影的分区键
=> SELECT DUMP_PROJECTION_PARTITION_KEYS('trade_super');
输出显示每个节点的每个 ROS 容器中存储了哪些分区键,便于诊断分区分布不均问题。
5.3 如何检查数据库 catalog 大小¶
=> SELECT node_name, MAX(ts) AS ts, MAX(catalog_size_in_MB) AS catalog_size_in_MB
FROM (
SELECT node_name,
TRUNC((dc_allocation_pool_statistics_by_second."time")::TIMESTAMP, 'SS') AS ts,
SUM((dc_allocation_pool_statistics_by_second.total_memory_max_value
- dc_allocation_pool_statistics_by_second.free_memory_min_value))/(1024*1024)
AS catalog_size_in_MB
FROM dc_allocation_pool_statistics_by_second
GROUP BY 1, TRUNC((dc_allocation_pool_statistics_by_second."time")::TIMESTAMP, 'SS')
) subquery_1
GROUP BY 1 ORDER BY 1 LIMIT 50;
5.4 如何检查分区是否导致 catalog 过大¶
=> SELECT s.node_name, p.table_schema, s.projection_name,
COUNT(DISTINCT s.storage_oid) storage_container_count,
COUNT(DISTINCT partition_key) partition_count,
COUNT(r.rosid) ros_file_count
FROM storage_containers s
LEFT OUTER JOIN PARTITIONS p ON s.storage_oid = p.ros_id
JOIN vs_ros r ON r.delid = s.storage_oid
GROUP BY 1,2,3 ORDER BY 4 DESC LIMIT 50;
该查询返回 ROS 容器数最多的前 50 个投影。如果结果中的表分区数很高,应考虑修改分区方案以减少分区数量。
常见操作命令速查¶
| 操作 | SQL |
|---|---|
| 创建分区表 | CREATE TABLE ... PARTITION BY EXTRACT(year FROM col) |
| 创建带分组的表 | CREATE TABLE ... PARTITION BY col::DATE GROUP BY CALENDAR_HIERARCHY_DAY(col::DATE, 2, 2) |
| 删除分区 | SELECT DROP_PARTITIONS('table', 2014, 2014) |
| 删除跨组分区 | SELECT DROP_PARTITIONS('table', 'min-key', 'max-key', 'true')(force_split) |
| 移动分区到归档 | SELECT MOVE_PARTITIONS_TO_TABLE('trade', 2008, 2008, 'trade_archive') |
| 复制分区到目标表 | SELECT COPY_PARTITIONS_TO_TABLE('trade', 2008, 2008, 'trade_copy') |
| 交换分区 | SELECT SWAP_PARTITIONS_BETWEEN_TABLES('staging', 2008, 2009, 'target') |
| 查看分区信息 | SELECT * FROM partitions WHERE table_name = 'trade' |
| 查看分区键分布 | SELECT DUMP_TABLE_PARTITION_KEYS('trade') |
| 查看所有分区键 | SELECT DUMP_PARTITION_KEYS() |
| 修改分区 | ALTER TABLE t PARTITION BY EXTRACT(month FROM tdate) REORGANIZE |
| 修改分区(等价) | SELECT PARTITION_TABLE('t') |
| 修改分区+分组 | ALTER TABLE t PARTITION BY col::DATE GROUP BY CALENDAR_HIERARCHY_DAY(col::DATE, 2, 2) REORGANIZE |
| 按表设活跃分区数 | ALTER TABLE t SET ACTIVEPARTITIONCOUNT 3 |
| 移除分区 | ALTER TABLE t REMOVE PARTITIONING |
| 查询活跃分区 | SELECT DISTINCT partition_key FROM strata WHERE ... |
| 检查分区裁剪 | 查询 query_events WHERE event_type = 'PARTITIONS_ELIMINATED' |
Enterprise vs Eon 分区行为速查¶
原文仅覆盖 Enterprise 模式。以下补充 Eon 模式的关键差异。
| 维度 | Enterprise 模式 | Eon 模式 |
|---|---|---|
| ROS 容器位置 | 节点本地磁盘 | 公共存储(S3/HDFS),本地仅 Depot 缓存 |
| ROS Pushback(1024 上限) | 本地 ROS 容器达到上限后加载失败 | ROS 容器位于公共存储,Depot 仅缓存;1024 上限同样适用 |
| 分区管理函数 | MOVE/SWAP/COPY 纯 catalog;DROP 同步删除本地文件 | MOVE/SWAP/COPY 纯 catalog;DROP 仅更新 catalog,Reaper 异步删 S3 |
| 写入非活跃分区 | 本地 I/O,代价可控 | 应避免,每次 S3 下载→合并→上传 |
| ActivePartitionCount | 影响 mergeout 效率 | 更为关键,直接影响 S3 成本 |
| 冷热分层 | MOVE + 不同表空间 | SET_OBJECT_STORAGE_POLICY + 不同 S3 bucket |
| 数据清理 | 同步删除本地文件 | 异步(Reaper 队列),需监控死文件 |
| 子集群弹性伸缩 | 需 rebalance | 无需 rebalance,分钟级 |
详细说明参见 Eon 模式下 Vertica 的 ETL 最佳实践 与 Eon 公共存储文件不删除问题。
扩展阅读¶
- Vertica 表分区策略选择指南 — 分区决策框架与 ROS 容器数控制
- Tuple Mover 最佳实践完全指南 — TM mergeout 机制与调优
- Vertica 冷热数据管理与成本优化 — 冷热分层与存储策略
- 分区范围投影 — 利用分区键优化投影设计