在PostgreSQL的日常运维中,当我们需要对表进行物理排序以匹配某个索引的顺序时,通常会使用CLUSTER命令。这个操作会重写整个表文件并重建所有索引,对于数据量庞大的表来说,执行时间可能从几分钟延伸到数小时。为了不让运维人员盲目等待,PostgreSQL在较新版本中提供了pg_stat_progress_cluster系统视图,专门用于暴露聚簇操作的实时内部状态。

pg_stat_progress_cluster视图的字段含义
pg_stat_progress_cluster视图为每一个正在执行CLUSTER或者VACUUM FULL(因为VACUUM FULL内部也走聚簇式的表重写)的 backend 进程返回一行记录。最核心的字段包括pid、datname、relid、command、phase、cluster_index_relid、heap_tuples_scanned、heap_tuples_written、heap_blks_total、heap_blks_scanned等。其中pid表示执行进程的标识符,relid是正在被聚簇的表OID,command则区分当前是CLUSTER还是VACUUM FULL。
phase字段尤其重要,它描述了当前所处的阶段,例如初始化、扫描堆、排序元组、写入新堆、重建索引、清理等。每个阶段的工作性质不同,对I/O和CPU的压力也不一样。heap_blks_total代表表总共包含的堆块数量,heap_blks_scanned则是已经扫描过的堆块数,通过两者比值就能估算扫描进度。heap_tuples_scanned和heap_tuples_written反映了元组级别的统计,用于更细粒度地观察写入吞吐。
理解这些字段是监控的基础。很多人在查看时只盯着pid和relid,却忽略了phase的变化规律。实际上,当phase停留在“building index”时,说明表数据已经写完,正在重建索引,此时heap_blks_scanned等于total但进度看似不往前走,容易误判为卡死。只有结合phase与索引数量,才能正确解读系统真实负载。
通过SQL查询计算实时完成度
仅仅查看原始视图还不够直观,我们可以借助一个简单的查询将堆块扫描比例换算成百分比。下面的示例展示了如何关联pg_class获取表名,并计算出扫描进度。这种方式能够在psql中反复执行,也可以被监控脚本定时抓取。
SELECT
c.relname AS table_name,
p.phase,
p.heap_blks_total,
p.heap_blks_scanned,
CASE
WHEN p.heap_blks_total > 0
THEN (p.heap_blks_scanned * 100 / p.heap_blks_total)
ELSE 0
END AS scan_percent
FROM pg_stat_progress_cluster p
JOIN pg_class c ON p.relid = c.oid
WHERE p.command = 'CLUSTER';
上述查询在堆扫描阶段非常有效,但要注意如果phase已经进入索引构建,堆相关的字段不再更新,此时进度应参考索引重建的状态。对于包含多个索引的大表,可以额外关联pg_index来统计待重建索引个数,从而粗略估算整体任务的剩余时间。实践中建议每五到十秒采样一次,避免频繁查询对系统造成额外压力。
除了自己写SQL,也可以把采样数据写入临时表,利用前后两次的差值计算元组写入速率。例如用heap_tuples_written的差除以时间间隔,就能得出每秒写入元组数,进而推算剩余时长。这种基于速率的预测比单纯比例更准确,因为它考虑了索引阶段写入放缓的情况。
聚簇进度监控中的常见误区与应对
一个典型的误区是认为pg_stat_progress_cluster里的进度等同于整个CLUSTER命令的完成度。事实上,表重写只是前半段,后续的索引重建和统计信息更新同样消耗时间。如果在堆扫描完成后就通知业务恢复,往往会导致索引仍在建、查询变慢的尴尬局面。正确做法是将phase是否为“final cleanup”作为结束信号。
另一个问题是多人同时对不同表聚簇时,视图会返回多行,如果脚本没有按pid或relid区分,就容易把不同表的进度混在一起计算,得出超过百分之一百的荒谬结果。因此在编写监控逻辑时,务必以relid加pid作为分组依据,并为每个表单独维护进度状态机。
此外,当发现heap_blks_scanned长时间不增长,也不要立刻取消任务。应先检查phase是否位于排序或索引构建,并利用系统视图pg_stat_activity确认该pid的wait_event类型。若是IO等待,可能是存储吞吐瓶颈;若是CPU受限,则应考虑降低并发聚簇任务数。只有综合进度视图与系统负载,才能做出合理决策。
结合自动化脚本提升运维效率
在拥有数十张大表的仓库环境中,手动执行查询并不现实。我们可以把前面提到的SQL封装进一个Shell或Python脚本,由定时任务调用,并将结果推送到监控面板。脚本逻辑应包括:发现正在进行聚簇的表、计算各阶段进度、超时未推进则告警。这样DBA无需登录数据库也能掌握全局。
import psycopg2
conn = psycopg2.connect(dbname='ipipp', user='admin')
cur = conn.cursor()
cur.execute("""
SELECT c.relname, p.phase, p.heap_blks_scanned, p.heap_blks_total
FROM pg_stat_progress_cluster p
JOIN pg_class c ON p.relid = c.oid
""")
for row in cur.fetchall():
name, phase, scanned, total = row
if total and total > 0:
print(f'{name} {phase} {scanned*100//total}%')
cur.close()
conn.close()
上面的Python片段演示了如何从视图拉取数据并本地打印进度。在生产中可替换为写入Prometheus或发送企业微信通知。通过把pg_stat_progress_cluster纳入标准运维工具链,我们能够把不可见的长时间维护操作变得透明可控,显著降低误操作风险。
最后需要强调的是,该视图只在聚簇进行中才有数据,命令结束行即消失。若想做历史分析,必须在运行期间持续采集。结合日志中的命令开始与结束时间戳,便能形成完整的维护审计记录,为后续容量规划提供依据。
pg_stat_progress_clusterPostgreSQL聚簇进度监控修改时间:2026-08-16 23:46:33