Vertica 表分区策略选择指南¶
作者:JiangChong | 发布时间:2026-05-27
适用场景:当遇到以下任一情况时,本文可提供系统性的决策框架和操作指南——开发团队不确定某张表是否应该分区、现有的分区方案导致 ROS 容器数逼近上限、需要从非分区表迁移为分区表以支持数据生命周期管理。
关联文章¶
- 理解 Vertica 的分区 — 分区基础概念、活动分区、存储裁剪原理全面讲解
- Vertica 分区表常见问题 — 分区 FAQ,含操作命令速查表
- 分区范围投影 (Partition Range Projections) — 按分区键范围创建投影以降低 ROS 容器数
- Vertica 数据删除最佳实践 — DROP PARTITION 与分区删除策略
- Tuple Mover 最佳实践完全指南 — Mergeout 与分区交互机制
- ROS Pushback 故障排查 — ROS 容器数超限的排查和修复
理解全文脉络¶
本文按「判断 → 设计 → 实施 → 优化」的逻辑组织。第 1 节阐释分区的底层工作原理和成本模型,帮你建立直觉;第 2-3 节提供从监控到定位的完整排查路径,告诉你如何评估当前分区策略的健康状况;第 4 节给出从快速修复到根本治理的解决方案,包括从非分区表迁移到分区表的完整步骤;第 5-6 节通过案例和演练串联所有知识点;第 7 节是可复用的 SQL 工具箱。如果你只想知道「某张表该不该分区」,可以直接跳到第 3.1 节。
1. 原理理解¶
1.1 分区在 Vertica 中到底做了什么¶
Vertica 的分区不是把数据放到不同文件系统中的目录(不像 PostgreSQL 的表分区那样创建子表),而是在逻辑层面将同一张表不同分区的数据分配到不同的 ROS 容器(ROS Container)中。分区键的值决定了每一行数据进入哪个 ROS 容器。
可以这样理解:非分区表就像一个巨大的文件柜,所有文件都塞在一起;分区表则像把文件柜按年份贴上标签——2008 年的文件在一个抽屉,2009 年的在另一个抽屉。标签(分区键)让你能直接找到想要的抽屉,而不需要翻遍整个柜子。
Vertica 分区有两个核心优势:
- 存储裁剪(Storage Pruning):查询优化器读取每个 ROS 容器的分区键 min/max 值,在查询规划阶段直接跳过不相关的容器。如果查询谓词包含了分区键,Vertica 可以跳过绝大部分数据块,大幅减少 I/O。
- 数据生命周期管理:分区让你能以分区为单位执行 DROP_PARTITIONS(秒级完成,纯 catalog 操作;旧函数
DROP_PARTITION自 Vertica 9.0 起废弃)、MOVE_PARTITIONS_TO_TABLE(将冷数据移到归档表)、或通过存储策略将冷分区迁到廉价存储。
1.2 分区与分段是两个正交的概念¶
这是初学者最容易混淆的地方。分段(Segmentation)决定数据在集群节点间如何分布,分区(Partitioning)决定在每个节点内部数据如何组织。
| 维度 | 分段(Segmentation) | 分区(Partitioning) |
|---|---|---|
| 作用域 | 跨节点 | 单节点内部 |
| 目的 | 利用 MPP 并行计算 | 减少 I/O,简化数据管理 |
| 典型表达式 | SEGMENTED BY HASH(id) ALL NODES |
PARTITION BY EXTRACT(year FROM date_col) |
| 对查询的影响 | 决定并行度 | 决定存储裁剪效率 |
一张大表通常同时需要分段和分区。例如,按 HASH(trans_id) 分段保证数据均匀分布到所有节点,按 EXTRACT(month FROM trans_date) 分区保证查询能裁剪到目标月份。
1.3 活跃分区与非活跃分区的关键区别¶
最后创建的分区称为活跃分区(Active Partition),默认为 1 个,由配置参数 ActivePartitionCount 控制。活跃分区和非活跃分区在 Tuple Mover 的合并策略上存在本质差异:
- 非活跃分区:Tuple Mover 会将该分区内所有 ROS 容器合并为1 个 ROS 容器(每个节点每个投影)。这就是为什么按天分区保留 3 年会产生
365 × 3 = 1095个 ROS 容器——每个非活跃分区的每一天都会变成 1 个独立的 ROS 容器。 - 活跃分区:Tuple Mover 使用 strata 算法进行分层合并——不同大小的 ROS 容器被分组到不同的 stratum,按比例合并。活跃分区的 ROS 容器数量不固定,取决于加载频率和数据量。
这个区别直接决定了分区粒度的取舍:分区太细(如按天),非活跃分区的 ROS 容器数量会线性增长,逼近 1024 的上限;分区太粗(如按年),虽然 ROS 容器数很少,但存储裁剪的优势也大幅减弱。
1.4 分区粒度的成本模型¶
每个节点每个投影最多 1024 个 ROS 容器。总 ROS 容器数的构成如下:
解释:每个非活跃分区经过 mergeout 后固定贡献 1 个 ROS 容器(无论分区内有多少行数据),而活跃分区使用 strata 分层算法,可能产生 1~50+ 个 ROS 容器(取决于数据加载频率)。因此分区数越多,ROS 容器数就线性增长——这也是为什么按天分区很危险。
不同分区粒度的非活跃分区数对比(以保留 3 年数据为例):
| 分区粒度 | 3 年分区数 | 非活跃 ROS 容器数 | 风险等级 |
|---|---|---|---|
| 按天 | ~1095 | ~1094 | 🔴 已超过 1024 上限 |
| 按周 | ~156 | ~155 | 🟢 安全 |
| 按月 | ~36 | ~35 | 🟢 安全 |
| 按季度 | ~12 | ~11 | 🟢 安全 |
| 按年 | 3 | 2 | 🟢 安全 |
超过 365 个分区(约上限的 1/3)时,就应该密切监控 ROS 容器数。当接近 1024 时,新数据加载会因为 ROS Pushback 而失败。
突破困境:分层分区(Hierarchical Partitioning) 按天分区裁剪精准但 ROS 容器爆炸,按月分区 ROS 安全但裁剪粗糙。
CALENDAR_HIERARCHY_DAY用分层结构同时解决两个问题——近期数据按天裁剪,旧数据按月/按年合并。详细用法见第 4.4 节。
1.5 所有可能的来源/原因总结¶
| 场景 | 是否需要分区 | 原因 |
|---|---|---|
| 大事实表(> 1 亿行),需要按时间维度删除历史数据 | ✅ 强烈建议 | DROP_PARTITIONS 秒级完成,比 DELETE 高效数个量级 |
| 大事实表,查询总带日期范围过滤 | ✅ 建议 | 存储裁剪可跳过 90%+ 的 ROS 容器 |
| 中大型表,查询不按时间过滤 | ⚠️ 谨慎 | 分区不会加速查询,反而增加 ROS 容器数 |
| 小表(< 100 万行) | ❌ 不建议 | ROS 容器开销远大于裁剪收益 |
| 维度表 | ❌ 不建议 | 表本身很小,分区只会增加目录大小 |
| 临时/Staging 表 | ❌ 不需要 | 生命周期短,TRUNCATE 即可 |
2. 系统级监控(从宏观入手)¶
2.1 检查整体分区使用情况¶
首先从全局视角了解数据库中分区表的数量和分布:
-- 两层聚合:内层按 (schema, 表, 分区键) 消除 buddy + 节点重复,外层按 schema 汇总
SELECT table_schema,
COUNT(DISTINCT anchor_table_name) AS partitioned_tables,
COUNT(*) AS total_partitions, -- 内层已按表区分,COUNT(*) = 各表分区数之和,不跨表去重
SUM(adj_row_count) AS total_rows,
SUM(adj_size_bytes) / (1024*1024*1024) AS total_size_gb
FROM (
SELECT p.table_schema, pr.anchor_table_name, p.partition_key,
CASE WHEN BOOL_AND(pr.is_segmented)
THEN SUM(p.ros_row_count) -- 分段表:跨节点汇总 = 分区总行数
ELSE SUM(p.ros_row_count) / COUNT(DISTINCT p.node_name) -- 非分段表:除以节点数
END AS adj_row_count,
CASE WHEN BOOL_AND(pr.is_segmented)
THEN SUM(p.ros_size_bytes)
ELSE SUM(p.ros_size_bytes) / COUNT(DISTINCT p.node_name)
END AS adj_size_bytes
FROM v_monitor.partitions p
JOIN (
-- 每表只保留 MIN(projection_id) 对应的一个投影,消除 buddy + 避免混合类型导致 BOOL_AND 失效
-- DISTINCT 必须:非分段投影在 v_catalog.projections 中每节点一行,同一 projection_id 重复
SELECT DISTINCT projection_id, anchor_table_name, is_segmented
FROM v_catalog.projections
WHERE projection_id IN (
SELECT MIN(projection_id) FROM v_catalog.projections
GROUP BY projection_schema, anchor_table_name
)
) pr ON p.projection_id = pr.projection_id
GROUP BY p.table_schema, pr.anchor_table_name, p.partition_key
) sub
GROUP BY table_schema
ORDER BY total_size_gb DESC;
如何解读结果:
- 关注
total_partitions超过 365 的 schema/表:这些表存在 ROS 容器数增长的长期风险。 total_rows和total_size_gb已通过两层聚合同时消除重复:①GROUP BY projection_schema, anchor_table_name每表只保留一个投影,彻底杜绝 buddy 和混合投影类型干扰BOOL_AND;②CASE WHEN is_segmented对非分段表除以节点数。分段/非分段表均准确。- 如果
total_size_gb很大但partitioned_tables很少,说明可能存在大表未分区的情况——这正是第 3.1 节要排查的。 - 对比各 schema 的分区表与非分区表比例,识别分区覆盖率偏低的 schema。
2.2 按投影查看 ROS 容器分布¶
这是最有价值的监控查询。它直接展示哪些表的 ROS 容器数已接近 1024 上限:
SELECT
s.node_name,
COALESCE(p.table_schema, s.schema_name) AS table_schema,
s.projection_name,
COUNT(DISTINCT s.storage_oid) AS storage_container_count,
COUNT(DISTINCT p.partition_key) AS partition_count,
COUNT(r.rosid) AS ros_file_count
FROM storage_containers s
LEFT JOIN v_monitor.partitions p
ON s.storage_oid = p.ros_id
JOIN vs_ros r
ON r.delid = s.storage_oid
WHERE s.node_name = (SELECT local_node_name())
GROUP BY 1, 2, 3
ORDER BY storage_container_count DESC
LIMIT 30;
如何解读结果:
| ROS 容器数 | 风险等级 | 行动建议 |
|---|---|---|
| < 300 | 🟢 健康 | 无需干预 |
| 300-500 | 🟡 关注 | 每月检查一次趋势 |
| 500-800 | 🟠 警告 | 评估分区粒度是否需要调整 |
| 800-1024 | 🔴 危险 | 立即制定降容方案,随时可能触发 ROS Pushback |
如果 partition_count 与 storage_container_count 接近(比例 > 0.8),说明 ROS 容器数主要由分区数决定——这是分区粒度过细的典型特征。
2.3 检查分区重组状态¶
当 ALTER TABLE ... PARTITION BY ... 执行后(不带 REORGANIZE 关键字),已有数据的分区键信息会被清除,但 Tuple Mover 需要在后台重新组织数据才能重建分区键。在此期间,这些 ROS 容器不参与 Mergeout,可能导致额外的 ROS 累积。
SELECT
table_schema,
table_name,
projection_name,
partition_reorganize_percent
FROM v_monitor.partition_status
WHERE partition_reorganize_percent < 100
ORDER BY partition_reorganize_percent ASC;
partition_reorganize_percent 表示该投影中已按当前分区表达式完成分区的数据比例。值小于 100 表示仍有数据在等待重组。如果这个值长时间停滞不增长,应检查 PARTITION_REORGANIZE_ERRORS 表排查错误。
2.4 识别未分区的大表¶
要找到数据库中哪些大表尚未分区,需要结合系统表的大小信息:
WITH proj_rows AS (
SELECT
ps.anchor_table_id,
ps.projection_id,
CASE WHEN pr.is_segmented
THEN SUM(ps.row_count) -- segmented: 跨节点汇总 = 该投影总行数
ELSE MAX(ps.row_count) -- unsegmented: 每节点全量副本,取任意一个即可
END AS proj_total_rows
FROM v_monitor.projection_storage ps
JOIN v_catalog.projections pr ON ps.projection_id = pr.projection_id
GROUP BY ps.anchor_table_id, ps.projection_id, pr.is_segmented
)
SELECT
t.table_schema,
t.table_name,
SUM(ps.used_bytes) / (1024*1024*1024) AS total_size_gb,
AVG(pr.proj_total_rows)::INT AS total_rows,
t.partition_expression
FROM v_catalog.tables t
JOIN v_monitor.projection_storage ps ON t.table_id = ps.anchor_table_id
JOIN proj_rows pr ON t.table_id = pr.anchor_table_id AND ps.projection_id = pr.projection_id
WHERE t.partition_expression = ''
GROUP BY 1, 2, 5
HAVING SUM(ps.used_bytes) > 50 * 1024*1024*1024 -- 总存储 > 50 GB
ORDER BY total_size_gb DESC;
计算口径说明: -
total_size_gb:SUM(used_bytes)不加除——包含所有 buddy 投影、所有节点的存储总和,反映实际磁盘占用 -total_rows:CTE 在投影级别根据is_segmented区分计算方式,再AVG跨投影消除 buddy 重复——分段表和非分段表均准确 - 分段表:每节点存数据分片,跨节点SUM= 该投影总行数 - 非分段表:每节点存全量副本,MAX取任意节点 = 全量行数
如何解读结果:此查询筛选总存储 > 50 GB 的未分区表(约 25 GB 逻辑数据)。这是一个初筛门槛,不是分区标准。 结果中的表需按第 3.1 节的决策树逐一评估——有时间维度删除需求或日期范围查询优先分区,单纯数据量大且无以上需求的,参考 100 GB 逻辑数据阈值再决定。
2.5 检查 Catalog 大小¶
分区过多导致的 ROS 容器膨胀最终会体现在 catalog 大小上。catalog 超过 15 GB 就需要关注。两种检查方式:
方式一:数据库内查询(推荐,无需登录服务器) — 出自 理解 Vertica 的分区
SELECT
node_name,
MAX(catalog_size_in_MB) AS catalog_size_in_MB
FROM (
SELECT
node_name,
SUM((total_memory_max_value - free_memory_min_value)) / (1024*1024) AS catalog_size_in_MB
FROM dc_allocation_pool_statistics_by_second
GROUP BY 1, TRUNC(("time")::TIMESTAMP, 'SS')
) foo
GROUP BY 1
ORDER BY 1;
dc_allocation_pool_statistics_by_second.total_memory_max_value - free_memory_min_value 是 catalog 内存池的使用量,等同于 catalog 占用的内存大小。超过 15 GB(约 15,000 MB)就需要关注。
方式二:Linux 层面直接检查
注意:catalog 的实际路径因安装配置而异。Enterprise 模式通常在
/vertica/data/<dbname>/v_<dbname>_node*_catalog/Catalog/,Eon 模式下 catalog 路径需先通过v_monitor.disk_storage查询获取。
3. 逐步定位根因(从宏观到微观)¶
3.1 Step 1:判断某张表是否需要分区¶
这是最基础的决策问题。按以下决策树判断:
决策逻辑:
-
表是维度表还是事实表?
- 维度表(按业务角色判断,通常是 lookup/reference 表)→ 不需要分区,分区只会增加 ROS 容器数
- 事实表 → 继续判断
-
是否有按时间维度删除历史数据的需求?
- 是 → 强烈建议分区
- 否 → 继续判断
-
查询是否经常带日期/时间范围的谓词?
- 是 → 建议分区(存储裁剪收益)
- 否 → 继续判断
-
表大小是否超过 100 GB?
- 是 → 可考虑分区以优化数据管理
- 否 → 分区带来的 ROS 容器开销可能超过收益
关键原则:只对大事实表分区,不要对小表或维度表分区。 对小表分区会增加大量 ROS 容器,导致 catalog 膨胀,查询性能反而下降。详见 理解 Vertica 的分区。
3.2 Step 2:评估当前分区方案是否合理¶
如果表已分区,需要评估分区粒度是否与数据增长和查询模式匹配。
检查当前分区粒度:
SELECT
p.table_schema,
pr.anchor_table_name AS table_name,
COUNT(DISTINCT p.partition_key) AS partition_count,
MIN(p.partition_key) AS oldest_partition,
MAX(p.partition_key) AS newest_partition
FROM v_monitor.partitions p
JOIN v_catalog.projections pr
ON p.projection_id = pr.projection_id
WHERE p.table_schema = 'public' -- 替换为目标 schema
AND pr.anchor_table_name = 'your_table' -- 替换为目标表名
AND p.node_name = (SELECT local_node_name())
GROUP BY 1, 2;
评估标准:
| 指标 | 健康值 | 警告值 | 行动 |
|---|---|---|---|
partition_count |
< 365 | > 500 | 考虑改粗分区粒度 |
| 分区时间跨度 | < 3 年 | > 5 年 | 考虑归档旧分区 |
| 每分区平均行数 | > 100 万 | < 10 万 | 分区粒度可能太细 |
3.3 Step 3:检查存储裁剪是否生效¶
分区的主要查询性能价值在于存储裁剪。如果查询没利用到分区裁剪,分区的收益就只剩数据管理。以下查询出自 理解 Vertica 的分区:
SELECT
node_name,
event_details,
transaction_id,
statement_id
FROM v_monitor.query_events
WHERE event_type = 'PARTITIONS_ELIMINATED'
AND event_timestamp > NOW() - INTERVAL '1 day'
ORDER BY event_timestamp DESC
LIMIT 20;
如何解读结果:
Using only 1 stores out of 10 for projection xxx表示 10 个存储容器中只用了 1 个,裁剪率 90%——这是优秀的结果。- 如果某张分区表的查询在
QUERY_EVENTS中没有PARTITIONS_ELIMINATED事件,说明查询谓词和分区键不匹配,存储裁剪完全没生效。
诊断存储裁剪失效的方法:
PROFILE SELECT * FROM your_table WHERE date_col BETWEEN '2025-01-01' AND '2025-01-31';
-- 注意 PROFILE 输出的 transaction_id 和 statement_id
然后查询:
SELECT node_name, event_details
FROM v_monitor.query_events
WHERE event_type = 'PARTITIONS_ELIMINATED'
AND transaction_id = :t_id -- 替换为 PROFILE 输出的 transaction_id
AND statement_id = :s_id; -- 替换为 PROFILE 输出的 statement_id
3.4 Step 4:排查 ROS Pushback 风险¶
ROS Pushback 是新数据无法加载的直接信号。它发生在某投影的 ROS 容器数达到 1024 时。如果你已经到了这一步,说明前面的监控有盲区,需要立即处理。
检查最接近上限的投影:
SELECT
s.node_name,
s.projection_name,
COUNT(DISTINCT s.storage_oid) AS ros_count,
1024 - COUNT(DISTINCT s.storage_oid) AS remaining_slots
FROM v_monitor.storage_containers s
GROUP BY 1, 2
HAVING COUNT(DISTINCT s.storage_oid) > 700
ORDER BY remaining_slots ASC;
如果 remaining_slots < 100,必须立即行动:
- 评估是否可以对旧分区执行 MOVE_PARTITIONS_TO_TABLE 释放槽位
- 评估是否可以将按天分区改为按月或使用分层分区(见 4.4)——既保留天级裁剪,又不爆 ROS 容器
- 如果数据不再需要,执行 DROP_PARTITIONS 直接删除
3.5 Step 5:检查重组操作的完成情况¶
如果最近执行了 ALTER TABLE ... PARTITION BY ... REORGANIZE,或者修改了分区表达式,需要确认重组是否正常运行。
-- 检查未完成的重组
SELECT
table_schema,
table_name,
projection_name,
partition_reorganize_percent
FROM v_monitor.partition_status
WHERE partition_reorganize_percent > 0
AND partition_reorganize_percent < 100;
-- 检查重组过程中的错误
SELECT
node_name,
table_name,
projection_name,
message,
hint
FROM v_monitor.partition_reorganize_errors
ORDER BY session_id DESC;
4. 解决方案(从快速见效到根本治理)¶
4.1 立即措施:为已有非分区大表添加分区¶
如果通过第 3.1 节的决策树确认某张表需要分区(优先看时间删除需求和查询谓词模式,而非单一大小数值),应尽快执行分区操作。
完整操作流程:
-- 步骤 1:分析表中可用的时间列
-- 优先选择 DATE/TIMESTAMP 类型、NOT NULL、且经常作为查询谓词的列
SELECT column_name, data_type, is_nullable
FROM columns
WHERE table_schema = 'public' -- 替换为实际 schema
AND table_name = 'your_table' -- 替换为实际表名
AND data_type IN ('date', 'timestamp', 'timestamptz', 'int', 'integer')
ORDER BY ordinal_position;
-- 步骤 2:按日期列添加分区,并按月 GROUP BY 以减少 ROS 容器数
ALTER TABLE your_table
PARTITION BY date_col::DATE
GROUP BY DATE_TRUNC('month', date_col::DATE)
REORGANIZE;
-- ALTER TABLE ... PARTITION BY ... REORGANIZE 是一步完成的原子操作:
-- 先设置分区表达式,然后立即启动后台重组。
-- 注意:此操作需要排他锁,建议在维护窗口执行。
⚠️ TIMESTAMPTZ 陷阱:如果分区列是
TIMESTAMPTZ类型,直接用::DATE会报错ROLLBACK 2552: Cannot use meta function or non-deterministic function in PARTITION BY expression。原因是TIMESTAMPTZ::DATE依赖会话时区,被 Vertica 归类为 non-immutable。必须用AT TIME ZONE 'UTC'锚定时区:
为什么用 GROUP BY DATE_TRUNC('month', ...) 而不是直接按天分区? GROUP BY 子句指定了 Tuple Mover 合并 ROS 容器时的分组粒度。即使分区表达式按 date_col::DATE 区分每个日期,GROUP BY DATE_TRUNC('month', ...) 会告诉 Tuple Mover 将同月的不同日期的 ROS 容器合并在一起,大幅减少实际产生的 ROS 容器数。
4.2 立即措施:处理 ROS 容器数逼近上限¶
当 ROS 容器数超过 800 时,需要紧急降容(否则新数据加载会失败):
方案 A:归档旧分区(推荐,不丢数据)
-- 创建归档表(结构与原表相同,含 projection);同样需要 schema 前缀
CREATE TABLE schema.your_table_archive
LIKE schema.your_table INCLUDING PROJECTIONS;
-- 将旧分区的数据搬到归档表
-- MOVE_PARTITIONS_TO_TABLE 是纯 catalog 操作(在分区内所有 ROS 容器已合并的前提下),速度极快
SELECT MOVE_PARTITIONS_TO_TABLE(
'schema.your_table', -- 必须带 schema 前缀,否则报错 table does not exist
'20230101', -- 起始分区键值(替换为实际值)
'20230630', -- 结束分区键值(替换为实际值)
'schema.your_table_archive'
);
MOVE_PARTITIONS_TO_TABLE 的原理:它不实际移动数据,而是修改 catalog 中的元数据,将 ROS 容器的所有权从原表转移到归档表,因此即使 TB 级分区也是秒级完成。
方案 B:调整分区粒度(彻底根治)
注意:这一操作需要重写所有 ROS 容器的分区键,对 TB 级表可能需要数小时甚至更久。务必在维护窗口执行,并监控 PARTITION_STATUS 获取进度。
4.3 短期优化:为分区表创建分区范围投影¶
如果不想改变分区粒度,但需要控制特定投影的 ROS 容器数,可以使用分区范围投影(Partition Range Projection)。这种投影只包含指定分区范围内的数据,详见 分区范围投影 (Partition Range Projections)。
-- 创建仅包含当年数据的投影(动态范围)
CREATE PROJECTION ytd_sales AS
SELECT * FROM sales
ORDER BY sale_date
ON PARTITION RANGE BETWEEN DATE_TRUNC('year', NOW())::DATE AND NULL;
SELECT REFRESH();
为什么这样有效? ON PARTITION RANGE 限制了该投影只存储符合范围条件的分区数据。以上示例中,ytd_sales 投影只包含从今年 1 月 1 日至今的数据,ROS 容器数大幅减少。对于只查询近期数据的报表,查询优化器会自动选择这个 ROS 容器更少的投影。
4.4 根治方案:分层分区(Hierarchical Partitioning)¶
第 1.4 节暴露了一个根本矛盾:按天分区裁剪精准但 ROS 容器数线性增长,按月分区安全但裁剪粗糙。分层分区通过 CALENDAR_HIERARCHY_DAY 同时兼顾两者:
-- 分区键按天,但 Tuple Mover 分层合并:
-- 近 2 个月保留天级 → 远 2-24 个月合并到月 → 2 年以上合并到年
ALTER TABLE your_table
PARTITION BY date_col::DATE
GROUP BY CALENDAR_HIERARCHY_DAY(date_col::DATE, 2, 2)
REORGANIZE;
CALENDAR_HIERARCHY_DAY(col, active_months, active_days) 的参数含义:
active_months(第一参数):多少个月内保留天级粒度(活跃期),超过后合并到月active_days(第二参数):活跃期内多少天保持独立 ROS 容器,超过后合并到月
以 CALENDAR_HIERARCHY_DAY(date_col, 2, 2) 为例,Vertica 会根据当前日期自动将分区组织为三层:
近 2 个月的数据 → 按天独立存储(裁剪精确到天)
2 个月 ~ 2 年前的数据 → 同月合并为一个 ROS 容器(裁剪精确到月)
2 年以前的数据 → 同年合并为一个 ROS 容器(裁剪精确到年)
实际验证:以上 DDL 执行后,partition_group_expression 被 Vertica 展开为:
CASE
WHEN datediff('year', date_col, now()) >= 2
THEN date_trunc('year', date_col)::date -- 2 年前 → 年级
WHEN datediff('month', date_col, now()) >= 2
THEN date_trunc('month', date_col)::date -- 2 个月前 → 月级
ELSE date_col -- 近期 → 天级
END
为什么分层分区优于简单改粗粒度:
| 方案 | 近期查询裁剪 | 远期 ROS 容器数 | 3 年总量 |
|---|---|---|---|
| 按天分区 | 天级 ✓ | 1094 个 | 1095 🔴 |
| 按月分区 | 月级(30 天) | 35 个 | 36 🟢 |
CALENDAR_HIERARCHY_DAY(, 2, 2) |
天级 ✓ | ~35 个(月+年级) | ~40 🟢 |
分层分区让近期数据享有天级裁剪,同时通过将旧数据分层合并,保持了接近按月分区的 ROS 容器总量。
注意:分层分区依赖 Tuple Mover 在后台按 hierarchy 规则合并,首次执行
REORGANIZE可能需要较长时间(与直接 ALTER TABLE 改粒度相当)。后续增量数据加载后会自动继承分层规则。
4.5 根本治理:分区键选择原则¶
选择分区键是分区设计中最关键的决策。以下是四个选择原则,按重要性排序:
原则 1:分区键必须匹配数据生命周期管理策略
如果你的保留策略是「保留 3 年,按月删除旧数据」,那么分区键必须是日期类型,粒度不应小于月。这是最核心的约束——如果分区键和数据保留策略不匹配,分区的最大优势(DROP_PARTITIONS)就完全失效。
原则 2:分区键应与最常见的查询谓词对齐
如果 80% 的查询都带 WHERE created_date BETWEEN ... AND ...,那么用 created_date 作为分区键可以让 80% 的查询都享受到存储裁剪。如果表同时有 created_date 和 updated_date 两个常用时间字段,优先选择:
- 数据加载时填充的字段(
created_date):数据按加载时间自然聚集 - 涉及数据删除策略的字段:支持 DROP_PARTITIONS 按此字段删除
原则 3:分区键的数据类型影响分区粒度控制
| 数据类型 | 分区表达式示例 | 典型粒度 | 适用场景 |
|---|---|---|---|
| DATE | date_col::DATE |
天 | 每天数据量 > 1 亿行 |
| DATE | DATE_TRUNC('month', date_col)::DATE |
月 | 每天数据量 < 1000 万行 |
| TIMESTAMP | date_col::DATE |
天 | 与 DATE 相同,::DATE 截断时间部分 |
| TIMESTAMPTZ | (date_col AT TIME ZONE 'UTC')::DATE |
天 | 必须用 AT TIME ZONE 'UTC' 锚定时区,否则报错 2552 |
| INTEGER(YYYYMMDD) | (date_int / 100)::INT |
月 | 日期以整数存储的系统 |
原则 4:分区键列必须有 NOT NULL 约束
Vertica 要求分区键不能包含 NULL 值,且分区表达式必须使用 immutable 函数(不依赖会话状态、时区等可变因素)。如果分区列可能为 NULL,需在创建表时添加 NOT NULL 约束或在分区表达式中处理 NULL,否则数据加载会失败。TIMESTAMPTZ 列需要特别注意——直接用 ::DATE 或 DATE_TRUNC 会因时区依赖被拒绝(错误 2552),必须用 AT TIME ZONE 'UTC' 锚定(详见上表)。
4.6 从非分区表迁移到分区表的完整操作指南¶
这是实践中最常见的场景。以下是经过验证的完整操作步骤:
方案 A:直接 ALTER TABLE(适用于 < 500 GB 的表)
-- 第 1 步:确认目标表大小(is_segmented 区分计算,分段/非分段表均准确;total_rows 已消除 buddy 重复)
WITH proj_rows AS (
SELECT
ps.projection_id,
CASE WHEN pr.is_segmented
THEN SUM(ps.row_count) -- 分段表:跨节点汇总 = 该投影总行数
ELSE MAX(ps.row_count) -- 非分段表:每节点全量副本,取任一节点
END AS proj_total_rows
FROM v_monitor.projection_storage ps
JOIN v_catalog.projections pr ON ps.projection_id = pr.projection_id
WHERE ps.anchor_table_name = 'your_table'
GROUP BY ps.projection_id, pr.is_segmented
)
SELECT
SUM(ps.used_bytes) / (1024*1024*1024) AS size_gb,
AVG(pr.proj_total_rows)::INT AS total_rows
FROM v_monitor.projection_storage ps
JOIN proj_rows pr ON ps.projection_id = pr.projection_id
WHERE ps.anchor_table_name = 'your_table';
-- 第 2 步:执行分区 + 重组(维护窗口内)
ALTER TABLE your_table
PARTITION BY date_col::DATE
GROUP BY DATE_TRUNC('month', date_col::DATE)
REORGANIZE;
-- 第 3 步:监控重组进度
SELECT
table_name,
projection_name,
partition_reorganize_percent
FROM v_monitor.partition_status
WHERE table_name = 'your_table'
ORDER BY partition_reorganize_percent ASC;
方案 B:创建新分区表 + 数据迁移(适用于 > 500 GB 的表)
对于超大表,直接 ALTER TABLE ... REORGANIZE 可能需要数天。更可控的方案是创建新表然后切换:
-- 第 1 步:创建结构相同的新表,加上分区定义
CREATE TABLE your_table_new
LIKE your_table INCLUDING PROJECTIONS;
ALTER TABLE your_table_new
PARTITION BY date_col::DATE
GROUP BY DATE_TRUNC('month', date_col::DATE);
-- 第 2 步:分批迁移数据(可在业务低峰期多次执行)
INSERT /*+ DIRECT */ INTO your_table_new
SELECT * FROM your_table
WHERE date_col BETWEEN '2024-01-01' AND '2024-01-31';
-- 逐月重复,直到全部迁移完成
-- 第 3 步:切换表名
ALTER TABLE your_table RENAME TO your_table_old;
ALTER TABLE your_table_new RENAME TO your_table;
-- 第 4 步:验证后删除旧表
DROP TABLE your_table_old;
为什么大表推荐分批迁移? ALTER TABLE ... REORGANIZE 虽然自动,但对 TB 级表运行时可能持续数天,且一旦开始无法暂停。分批 INSERT /*+ DIRECT */ 允许你控制每次迁移的数据量和时间窗口,出现问题可随时中断。
5. 深入案例¶
5.1 虚构案例:非分区大表查询性能持续恶化¶
📝 虚构案例
场景描述:某电商平台的 order_detail 表存储 3 年订单明细数据,总计 15 亿行、约 800 GB。表未分区,查询始终扫描全表。随着数据累积,日报查询从 3 秒恶化到 45 秒。
诊断过程:
-- 1. 确认表大小和分区状态
-- CTE 根据 is_segmented 区分计算,避免 buddy 投影和节点重复计数
WITH proj_rows AS (
SELECT
ps.projection_id,
CASE WHEN pr.is_segmented THEN SUM(ps.row_count) ELSE MAX(ps.row_count) END AS total_rows
FROM v_monitor.projection_storage ps
JOIN v_catalog.projections pr ON ps.projection_id = pr.projection_id
WHERE ps.anchor_table_name = 'order_detail'
GROUP BY ps.projection_id, pr.is_segmented
)
SELECT
ps.anchor_table_schema AS table_schema,
ps.anchor_table_name AS table_name,
t.partition_expression,
AVG(pr.total_rows) / 1000000000.0 AS rows_billion,
SUM(ps.used_bytes) / (1024*1024*1024) AS size_gb
FROM v_monitor.projection_storage ps
JOIN v_catalog.tables t ON ps.anchor_table_id = t.table_id
JOIN proj_rows pr ON ps.projection_id = pr.projection_id
WHERE ps.anchor_table_name = 'order_detail'
GROUP BY 1, 2, 3;
-- 输出:partition_expression = '' (空 = 未分区),rows = 1.5 billion, size = 800 GB
-- 2. 检查典型日报查询的存储裁剪情况
-- 先 PROFILE 慢查询
PROFILE SELECT region, SUM(amount)
FROM order_detail
WHERE order_date BETWEEN '2026-05-01' AND '2026-05-31'
GROUP BY region;
-- 在 QUERY_EVENTS 中搜索 PARTITIONS_ELIMINATED
-- 结果:无此事件 → 存储裁剪完全未生效,每次查询扫描全部 15 亿行
根因分析:表未分区,查询优化器无法跳过任何存储容器。即使 WHERE order_date 只查询 5 月份数据,Vertica 也必须读取所有 ROS 容器并逐行过滤。
修复方案:按订单日期添加分区
ALTER TABLE order_detail
PARTITION BY order_date::DATE
GROUP BY DATE_TRUNC('month', order_date::DATE)
REORGANIZE;
效果对比:
| 指标 | 分区前 | 分区后 |
|---|---|---|
| 日报查询响应时间 | 45 秒 | 2.3 秒 |
| 扫描数据量 | 800 GB(全表) | ~22 GB(单月分区) |
| 存储裁剪率 | 0% | 97%(36 分区中跳过 35 个) |
| 月度数据删除耗时 | ~6 小时(Bulk DELETE) | < 1 秒(DROP_PARTITIONS) |
5.2 虚构案例:按天分区导致 ROS Pushback¶
📝 虚构案例
场景描述:某金融机构的 transaction_log 表按 trans_date::DATE 分区,保留 4 年数据。数据库已稳定运行 3 年多,某日夜间 ETL 加载失败,报错 Too many ROS containers for projection transaction_log_b0。
诊断过程:
-- 1. 快速确认 ROS 容器数和分区数
SELECT
s.projection_name,
COUNT(DISTINCT s.storage_oid) AS ros_count,
COUNT(DISTINCT p.partition_key) AS partition_count
FROM storage_containers s
LEFT JOIN v_monitor.partitions p
ON s.storage_oid = p.ros_id
WHERE s.projection_name LIKE 'transaction_log%'
AND s.node_name = (SELECT local_node_name())
GROUP BY 1;
-- 输出:ros_count = 1024, partition_count = 1010
-- 2. 确认分区跨度
SELECT
MIN(p.partition_key) AS oldest,
MAX(p.partition_key) AS newest,
COUNT(DISTINCT p.partition_key) AS total
FROM v_monitor.partitions p
JOIN v_catalog.projections pr
ON p.projection_id = pr.projection_id
WHERE pr.anchor_table_name = 'transaction_log'
AND p.node_name = (SELECT local_node_name());
-- 输出:oldest=20220301, newest=20260531, total=1010
根因分析:按天分区 × 4 年 ≈ 1460 个分区,远超 1024 上限。活跃分区自身还占用了额外的 14 个 ROS 容器槽位,导致总量抵达 1024 后无法加载新数据。
修复方案:
-- 方案 A:将 2024 年之前的分区归档到历史表(快速释放槽位)
CREATE TABLE transaction_log_archive
LIKE transaction_log INCLUDING PROJECTIONS;
SELECT MOVE_PARTITIONS_TO_TABLE(
'public.transaction_log',
'20220301',
'20231231',
'public.transaction_log_archive'
);
-- 方案 B:改为分层分区(根治,保留天级裁剪 + 控制 ROS 容器)
-- 也可直接用 DATE_TRUNC('month', ...) 改为纯按月分区
ALTER TABLE transaction_log
PARTITION BY trans_date::DATE
GROUP BY CALENDAR_HIERARCHY_DAY(trans_date::DATE, 2, 2)
REORGANIZE;
效果对比:
| 指标 | 修复前 | 修复后 |
|---|---|---|
| ROS 容器数 | 1024(已达上限) | 62(24 个月 + 活跃分区容器) |
| ETL 加载 | 失败(ROS Pushback) | 正常 |
| 月度查询 | 裁剪 96.7%(29/30 月) | 裁剪 95.8%(23/24 月) |
| 未来 3 年 ROS 预估 | N/A(已满) | ~110(安全范围) |
5.3 真实案例:分区数据管理避免宕机恢复延迟¶
📋 真实案例
场景摘要:某运营商的多租户 Vertica 集群中,多个租户数据库发生夯死问题。故障复盘总结中明确提出了一条与分区相关的关键建议。
关键发现:故障报告中指出,「避免对大表执行 Delete 操作」——节点宕机后若发生大量 delete 操作,将导致节点故障恢复时间变长。报告明确建议使用分区进行数据生命周期管理,用 DROP_PARTITIONS 替代 DELETE。
技术解释:DELETE 在 Vertica 中不真正删除数据,而是创建 delete vector。大量 delete vector 会延长恢复时的 replay delete 过程,因为系统需要逐一重放所有删除标记。相比之下,DROP_PARTITIONS 是纯 catalog 操作——它直接解除 ROS 容器的所有权引用,无需逐行重放。
效果对比:
| 操作类型 | 执行耗时(1 亿行数据) | 对恢复的影响 |
|---|---|---|
DELETE FROM table WHERE date_col < '2023-01-01' |
30-60 分钟 | 产生大量 delete vector,恢复时需逐条 replay |
SELECT DROP_PARTITIONS('schema.table', '20220101', '20220101') |
< 1 秒 | 纯 catalog 操作,不影响恢复速度 |
6. 完整诊断流程实战¶
📝 虚构场景 · 完整演练
背景:某数据分析团队反馈「最近几周日报越来越慢,而且昨天凌晨的月度数据清理脚本跑了 4 小时还没完成」。你被要求排查并修复。
时间线:
09:15 — 全局扫描,了解环境
-- 确认数据库版本
SELECT version();
-- 输出:Vertica Analytic Database v26.1.0-2
-- 找出最大的几张未分区表
WITH proj_rows AS (
SELECT
ps.anchor_table_id,
ps.projection_id,
CASE WHEN pr.is_segmented THEN SUM(ps.row_count) ELSE MAX(ps.row_count) END AS total_rows
FROM v_monitor.projection_storage ps
JOIN v_catalog.projections pr ON ps.projection_id = pr.projection_id
GROUP BY ps.anchor_table_id, ps.projection_id, pr.is_segmented
)
SELECT
t.table_schema,
t.table_name,
SUM(ps.used_bytes) / (1024*1024*1024) AS size_gb,
AVG(pr.total_rows)::INT AS total_rows,
t.partition_expression
FROM v_catalog.tables t
JOIN v_monitor.projection_storage ps ON t.table_id = ps.anchor_table_id
JOIN proj_rows pr ON t.table_id = pr.anchor_table_id AND ps.projection_id = pr.projection_id
WHERE t.partition_expression = ''
GROUP BY 1, 2, 5
HAVING SUM(ps.used_bytes) > 50 * 1024*1024*1024
ORDER BY size_gb DESC;
-- 输出:daily_metrics, 2.3 TB, partition_expression = ''
-- event_log, 850 GB, partition_expression = ''
-- user_activity, 320 GB, partition_expression = ''
判断:daily_metrics(2.3 TB)是最大的未分区表,且表名暗示是按天存储的指标数据——非常符合分区条件。
09:25 — 确认查询模式
-- 检查最近的慢查询,确认查询谓词模式
SELECT
request,
request_duration_ms / 1000.0 AS duration_sec
FROM v_monitor.query_requests
WHERE request ILIKE '%daily_metrics%'
AND start_timestamp > NOW() - INTERVAL '7 days'
ORDER BY request_duration_ms DESC
LIMIT 5;
-- 输出:所有慢查询都包含 WHERE metric_date BETWEEN ... AND ...
判断:查询谓词模式与日期列强相关——分区对存储裁剪收益极高。
09:35 — 检查清理脚本
-- 检查昨晚的月度清理 SQL
SELECT
request,
request_duration_ms / 1000.0 / 3600 AS duration_hours
FROM v_monitor.query_requests
WHERE request ILIKE '%DELETE%daily_metrics%'
AND start_timestamp > NOW() - INTERVAL '24 hours';
-- 输出:DELETE FROM daily_metrics WHERE metric_date < '2025-06-01'
-- duration_hours = 4.2 hours
判断:DELETE 在大表上执行极慢,且留下大量 delete vector。用 DROP_PARTITIONS 替代只需秒级。
10:00 — 执行修复
-- ALTER TABLE + REORGANIZE 一步完成
ALTER TABLE daily_metrics
PARTITION BY metric_date::DATE
GROUP BY DATE_TRUNC('month', metric_date::DATE)
REORGANIZE;
-- 对于 2.3 TB 的表,REORGANIZE 预计需要 3-6 小时
-- 监控进度
SELECT table_name, projection_name, partition_reorganize_percent
FROM v_monitor.partition_status
WHERE table_name = 'daily_metrics';
16:30 — 验证结果
-- REORGANIZE 100% 完成,验证存储裁剪
PROFILE SELECT region, SUM(value)
FROM daily_metrics
WHERE metric_date BETWEEN '2026-04-01' AND '2026-04-30'
GROUP BY region;
-- 查询 PARTITIONS_ELIMINATED 事件
SELECT node_name, event_details
FROM v_monitor.query_events
WHERE event_type = 'PARTITIONS_ELIMINATED'
AND transaction_id = :t_id;
-- 输出:Using only 1 stores out of 26 for projection daily_metrics_b0
-- 裁剪率:25/26 = 96.2%
-- 验证 DROP_PARTITIONS 替代 DELETE
SELECT DROP_PARTITIONS('public.daily_metrics', '20250501', '20250501');
-- DROP PARTITION 秒级完成!
最终效果:
| 指标 | 修复前 | 修复后 |
|---|---|---|
| 日报查询时间 | 48 秒 | 2.1 秒 |
| 月度数据清理 | 4.2 小时(DELETE) | < 1 秒(DROP_PARTITIONS) |
| 存储裁剪率 | 0% | 96% |
| 月度 ROS 容器数 | N/A | 32(安全) |
7. 快速诊断 SQL 工具箱¶
| 诊断目标 | SQL | 说明 |
|---|---|---|
| 找出未分区的大表 | WITH p AS (SELECT ps.anchor_table_id, ps.projection_id, CASE WHEN pr.is_segmented THEN SUM(ps.row_count) ELSE MAX(ps.row_count) END AS r FROM projection_storage ps JOIN v_catalog.projections pr ON ps.projection_id=pr.projection_id GROUP BY 1,2,pr.is_segmented) SELECT t.table_schema, t.table_name, SUM(ps.used_bytes)/(1024*1024*1024) AS size_gb, AVG(p.r)::INT AS total_rows FROM v_catalog.tables t JOIN projection_storage ps ON t.table_id=ps.anchor_table_id JOIN p ON t.table_id=p.anchor_table_id AND ps.projection_id=p.projection_id WHERE t.partition_expression='' GROUP BY 1,2 HAVING SUM(ps.used_bytes) > 50*1024*1024*1024 ORDER BY size_gb DESC |
50 GB 为初筛门槛(含 buddy 投影,约 25 GB 逻辑数据),实际分区决策见 §3.1 |
| 查看分区表概况 | SELECT s, COUNT(DISTINCT t) AS tables, COUNT(*) AS partitions, SUM(r) AS rows, SUM(b)/(1024*1024*1024) AS gb FROM (SELECT p.table_schema s, pr.anchor_table_name t, p.partition_key k, CASE WHEN BOOL_AND(pr.is_segmented) THEN SUM(p.ros_row_count) ELSE SUM(p.ros_row_count)/COUNT(DISTINCT p.node_name) END r, CASE WHEN BOOL_AND(pr.is_segmented) THEN SUM(p.ros_size_bytes) ELSE SUM(p.ros_size_bytes)/COUNT(DISTINCT p.node_name) END b FROM v_monitor.partitions p JOIN (SELECT DISTINCT projection_id, anchor_table_name, is_segmented FROM v_catalog.projections WHERE projection_id IN (SELECT MIN(projection_id) FROM v_catalog.projections GROUP BY projection_schema, anchor_table_name)) pr ON p.projection_id=pr.projection_id GROUP BY 1,2,3) foo GROUP BY 1 ORDER BY gb DESC |
partitions > 365 需关注;两层聚合:同时消除 buddy 重复 + 非分段表节点倍率 |
| 按投影查看 ROS 容器数 | SELECT node_name, projection_name, COUNT(DISTINCT storage_oid) AS ros_count FROM v_monitor.storage_containers GROUP BY 1,2 HAVING COUNT(DISTINCT storage_oid) > 300 ORDER BY ros_count DESC |
接近 1024 时立即行动 |
| 检查存储裁剪效果 | SELECT node_name, event_details FROM v_monitor.query_events WHERE event_type='PARTITIONS_ELIMINATED' AND event_timestamp > NOW() - INTERVAL '1 day' ORDER BY event_timestamp DESC LIMIT 20 |
无此事件 → 裁剪未生效 |
| 监控重组进度 | SELECT table_name, projection_name, partition_reorganize_percent FROM v_monitor.partition_status WHERE partition_reorganize_percent < 100 |
停滞不增需查错误 |
| 查看分区详细信息 | SELECT p.table_schema, pr.anchor_table_name AS table_name, p.partition_key, p.ros_row_count, p.ros_size_bytes/(1024*1024) AS size_mb FROM v_monitor.partitions p JOIN v_catalog.projections pr ON p.projection_id=pr.projection_id WHERE pr.anchor_table_name=':table' AND p.node_name=(SELECT local_node_name()) ORDER BY p.partition_key |
替换 :table 为目标表名 |
| 添加分区(含重组) | ALTER TABLE :table PARTITION BY :col::DATE GROUP BY DATE_TRUNC('month', :col::DATE) REORGANIZE |
维护窗口执行 |
| 添加分层分区(兼顾天级裁剪和月级合并) | ALTER TABLE :table PARTITION BY :col::DATE GROUP BY CALENDAR_HIERARCHY_DAY(:col::DATE, 2, 2) REORGANIZE |
(2,2) 表示近 2 月天级、2 月~2 年月级、2 年+年级;参数可按需调整 |
| 删除分区(替代 DELETE) | SELECT DROP_PARTITIONS(':schema.:table', ':min_key', ':max_key') |
范围删除,秒级完成,纯 catalog 操作;单分区 min=max |
| 归档旧分区 | SELECT MOVE_PARTITIONS_TO_TABLE(':schema.:table', ':min_key', ':max_key', ':schema.:archive_table') |
先创建归档表 LIKE ... INCLUDING PROJECTIONS;表名必须带 schema 前缀 |
| 检查 catalog 大小 | SELECT node_name, MAX(catalog_size_in_MB) AS catalog_mb FROM (SELECT node_name, SUM((total_memory_max_value-free_memory_min_value))/(1024*1024) AS catalog_size_in_MB FROM dc_allocation_pool_statistics_by_second GROUP BY 1, TRUNC(("time")::TIMESTAMP,'SS')) foo GROUP BY 1 |
> 15 GB 需关注 |
8. 最佳实践清单¶
按投入产出比从高到低排列:
- 大事实表按时间字段分区是基本操作 — 如果表有时间维度的查询/删除需求,分区就是必须的,不要等到几百 GB 才行动。投入(一条 DDL 语句)远小于回报(查询加速 + 管理简化)。优先用 3.1 决策树判断,而非单一大小阈值。
- 分区粒度选月不选天,除非用分层分区 — 按月分区 ROI 最高。如果确实需要天级裁剪,用
CALENDAR_HIERARCHY_DAY(col, 2, 2)实现「近期天级、远期月/年级」的分层合并,详见第 4.4 节。简单按天分区 × 3 年 = 1095 个容器(超限),分层分区 × 3 年 ≈ 40 个(安全)。 - 用 DROP_PARTITIONS 替代 DELETE 做数据清理 — DROP_PARTITIONS 是纯 catalog 操作,秒级完成,不产生 delete vector。DELETE 在 TB 级表上可能跑数小时,且留下的 delete vector 会增加恢复时间(详见第 5.3 节真实案例)。
- 使用 GROUP BY 子句控制合并粒度 —
PARTITION BY date_col::DATE GROUP BY DATE_TRUNC('month', date_col::DATE)让你既能按天查询裁剪,又不会为每一天创建一个独立的 ROS 容器。Tuple Mover 会在同一 GROUP 的日期之间合并 ROS 容器。 - 不要在维度表和小表上分区 — 小表分区增加的 ROS 容器开销远超裁剪收益。如果一张表的总 ROS 容器数只有个位数,分区反而会让它变成几十个,查询优化器需要扫描更多元数据,性能可能下降。
- 分区键必须 NOT NULL — NULL 值会导致数据加载失败。建议在列定义中加
NOT NULL约束。 - 监控 ROS 容器数比监控分区数更重要 — 分区数只是中间指标,真正的硬限制是每投影每节点 1024 个 ROS 容器。当你拥有超过 365 个分区时,每周至少检查一次 ROS 容器数趋势。
- 执行 ALTER TABLE ... REORGANIZE 前评估时间窗口 — 对于 TB 级表,重组可能需要数小时。大表推荐用分批 INSERT 到新表的方案(见 4.6 方案 B),更可控且可中断。
- 创建 MOVE_PARTITIONS_TO_TABLE 的归档表时带上
INCLUDING PROJECTIONS— 否则归档表不会有预设的 projection,后续查询性能会很差。 - 分区键选择优先匹配数据生命周期策略 — 如果数据按「创建日期」保留 3 年,那么分区键应该用创建日期,即使大多数查询用的是「更新日期」。因为 DROP_PARTITIONS 必须按分区键删除,数据管理需求优先于查询优化。
扩展阅读¶
- 理解 Vertica 的分区 — 分区基础概念与存储裁剪原理
- Vertica 分区表常见问题 — 分区 FAQ 与命令速查
- ROS Pushback 故障排查 — ROS 容器数超限排查与修复
- Tuple Mover 最佳实践完全指南 — Mergeout 与分区交互机制