跳转至

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 返回:

ERROR 10764: [53100] Disk Quota Exceeded for the Schema object <schema_name>

配额检查发生在操作开始前——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_namesql_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:临时调高配额(紧急止损)

适用场景:配额超限导致业务写入失败,需要立刻恢复。

-- 将 Schema 配额从当前值调大
ALTER SCHEMA your_schema DISK_QUOTA '5T';

为什么是这个值:新配额至少应是当前用量的 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 而不是 DELETEDROP_PARTITIONS 直接从存储元数据中移除分区数据,不产生 delete vectors,配额空间立即可用。而 DELETE FROM ... WHERE ... 会产生 delete vectors,空间要等 Mergeout 回收。DROP_PARTITIONS 支持范围删除(min, max),比逐分区调用 DROP_PARTITION 更高效。

4.2 短期优化(当周执行)

方案 D:为高增长表设置独立配额

适用场景:Schema 中有某几张表增长特别快,想单独控制而不是一起受限于 Schema 配额。

-- 为单表设置 500GB 配额
ALTER TABLE your_schema.fast_growing_table DISK_QUOTA '500G';

为什么需要表级配额:Schema 级配额是一个总闸门,所有表共享。当只有一张表在疯狂增长时,它会吃掉所有配额让其他表也无法写入。表级配额可以「隔离故障域」——限制住失控的表,保护同 Schema 下其他表的正常写入。

⚠️ 注意:表级配额不应超过父 Schema 配额(否则 Vertica 警告)。建议 Schema 配额 > Σ 表配额,但前提是所有可能增长的大表都设了表配额——若有表未设配额,它不受表级限制,可单表吃光 Schema 配额。

方案 E:归档冷数据到外部存储

适用场景:大量历史数据很少被查询,但占据配额。

详见 Vertica 冷热数据管理与成本优化

简要思路:

  1. 使用 projection_usage 表识别冷表
  2. 将冷数据导出到 HDFS/S3/对象存储
  3. 在原 Schema 中删除冷表或冷分区
  4. 通过外部表 + 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 而不是全放一起

  1. 故障隔离:一个 Schema 的配额超限不影响其他 Schema
  2. 精细管控:不同生命周期的数据用不同配额
  3. 降低检查开销:每个 Schema 表数减少,配额检查效率提升
  4. 运维友好:清理到期数据时可通过 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 的加载量。

修复方案

  1. 临时:调高配额至 15TB 让当日 ETL 继续运行
  2. 短期:对 call_detail_record 执行 PURGE_PROJECTION(),delete vectors 占比从 12% 降至 < 1% 后用量降至约 8.6TB
  3. 长期:改用 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 配额根本没有考虑新业务的增长。

修复方案

  1. 立即:将 Schema 配额调到 5TB
  2. 短期:为新增的 5 张明细表分别设置表级配额(rt_detail_transaction: 1.5TB, 其余各 500GB),在 Schema 总配额内做到表级隔离
  3. 长期:将实时风控相关的 5 张明细表拆分到独立的 rt_fraud_detection Schema,配置 3TB 配额
  4. 流程改进:新增数据表上线前必须评估存储增长量,确认配额余量是否充足

效果对比

指标 修复前 修复后
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,每次写入前都要走配额检查逻辑。

修复方案

  1. 将 2000+ 张表按业务子模块拆分到 4 个子 Schema
  2. 每个子 Schema 独立设置 DiskQuota
  3. 原 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';

输出:

object_name          | is_schema | quota_gb | used_gb
marketing_campaign   | t         | 500      | 512

判断:已超配额(512GB > 500GB),需要立即止血并定位根因。

T+5min — 临时调高配额,恢复写入

ALTER SCHEMA marketing_campaign DISK_QUOTA '800G';

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_logpromotion_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 — 制定长期方案

  1. 将配额从临时值 800GB 调回合理值 600GB(380GB 实际 × 1.5 倍缓冲)
  2. campaign_event_log 改为按月分区,过期活动用 DROP_PARTITIONS 清理(从源头消除 delete vectors)
  3. 建立 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. 配额设为实际用量的 1.3–1.5 倍。这不是给 Tuple Mover 留空间(TM 豁免配额),而是给批量加载的 peak 时段留缓冲。配额卡在刚好等于日常用量时,任何一次加载批次稍大就会触发 ERROR 10764。1.3× 保证单日峰值不超限,1.5× 覆盖 2-3 天的小幅增长窗口,给清理操作留出反应时间。
  2. 每个 Schema 的表数控制在 1000 以内。超过 1000 张表共享一个配额时,每次 DML/加载都走配额检查,在高并发 ETL 场景下累积开销不可忽略。如果确实有很多表,按业务子模块拆分成多个 Schema。
  3. PURGE_PROJECTION 是应急手段,不要列入定时任务。正常情况下 AHM 自行推进、Tuple Mover 逐步回收 delete vectors,无需人工干预。PURGE 会锁表并产生大量 IO,拖慢调度链。仅在 delete vectors 异常积压(占比 > 20%)导致配额告急时手动执行,执行前先确认 GET_AHM_EPOCH() 已推进到 delete vectors 所在 epoch 之后。
  4. 优先使用 DROP_PARTITIONS 而非 DELETE 清理数据DROP_PARTITIONS 直接从存储元数据中移除分区数据,空间立即回收且计入配额释放。DELETE 产生 delete vectors,空间要等 Mergeout/PURGE 才回收。
  5. 表级配额和 Schema 配额是两层独立限制,任何一层超了都会阻断写入。如果某张表的表级配额设置超过了父 Schema 的配额设置,Vertica 会警告但不会阻止——但如果表写入数据时未超自己配额但 Schema 超了,写入照样失败。建议 Schema 配额 > Σ 表配额,但前提是所有可能增长的大表都设了表配额——如果有表没设配额,它不受表级限制,可以单表吃掉全部 Schema 配额压死其他表。不满足全表覆盖条件时,Schema 配额按实际数据增长预估来设,不要套用 Σ 公式。
  6. 监控 DISK_QUOTA_USAGES 而不是磁盘空间。磁盘还有空闲不意味着 DiskQuota 安全——配额限制的是 Schema 的逻辑用量,与物理磁盘空间是两套独立的限制机制。磁盘还有 50TB 空闲但 Schema 配额只有 5TB 时,Schema 写到 5TB 就会报错。
  7. 不要把配额当唯一的容量管理手段。DiskQuota 是最后的防线,不能替代数据生命周期管理。应配合分区策略、冷数据归档、定期清理等手段,让数据自然流动,而不是全靠配额硬阻拦。
  8. 配额变更纳入变更管理。调高配额容易,调低配额难(低于当前用量虽然语法允许但立即使写入全部失败)。每次调配额前评估:这个 Schema 的数据增长率是多少?半年后会不会再次打满?
  9. Eon 模式下注意 depot 容量与 DiskQuota 的关系。DiskQuota 统计的是 communal storage 中的实际数据量,depot 缓存不算在内。如果显示用量很低但 communal 读取比例高,可能是 depot 太小导致频繁从 communal 拉数据——这与 DiskQuota 无关,但会造成「配额没用满但查询很慢」的困惑。
  10. 建立 DiskQuota 用量趋势监控。每天记录 DISK_QUOTA_USAGES 的快照,计算日均增长率。当 7 天增长率持续高于 3% 时,提前介入调整配额或清理数据,而不是等到 ERROR 10764 才行动。

扩展阅读