Vertica Schema DiskQuota 配置指南¶
作者:JiangChong | 撰写时间:2026-05-26
适用场景:当需要限制 Schema 或表的最大磁盘用量,防止单个业务模块无限制增长挤占集群空间时,使用 DiskQuota 机制。
关联文章:Vertica 监控最佳实践 | Vertica 冷热数据管理与成本优化 | Vertica 表使用情况分析与资源优化
理解全文脉络¶
本文按照从原理到实操的路径组织:第 1 节解释 DiskQuota 的底层机制和限制边界;第 2 节介绍如何从系统表全局监控配额状态;第 3 节逐步带你从发现超限到定位根因;第 4 节给出分层解决方案;第 5–6 节通过案例和完整演练串联全部知识;第 7–8 节提供可直接粘贴的 SQL 工具箱和最佳实践清单。
- 如果你刚接触 DiskQuota,建议从第 1 节开始顺序阅读。
- 如果你已经知道配额超了,想快速解决,直接跳到第 4 节 + 第 7 节工具箱。
- 如果你在做数据库巡检发现了 DiskQuota 告警,从第 3.2 节开始。
第 1 节:原理理解¶
1.1 什么是 DiskQuota¶
DiskQuota 是 Vertica 提供的存储配额限制机制,可以在 Schema 级别或表级别设定磁盘用量的上限。当对象的实际存储用量达到或超过配额时,所有会增长磁盘使用的操作(INSERT、COPY、UPDATE 导致的 Ros 增长等)都会被数据库阻塞并报错。
DiskQuota 就像给每个 Schema 划定了一块"地皮红线"——你可以在这块地上自由盖房子(创建表、加载数据),但不能越过红线。一旦越过,施工必须停止,直到你把超出的部分拆掉或红线往外扩。
1.2 配额的计算方式¶
DiskQuota 统计的是该对象的逻辑唯一数据量(不是物理磁盘占用量),计算方式因模式不同而有差异:
| 模式 | 计算范围 | 说明 |
|---|---|---|
| Enterprise Mode | 所有 Storage Container 总和,但排除冗余副本 | 排除 buddy projection(KSAFE 镜像副本),UNSEGMENTED 表也只计一份(不计 N 节点副本) |
| Eon Mode | 所有 Shard 的数据量(primary subscription)总和 | 不包括 secondary subcluster 的订阅副本 |
这意味着什么:对同样 10GB 的逻辑数据:
- SEGMENTED + KSAFE=1:物理占 20GB(b0 + b1),DiskQuota 扣 10GB
- UNSEGMENTED + 3 节点:物理占 30GB(每节点 10GB),DiskQuota 扣 10GB
- DiskQuota 的目标是控制数据增长,而不是物理磁盘——冗余副本的开销不在配额管控范围内。
关键点:配额统计的是压缩后的实际存储(used_bytes),而不是原始数据量。一个 Schema 声称有 10TB 原始数据,经过 RLE、DeltaVal 等编码后可能只占 1TB 配额空间。
1.3 配额的限制边界(什么受限制 / 什么不受限制)¶
DiskQuota 不是全面无死角的限制器,它有明确的豁免场景。理解这些边界可以避免误判。
| 操作类型 | 是否受配额限制 | 说明 |
|---|---|---|
| INSERT / COPY | ✅ 是 | 数据加载会检查配额,超限则报错 ERROR 10764 |
| UPDATE(产生新 ROS) | ✅ 是 | UPDATE 本质是 delete + insert,会增加存储 |
| ILM(数据生命周期策略) | ✅ 是 | 搬迁数据产生新副本时受配额约束 |
| Tuple Mover(Mergeout / Moveout) | ❌ 否 | TM 内部操作不受配额限制 |
| 节点恢复(Recovery) | ❌ 否 | 恢复过程复制数据不受配额限制 |
| Rebalance(数据重分布) | ❌ 否 | 集群均衡操作不受配额限制 |
| DDL(CREATE TABLE / ALTER TABLE) | ⚠️ 部分 | 创建空表不检查配额,但涉及数据拷贝的 DDL 会检查 |
这个豁免机制很重要:即使 Schema 已经超过配额,Tuple Mover 仍然可以执行 Mergeout 来合并 ROS 小文件,逐步回收 delete vector 空间。这意味着配额不会把数据库锁死——内部维护操作照常进行,只是阻止新的数据写入。
1.4 触发条件和错误表现¶
当用户执行的 DML/加载操作会导致配额超限时,Vertica 返回:
配额检查发生在操作开始前——Vertica 预估本次操作会增加的磁盘量,加上当前用量,判断是否超出配额。这意味着即使当前用量刚好等于配额,任何增加用量的操作都会被拒绝。
一个重要细节:把配额设得比当前用量低是允许的(ALTER SCHEMA ... DISK_QUOTA '较小值' 不会报错),但此后任何写入操作都会失败,直到用量降到新配额以下。
第 2 节:系统级监控(从宏观入手)¶
2.1 查看所有已配置的 DiskQuota¶
使用 DISK_QUOTA_USAGES 系统表(v_monitor 模式)查看所有已配置配额的对象及其用量:
SELECT
object_name,
CASE WHEN is_schema THEN 'Schema' ELSE 'Table' END AS object_type,
disk_quota_in_bytes // 1024^3 AS quota_gb,
total_disk_usage_in_bytes // 1024^3 AS used_gb,
ROUND(total_disk_usage_in_bytes * 100.0 / NULLIF(disk_quota_in_bytes, 0), 2) AS usage_pct
FROM DISK_QUOTA_USAGES
ORDER BY usage_pct DESC;
如何解读结果:
usage_pct > 100:已超配额,该对象无法再写入新数据,需立即处理。usage_pct > 80:接近上限,应关注增长趋势,提前调整配额或清理数据。usage_pct = 0:配额已设但尚未有数据(常见于刚创建的 Schema)。- 结果为空:当前数据库没有任何 DiskQuota 配置,所有对象均无限制。
注意:
DISK_QUOTA_USAGES只显示已经配置了配额的对象。如果结果为空,说明没有人设置过配额,而不是所有配额都是 0。
2.2 结合巡检脚本查看 Schema 容量 + 配额全貌¶
在巡检脚本中的 Catalog_Analysis_Schema_DiskQuotas_and_TableCount 检查项,会将 Schema 的表数量、磁盘用量和 DiskQuota 配置一起列出:
-- 来源:databaseCheck Catalog_Analysis_Schema_DiskQuotas_and_TableCount
SELECT
s.table_schema AS schema_name,
s.tableCount,
nvl(d.total_disk_usage_in_MB::varchar, 'NoDiskQuota') AS totalDiskUsageinMB,
nvl(d.disk_quota_in_MB::varchar, 'NoDiskQuota') AS diskQuotainMB
FROM
(SELECT table_schema, count(*) tableCount FROM tables GROUP BY 1) s
LEFT JOIN
(SELECT object_name,
total_disk_usage_in_bytes // 1024^2 AS total_disk_usage_in_MB,
disk_quota_in_bytes // 1024^2 AS disk_quota_in_MB
FROM DISK_QUOTA_USAGES
WHERE is_schema) d
ON s.table_schema = d.object_name
ORDER BY 2 DESC, 1;
如何解读结果:
diskQuotainMB = 'NoDiskQuota':该 Schema 没有配额限制,无限制增长。tableCount > 1000且有 DiskQuota:高危组合。大量表共享一个限额,每个表的写入空间都被压缩,且配额检查自身有性能开销(每写入检查一次)。diskQuotainMB远小于totalDiskUsageinMB:配额配置不合理,需调高限额或清理数据。
2.3 通过查询请求日志定位配额拒绝事件¶
当有写入操作因 DiskQuota 被拒绝时,错误信息会记入 query_requests 表:
SELECT
start_timestamp::char(19) AS error_time,
user_name,
session_id,
request_id,
LEFT(request, 200) AS sql_snippet,
error_code
FROM query_requests
WHERE is_executing = false
AND error_code = 10764
AND start_timestamp > sysdate - 7
ORDER BY start_timestamp DESC
LIMIT 50;
如何解读结果:
- 如果
error_code = 10764的记录频繁出现,说明该 Schema 的配额已经成为业务瓶颈,需要重新评估配额值。 - 结合
user_name和sql_snippet可以定位是哪个业务模块在写入时触发限额。 start_timestamp的时间分布可以判断是持续性问题还是批次加载的周期性超限。
第 3 节:逐步定位根因(从宏观到微观)¶
当遇到 DiskQuota 告警或报错时,按以下步骤逐层排查。
步骤 1:确认哪些对象触发了配额¶
做什么:快速找出超配额或接近配额的所有对象。
SQL:
SELECT
object_name,
CASE WHEN is_schema THEN 'Schema' ELSE 'Table' END AS type,
disk_quota_in_bytes // 1024^2 AS quota_mb,
total_disk_usage_in_bytes // 1024^2 AS used_mb,
ROUND(total_disk_usage_in_bytes * 100.0 / disk_quota_in_bytes, 1) AS usage_pct,
CASE
WHEN total_disk_usage_in_bytes >= disk_quota_in_bytes THEN '🔴 已超配额'
WHEN total_disk_usage_in_bytes * 1.0 / disk_quota_in_bytes >= 0.85 THEN '🟡 接近上限(>85%)'
WHEN total_disk_usage_in_bytes * 1.0 / disk_quota_in_bytes >= 0.70 THEN '🟢 需关注(>70%)'
ELSE '✅ 正常'
END AS status
FROM DISK_QUOTA_USAGES
ORDER BY usage_pct DESC;
如何解读:
- 如果所有对象都是 ✅ 正常,但仍有
ERROR 10764报错 → 进入步骤 2。 - 如果有 🔴 已超配额对象 → 进入步骤 3,先看能不能清理数据。
- 如果有 🟡 接近上限对象 → 进入步骤 4,评估是否需要调高配额。
如果不是则进入下一步:配额状态正常但仍有报错,可能是表级配额与 Schema 级配额冲突。
步骤 2:检查表级配额与 Schema 级配额的冲突¶
做什么:当 Schema 本身未超配额、但某张表有自己独立的配额上限时,表的写入会先被表级配额阻断。
SQL:
SELECT
object_name,
disk_quota_in_bytes // 1024^2 AS quota_mb,
total_disk_usage_in_bytes // 1024^2 AS used_mb,
ROUND(total_disk_usage_in_bytes * 100.0 / disk_quota_in_bytes, 1) AS usage_pct
FROM DISK_QUOTA_USAGES
WHERE NOT is_schema
ORDER BY usage_pct DESC;
如何解读:
is_schema = false的行就是表级配额。object_name格式为schema.table。- 如果某张表的
usage_pct > 100,即使它的父 Schema 没超配额,对该表的写入也会被拒绝。 - 表级配额与 Schema 配额是独立的两层限制,一层超了就会阻止写入。
如果不是则进入下一步:配额状态一切正常,可能是预估检查的误判——Vertica 在操作前会用保守估算,即使实际写入后不会超。
步骤 3:分析 Schema 容量的组成(找到清理目标)¶
做什么:找出 Schema 中哪些表占用了最多空间,确定清理优先级。
SQL:
-- 行数公式:SEGMENTED 表 SUM/COUNT(DISTINCT projection_name) 消 buddy 重;
-- UNSEGMENTED 表取 MAX(每节点全量副本相等)
-- 注意:JOIN projections 必须 DISTINCT,否则 unsegmented 表产生笛卡尔积
SELECT
ps.anchor_table_name,
COUNT(DISTINCT ps.projection_name) AS proj_count,
SUM(ps.used_bytes) // 1024^3 AS used_gb,
CASE WHEN BOOL_AND(p.is_segmented)
THEN SUM(ps.row_count) / COUNT(DISTINCT ps.projection_name)
ELSE MAX(ps.row_count)
END AS row_count,
SUM(ps.ros_count) AS ros_file_count,
ROUND(SUM(ps.used_bytes) * 100.0 / SUM(SUM(ps.used_bytes)) OVER(), 1) AS pct_of_schema
FROM v_monitor.projection_storage ps
JOIN (SELECT DISTINCT projection_id, is_segmented
FROM v_catalog.projections) p
ON ps.projection_id = p.projection_id
WHERE ps.anchor_table_schema = 'your_schema' -- 替换为目标 Schema 名
GROUP BY ps.anchor_table_name
ORDER BY used_gb DESC
LIMIT 20;
如何解读:
| 指标 | 含义 | 阈值 |
|---|---|---|
used_gb |
该表的压缩后存储(物理占用,包含 buddy/副本) | 关注 top 5 的占比 |
row_count |
逻辑行数(已消 buddy 和 unsegmented 膨胀) | 辅助参考 |
ros_file_count |
ROS 容器文件数 | > 1000 说明 Mergeout 不充分,可通过 SELECT DO_TM_TASK('mergeout', 'schema.table') 合并 |
pct_of_schema |
该表占整个 Schema 的百分比 | > 60% 说明存在单一超级大表,应考虑分区 + 归档 |
公式说明:直接
SUM(row_count)在 3 节点 KSAFE=1 的 SEGMENTED 表上会膨胀 6 倍(b0 3节点 + b1 3节点 = 6 份行数)。用is_segmented区分类型:SEGMENTED 用SUM/COUNT(DISTINCT)消 buddy,UNSEGMENTED 取MAX(每个节点值相同)。而used_bytes无需除法——它代表物理磁盘占用。详见 agentmemory 记录mem_mq1uxytm。
如果不是则进入下一步:Schema 中表都较小,但总用量接近配额。问题出在表数量过多。
步骤 4:评估表数量对 DiskQuota 的影响¶
做什么:当 Schema 下有大量表时,即使每张表数据不大,检查配额的开销也会累积。
SQL:
SELECT
table_schema,
COUNT(*) AS table_count,
SUM(CASE WHEN LENGTH(partition_expression) > 0 THEN 1 ELSE 0 END) AS partitioned_tables,
SUM(CASE WHEN LENGTH(partition_expression) = 0 THEN 1 ELSE 0 END) AS unpartitioned_tables
FROM tables
WHERE table_schema = 'your_schema' -- 替换为目标 Schema 名
AND table_definition = '' -- 排除外部表
GROUP BY table_schema;
如何解读:
table_count > 1000:高危。每次写入都要遍历配额检查,大量表会拖慢 DML 操作。巡检中catalog_schema_diskquota_table_count: 1000正是基于此考虑。unpartitioned_tables占比高:这些表无法通过 DROP_PARTITIONS 高效清理,未来数据清理的成本更高。
第 4 节:解决方案(从快速见效到根本治理)¶
4.1 立即措施(当天可执行)¶
方案 A:临时调高配额(紧急止损)¶
适用场景:配额超限导致业务写入失败,需要立刻恢复。
为什么是这个值:新配额至少应是当前用量的 1.5 倍,给清理操作留出缓冲空间。例如当前 2TB,设 3TB;本例设为 5T 适用于当前用量 3TB 左右的场景。此操作需要 superuser 权限。
⚠️ 调高配额只是止血手段,根因是数据增长未受控。务必配合方案 C/D 进行清理和归档。
方案 B:清理 Delete Vectors 回收空间¶
适用场景:配额接近上限,但 Schema 中有大量 delete 操作历史,delete vectors 占用了配额。
SELECT
sc.schema_name || '.' || sc.projection_name AS proj_name,
CASE WHEN BOOL_AND(p.is_segmented)
THEN SUM(sc.total_row_count) / COUNT(DISTINCT sc.projection_name)
ELSE MAX(sc.total_row_count)
END AS total_rows,
SUM(sc.deleted_row_count) AS deleted_rows_physical,
ROUND(SUM(sc.deleted_row_count) * 100.0 / SUM(sc.total_row_count), 1) AS deleted_pct
FROM v_monitor.storage_containers sc
JOIN (SELECT DISTINCT projection_id, is_segmented
FROM v_catalog.projections) p
ON sc.projection_id = p.projection_id
WHERE sc.schema_name = 'your_schema' -- 替换为目标 Schema 名
AND sc.deleted_row_count > 0
GROUP BY sc.schema_name, sc.projection_name
HAVING SUM(sc.deleted_row_count) * 100.0 / SUM(sc.total_row_count) > 10
ORDER BY deleted_pct DESC
LIMIT 20;
-- 对高 deleted_pct 的投影执行 purge
-- 前提:AHM 已推进到 delete vectors 所在 epoch 之后,否则 PURGE 不会生效
-- SELECT GET_AHM_EPOCH(), GET_LAST_GOOD_EPOCH(); -- 差距 > 500 则先 MAKE_AHM_NOW()
SELECT PURGE_PROJECTION('your_schema.your_projection');
为什么有效:Delete vectors 在 Vertica 中仍占用存储空间,PURGE_PROJECTION 会将物理标记为删除的行彻底移除,立即释放配额空间。但前提是 AHM(Ancient History Mark)必须已经推进到 delete vectors 所在 epoch 之后——如果 AHM 滞后(例如有长事务阻塞),PURGE 不会生效。此时需先执行 SELECT MAKE_AHM_NOW() 手动推进 AHM。TM Mergeout 也会通过跳过已删行逐步回收空间,但速度慢(每次只处理少量 ROS 文件),PURGE 是即时手段。
方案 C:移除不再需要的旧数据¶
适用场景:表中有按日期/批次分区的历史数据不再需要。
-- 删除 2024 年之前的分区(分区键为 date_col 的情况)
SELECT DROP_PARTITIONS('your_schema.your_table', '2023-01-01', '2023-12-31');
为什么用 DROP_PARTITIONS 而不是 DELETE:DROP_PARTITIONS 直接从存储元数据中移除分区数据,不产生 delete vectors,配额空间立即可用。而 DELETE FROM ... WHERE ... 会产生 delete vectors,空间要等 Mergeout 回收。DROP_PARTITIONS 支持范围删除(min, max),比逐分区调用 DROP_PARTITION 更高效。
4.2 短期优化(当周执行)¶
方案 D:为高增长表设置独立配额¶
适用场景:Schema 中有某几张表增长特别快,想单独控制而不是一起受限于 Schema 配额。
为什么需要表级配额:Schema 级配额是一个总闸门,所有表共享。当只有一张表在疯狂增长时,它会吃掉所有配额让其他表也无法写入。表级配额可以「隔离故障域」——限制住失控的表,保护同 Schema 下其他表的正常写入。
⚠️ 注意:表级配额不应超过父 Schema 配额(否则 Vertica 警告)。建议 Schema 配额 > Σ 表配额,但前提是所有可能增长的大表都设了表配额——若有表未设配额,它不受表级限制,可单表吃光 Schema 配额。
方案 E:归档冷数据到外部存储¶
适用场景:大量历史数据很少被查询,但占据配额。
简要思路:
- 使用
projection_usage表识别冷表 - 将冷数据导出到 HDFS/S3/对象存储
- 在原 Schema 中删除冷表或冷分区
- 通过外部表 + Hive/Iceberg 按需查询归档数据
为什么能解决 DiskQuota 问题:配额统计的是内部表的存储,导出后的数据不算入配额。配合外部表(External Table)或 Iceberg 表可实现对归档数据的按需访问。
4.3 根本治理¶
方案 F:建立 Schema 拆分策略¶
适用场景:单个 Schema 表数过多(> 1000)或数据集中度过高(单 Schema 占 > 50% 集群容量)。
核心理念:不要把鸡蛋放在一个篮子里。按业务域、数据生命周期、访问频率三个维度拆分 Schema。
-- 示例 Schema 架构
analytics_ods -- 原始贴源层,短保留期(7天),配额 2TB
analytics_dwd -- 明细宽表层,中保留期(90天),配额 5TB
analytics_dws -- 汇总层,长保留期(1年),配额 3TB
analytics_dim -- 维度表,永久保留,配额 500GB
analytics_archive -- 归档区,外部表为主,无配额限制
为什么分 Schema 而不是全放一起:
- 故障隔离:一个 Schema 的配额超限不影响其他 Schema
- 精细管控:不同生命周期的数据用不同配额
- 降低检查开销:每个 Schema 表数减少,配额检查效率提升
- 运维友好:清理到期数据时可通过 TRUNCATE SCHEMA 或批量 DROP_PARTITIONS
第 5 节:深入案例¶
📝 虚构案例 1:ETL 批量加载触发配额超限¶
场景描述:某运营商的数据仓库中,ods_billing Schema 设置了 10TB 配额。某日凌晨 ETL 作业批量加载话单数据(每次约 500GB,每天 2 次),第 3 次加载时报 ERROR 10764,后续批次全部失败。当前 Schema 用量 9.8TB,磁盘空间充足(还有 20TB 可用)。
诊断过程:
-- 1. 确认配额状态
SELECT object_name,
disk_quota_in_bytes // 1024^4 AS quota_tb,
total_disk_usage_in_bytes // 1024^4 AS used_tb
FROM DISK_QUOTA_USAGES WHERE object_name = 'ods_billing';
-- 结果:quota_tb=10, used_tb=9.8,用量 98%
-- 2. 检查 delete vectors 累积量
SELECT p.projection_basename AS table_name,
CASE WHEN BOOL_AND(p.is_segmented)
THEN SUM(sc.total_row_count) / COUNT(DISTINCT sc.projection_name)
ELSE MAX(sc.total_row_count)
END AS total_rows,
CASE WHEN BOOL_AND(p.is_segmented)
THEN SUM(sc.deleted_row_count) / COUNT(DISTINCT sc.projection_name)
ELSE MAX(sc.deleted_row_count)
END AS deleted_rows,
ROUND(SUM(sc.deleted_row_count)*100.0/SUM(sc.total_row_count),1) AS deleted_pct
FROM v_monitor.storage_containers sc
JOIN (SELECT DISTINCT projection_id, projection_basename, is_segmented
FROM v_catalog.projections) p
ON sc.projection_id = p.projection_id
WHERE sc.schema_name = 'ods_billing'
AND sc.deleted_row_count > 0
GROUP BY p.projection_basename
ORDER BY SUM(sc.deleted_row_count) DESC
LIMIT 10;
-- 结果:call_detail_record 总行 100 亿,其中 12 亿行为 delete vectors(12%),
-- 对应物理存储约 1.2TB
根因分析:数据加载采用 DELETE + INSERT 模式更新话单(每天先 DELETE 旧数据再 INSERT 新数据),产生大量 delete vectors。这些 delete vectors 仍然计入 DiskQuota 统计(见 4.1B 节说明),导致实际可用于新数据的配额空间只有 0.2TB,无法容纳单次 0.5TB 的加载量。
修复方案:
- 临时:调高配额至 15TB 让当日 ETL 继续运行
- 短期:对
call_detail_record执行PURGE_PROJECTION(),delete vectors 占比从 12% 降至 < 1% 后用量降至约 8.6TB - 长期:改用
DROP_PARTITIONS+INSERT代替DELETE+INSERT,彻底避免 delete vector 累积
效果对比:
| 指标 | 修复前 | 修复后 |
|---|---|---|
| Schema 实际用量 | 9.8TB | 8.6TB |
| Delete Vectors 占比 | 12% | < 1% |
| 配额利用率(15TB 配额) | 65% | 57% |
| 单次加载时间 | 45 分钟 | 32 分钟(ROS 文件减少,扫描路径更短) |
📝 虚构案例 2:新业务上线未规划配额导致挤压存量业务¶
场景描述:某金融企业 Vertica 集群中,risk_analysis Schema 初始配额设为 2TB。原有风险模型表共占用 1.2TB,运行稳定。新上线实时风控项目新增 5 张明细表,计划每天加载 200GB 数据。上线第 5 天,所有表写入报 ERROR 10764。
诊断过程:
-- 1. 确认用量趋势
SELECT object_name,
disk_quota_in_bytes // 1024^4 AS quota_tb,
total_disk_usage_in_bytes // 1024^4 AS used_tb,
ROUND(total_disk_usage_in_bytes*100.0/disk_quota_in_bytes,1) AS pct
FROM DISK_QUOTA_USAGES WHERE object_name = 'risk_analysis';
-- 结果:quota_tb=2, used_tb=2.05, pct=102.5 — 已超
-- 2. 分析各表占用
SELECT ps.anchor_table_name,
SUM(ps.used_bytes)//1024^3 AS used_gb,
CASE WHEN BOOL_AND(p.is_segmented)
THEN SUM(ps.row_count) / COUNT(DISTINCT ps.projection_name)
ELSE MAX(ps.row_count)
END AS row_count
FROM v_monitor.projection_storage ps
JOIN (SELECT DISTINCT projection_id, projection_basename, is_segmented
FROM v_catalog.projections) p
ON ps.projection_id = p.projection_id
WHERE ps.anchor_table_schema = 'risk_analysis'
GROUP BY ps.anchor_table_name
ORDER BY used_gb DESC LIMIT 10;
-- 结果:rt_detail_transaction 单表 1.2GB/天 × 5天 = 6GB,加上原 1.2TB
根因分析:新上线 5 张明细表数据量超出预期(预估 50GB/天,实际 200GB/天),5 天内总用量从 1.2TB 涨到 2.05TB。原有 2TB 配额根本没有考虑新业务的增长。
修复方案:
- 立即:将 Schema 配额调到 5TB
- 短期:为新增的 5 张明细表分别设置表级配额(rt_detail_transaction: 1.5TB, 其余各 500GB),在 Schema 总配额内做到表级隔离
- 长期:将实时风控相关的 5 张明细表拆分到独立的
rt_fraud_detectionSchema,配置 3TB 配额 - 流程改进:新增数据表上线前必须评估存储增长量,确认配额余量是否充足
效果对比:
| 指标 | 修复前 | 修复后 |
|---|---|---|
| Schema 配额 | 2TB(共享) | risk_analysis: 3TB, rt_fraud_detection: 3TB(隔离) |
| 写入成功率 | 0%(全部阻塞) | 100% |
| 定位超限原因耗时 | 2 小时 | < 5 分钟(表级配额直接标识超限表) |
📋 真实案例¶
客户场景:某运营商的生产 Vertica 集群在执行巡检脚本时,Catalog_Analysis_Schema_DiskQuotas_and_TableCount 检查项首次被加入。该检查的目的是发现「Schema 存在 DiskQuota 且对象较多」的情况——这种情况下配额检查的性能开销不可忽视。
诊断发现:
- 集群中有 3 个 Schema 配置了 DiskQuota
- 其中 1 个 Schema 下包含超过 2000 张表
- 每次对这些 Schema 下的表做 DML 写入时,Vertica 都在检查该 Schema 的配额上限
- 高并发 ETL 场景下,配额检查的累计开销导致写入延迟增加约 5-10%
根因:当初配置 DiskQuota 是为了限制该 Schema 的存储增长,但没有考虑到表数量对配额检查性能的影响。2000+ 张表竞争写入一个带配额的 Schema,每次写入前都要走配额检查逻辑。
修复方案:
- 将 2000+ 张表按业务子模块拆分到 4 个子 Schema
- 每个子 Schema 独立设置 DiskQuota
- 原 Schema 的配额设为子 Schema 配额之和 + 20%
效果:DDL/DML 操作延迟恢复正常,配额管理更精细,各子模块故障隔离。
启示:DiskQuota + 大量表(> 1000)是一个需要关注的红旗组合。在巡检中,catalog_schema_diskquota_table_count: 1000 阈值正是源于此经验。
第 6 节:完整诊断流程实战¶
📝 虚构场景 · 完整演练¶
背景:某电商平台 DBA 收到业务方反馈:「促销活动数据无法写入 marketing_campaign Schema,vsql 报错 Disk Quota Exceeded」。该 Schema 配置了 500GB 配额,之前一直正常。
时间线¶
T+0min — 收到报错,确认现象
SELECT object_name, is_schema,
disk_quota_in_bytes//1024^3 AS quota_gb,
total_disk_usage_in_bytes//1024^3 AS used_gb
FROM DISK_QUOTA_USAGES
WHERE object_name = 'marketing_campaign';
输出:
判断:已超配额(512GB > 500GB),需要立即止血并定位根因。
T+5min — 临时调高配额,恢复写入
T+10min — 排查容量大户
-- 排查容量大户 + delete vectors
SELECT ps.anchor_table_name,
SUM(ps.used_bytes)//1024^3 AS used_gb,
SUM(ps.ros_count) AS ros_files,
COALESCE(dv.deleted_rows, 0) AS del_rows
FROM v_monitor.projection_storage ps
LEFT JOIN (
SELECT p.anchor_table_name,
CASE WHEN BOOL_AND(p.is_segmented)
THEN SUM(sc.deleted_row_count) / COUNT(DISTINCT sc.projection_name)
ELSE MAX(sc.deleted_row_count)
END AS deleted_rows
FROM v_monitor.storage_containers sc
JOIN (SELECT DISTINCT projection_id, anchor_table_name, is_segmented
FROM v_catalog.projections) p
ON sc.projection_id = p.projection_id
WHERE sc.schema_name = 'marketing_campaign'
GROUP BY p.anchor_table_name
) dv ON ps.anchor_table_name = dv.anchor_table_name
WHERE ps.anchor_table_schema = 'marketing_campaign'
GROUP BY ps.anchor_table_name, dv.deleted_rows
ORDER BY used_gb DESC LIMIT 10;
输出(截取):
anchor_table_name | used_gb | ros_files | del_rows
campaign_event_log | 230 | 4500 | 89000000
promotion_user_click | 120 | 3200 | 45000000
campaign_config | 2 | 10 | 0
...
判断:campaign_event_log 和 promotion_user_click 两张表占用了 350GB(68%),且有大量 delete vectors(1.34 亿行)。这两种表的数据特点是:促销期间写入量大,活动过期后会定期删除。
T+20min — 推进 AHM,确保 delete vectors 可被物理清除
PURGE_PROJECTION 只能清除 AHM 之前的 delete vectors。如果 AHM 滞后(长事务阻塞 epoch 推进),PURGE 不会生效。先检查 AHM 位置:
-- 检查 AHM 是否足够新(差距 < 100 说明 moveout 正常)
SELECT get_ahm_epoch() AS ahm, get_last_good_epoch() AS lge,
get_last_good_epoch() - get_ahm_epoch() AS lag;
-- 结果:lag = 45,正常;如 lag > 500 需先 SELECT MAKE_AHM_NOW()
确认 AHM 正常后执行 PURGE:
-- 对最大的两张表执行 purge(b0 + b1 都要清,否则 buddy 副本仍有 delete vectors)
SELECT PURGE_PROJECTION('marketing_campaign.campaign_event_log_b0');
SELECT PURGE_PROJECTION('marketing_campaign.campaign_event_log_b1');
SELECT PURGE_PROJECTION('marketing_campaign.promotion_user_click_b0');
SELECT PURGE_PROJECTION('marketing_campaign.promotion_user_click_b1');
等待 5 分钟后复查配额状态:
SELECT object_name,
total_disk_usage_in_bytes//1024^3 AS used_gb
FROM DISK_QUOTA_USAGES
WHERE object_name = 'marketing_campaign';
输出:used_gb = 380
T+30min — 用量从 512GB 降到 380GB(回收了 132GB delete vector 空间)。
T+40min — 制定长期方案
- 将配额从临时值 800GB 调回合理值 600GB(380GB 实际 × 1.5 倍缓冲)
- 将
campaign_event_log改为按月分区,过期活动用DROP_PARTITIONS清理(从源头消除 delete vectors) - 建立 DiskQuota 监控告警:usage > 80% 时提前通知
最终效果:
| 指标 | 处理前 | 处理后 |
|---|---|---|
| Schema 配额 | 500GB | 600GB |
| 实际用量 | 512GB(超配额) | 380GB |
| Delete vectors | 1.34 亿行 | < 500 万行 |
| ROS 文件数(campaign_event_log) | 4500 | 600 |
| 配额利用率 | 102.4% | 63.3% |
第 7 节:快速诊断 SQL 工具箱¶
| 诊断目标 | SQL | 说明 |
|---|---|---|
| 查看所有配额对象及用量 | SELECT object_name, CASE WHEN is_schema THEN 'Schema' ELSE 'Table' END, disk_quota_in_bytes//1024^3 AS quota_gb, total_disk_usage_in_bytes//1024^3 AS used_gb, ROUND(total_disk_usage_in_bytes*100.0/disk_quota_in_bytes,1) AS pct FROM DISK_QUOTA_USAGES ORDER BY pct DESC; |
优先关注 pct > 85 的对象 |
| 查看配额拒绝的查询 | SELECT start_timestamp::char(19), user_name, LEFT(request,200) FROM query_requests WHERE error_code=10764 AND start_timestamp>sysdate-7 ORDER BY start_timestamp DESC; |
error_code 10764 即 Disk Quota Exceeded |
| 查看 Schema 下各表存储占用 | SELECT anchor_table_name, SUM(used_bytes)//1024^3 AS used_gb, SUM(ros_count) AS ros_files FROM v_monitor.projection_storage WHERE anchor_table_schema=':schema' GROUP BY 1 ORDER BY 2 DESC; |
找到清理优先级最高的表 |
| 查看 Delete Vectors 占比高的表 | SELECT p.projection_basename AS tbl, ROUND(SUM(sc.deleted_row_count)*100.0/SUM(sc.total_row_count),1) AS del_pct FROM storage_containers sc JOIN (SELECT DISTINCT projection_id,projection_basename FROM projections) p ON sc.projection_id=p.projection_id WHERE sc.schema_name=':schema' AND sc.deleted_row_count>0 GROUP BY 1 HAVING SUM(sc.deleted_row_count)>0 ORDER BY del_pct DESC; |
del_pct > 20 建议执行 PURGE |
| 查看 Schema 下 ROS 文件数 | SELECT anchor_table_name, SUM(ros_count) FROM v_monitor.projection_storage WHERE anchor_table_schema=':schema' GROUP BY 1 ORDER BY 2 DESC; |
单表 > 1000 文件说明 Mergeout 不足 |
| 查看 Schema 表数量 | SELECT table_schema, COUNT(*) FROM tables WHERE table_schema=':schema' AND table_definition='' GROUP BY 1; |
> 1000 张表时需关注配额检查性能 |
| 查看 Schema 下的配额+表数(巡检 SQL) | SELECT s.table_schema, s.tableCount, nvl(d.total_disk_usage_in_MB::varchar,'NoDiskQuota') AS used_mb, nvl(d.disk_quota_in_MB::varchar,'NoDiskQuota') AS quota_mb FROM (SELECT table_schema, count(*) tableCount FROM tables GROUP BY 1) s LEFT JOIN (SELECT object_name, total_disk_usage_in_bytes//1024^2 AS total_disk_usage_in_MB, disk_quota_in_bytes//1024^2 AS disk_quota_in_MB FROM DISK_QUOTA_USAGES WHERE is_schema) d ON s.table_schema=d.object_name ORDER BY 2 DESC; |
来源:databaseCheck v6.1 |
| 查看 AHM 是否正常推进(影响 PURGE 能否生效) | SELECT get_ahm_epoch() AS ahm, get_last_good_epoch() AS lge, get_last_good_epoch()-get_ahm_epoch() AS lag; |
lag > 500 需先 MAKE_AHM_NOW() 再 PURGE |
第 8 节:最佳实践清单¶
- 配额设为实际用量的 1.3–1.5 倍。这不是给 Tuple Mover 留空间(TM 豁免配额),而是给批量加载的 peak 时段留缓冲。配额卡在刚好等于日常用量时,任何一次加载批次稍大就会触发
ERROR 10764。1.3× 保证单日峰值不超限,1.5× 覆盖 2-3 天的小幅增长窗口,给清理操作留出反应时间。 - 每个 Schema 的表数控制在 1000 以内。超过 1000 张表共享一个配额时,每次 DML/加载都走配额检查,在高并发 ETL 场景下累积开销不可忽略。如果确实有很多表,按业务子模块拆分成多个 Schema。
- PURGE_PROJECTION 是应急手段,不要列入定时任务。正常情况下 AHM 自行推进、Tuple Mover 逐步回收 delete vectors,无需人工干预。PURGE 会锁表并产生大量 IO,拖慢调度链。仅在 delete vectors 异常积压(占比 > 20%)导致配额告急时手动执行,执行前先确认
GET_AHM_EPOCH()已推进到 delete vectors 所在 epoch 之后。 - 优先使用 DROP_PARTITIONS 而非 DELETE 清理数据。
DROP_PARTITIONS直接从存储元数据中移除分区数据,空间立即回收且计入配额释放。DELETE产生 delete vectors,空间要等 Mergeout/PURGE 才回收。 - 表级配额和 Schema 配额是两层独立限制,任何一层超了都会阻断写入。如果某张表的表级配额设置超过了父 Schema 的配额设置,Vertica 会警告但不会阻止——但如果表写入数据时未超自己配额但 Schema 超了,写入照样失败。建议 Schema 配额 > Σ 表配额,但前提是所有可能增长的大表都设了表配额——如果有表没设配额,它不受表级限制,可以单表吃掉全部 Schema 配额压死其他表。不满足全表覆盖条件时,Schema 配额按实际数据增长预估来设,不要套用 Σ 公式。
- 监控 DISK_QUOTA_USAGES 而不是磁盘空间。磁盘还有空闲不意味着 DiskQuota 安全——配额限制的是 Schema 的逻辑用量,与物理磁盘空间是两套独立的限制机制。磁盘还有 50TB 空闲但 Schema 配额只有 5TB 时,Schema 写到 5TB 就会报错。
- 不要把配额当唯一的容量管理手段。DiskQuota 是最后的防线,不能替代数据生命周期管理。应配合分区策略、冷数据归档、定期清理等手段,让数据自然流动,而不是全靠配额硬阻拦。
- 配额变更纳入变更管理。调高配额容易,调低配额难(低于当前用量虽然语法允许但立即使写入全部失败)。每次调配额前评估:这个 Schema 的数据增长率是多少?半年后会不会再次打满?
- Eon 模式下注意 depot 容量与 DiskQuota 的关系。DiskQuota 统计的是 communal storage 中的实际数据量,depot 缓存不算在内。如果显示用量很低但 communal 读取比例高,可能是 depot 太小导致频繁从 communal 拉数据——这与 DiskQuota 无关,但会造成「配额没用满但查询很慢」的困惑。
- 建立 DiskQuota 用量趋势监控。每天记录
DISK_QUOTA_USAGES的快照,计算日均增长率。当 7 天增长率持续高于 3% 时,提前介入调整配额或清理数据,而不是等到ERROR 10764才行动。
扩展阅读¶
- Vertica 冷热数据管理与成本优化 — 冷数据归档与 DiskQuota 配额释放
- Vertica 表使用情况分析与资源优化 — 识别低效表并及时清理
- Vertica 监控最佳实践 — DISK_QUOTA_USAGES 日常监控
- Vertica 表分区策略选择指南 — 分区策略与 DROP_PARTITIONS 配合使用