PostgreSQL码农集散地

PostgreSQL 19 preview - 修复 pgstattuple 插件因索引页错误导致的统计查询报错

PostgreSQL 19 preview - 修复 pgstattuple 插件因索引页错误导致的统计查询报错

https://github.com/postgres/postgres/commit/684a745f55057edd2365a7125ebcb7a54a4db8f8

PostgreSQL 19 对扩展模块 pgstattuple 进行了一次改进(实际上该补丁针对13+的所有版本),主要内容是提升对索引(hash、gist、btree)的统计报告友好度,避免因为索引页异常导致报错终止,而是更人性化地处理这些情况。

pgstattuple: Improve reports generated for indexes (hash, gist, btree)
pgstattuple checks the state of the pages retrieved for gist and hash
using some check functions from each index AM, respectively
gistcheckpage() and _hash_checkpage().  When these are called, they
would fail when bumping on data that is found as incorrect (like opaque
area size not matching, or empty pages), contrary to btree that simply
discards these cases and continues to aggregate data.

Zero pages can happen after a crash, with these AMs being able to do an
internal cleanup when these are seen.  Also, sporadic failures are
annoying when doing for example a large-scale diagnostic query based on
pgstattuple with a join of pg_class, as it forces one to use tricks like
quals to discard hash or gist indexes, or use a PL wrapper able to catch
errors.

This commit changes the reports generated for btree, gist and hash to
be more user-friendly;
- When seeing an empty page, report it as free space.  This new rule
applies to gist and hash, and already applied to btree.
- For btree, a check based on the size of BTPageOpaqueData is added.
- For gist indexes, gistcheckpage() is not called anymore, replaced by a
check based on the size of GISTPageOpaqueData.
- For hash indexes, instead of _hash_getbuf_with_strategy(), use a
direct call to ReadBufferExtended(), coupled with a check based on
HashPageOpaqueData.  The opaque area size check was already used.
- Pages that do not match these criterias are discarded from the stats
reports generated.

There have been a couple of bug reports over the years that complained
about the current behavior forhash and gist, as being not that useful,
with nothing being done about it.  Hence this change is backpatched down
to v13.

Reported-by: Noah Misch <[email protected]>
Author: Nitin Motiani <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Discussion: https://postgr.es/m/CAH5HC95gT1J3dRYK4qEnaywG8RqjbwDdt04wuj8p39R=HukayA@mail.gmail.com
Backpatch-through: 13

Patch 主要内容

  1. 背景问题

  • 旧版 pgstattuple 在统计 hash/gist 索引时,如果遇到不正常(如空页、opaque 区域大小不符等)的页面会直接报错,导致统计过程终止。
  • btree 索引则遇到这类情况会跳过这些页面继续统计。
  • 由于 crash 后索引可能出现空页,导致大范围统计(比如 join pg_class 跑全库索引统计)经常会遇到报错,实际使用很不方便。
  • 本次改动

    • 遇到空页时,将其标记为 free space(空闲空间),不再报错。这对 gist/hash 索引是新规则,btree 之前就有。
    • btree 索引增加了一个 BTPageOpaqueData 大小的检查。
    • gist 索引不再用 gistcheckpage(),而用 GISTPageOpaqueData 大小检查。
    • hash 索引直接用 ReadBufferExtended() 读取页面,然后用 HashPageOpaqueData 检查(之前就有 opaque 区域大小检查)。
    • 不符合这些条件的页面会被丢弃,不再计入统计报告。
  • 影响

    • 统计 hash/gist 索引时遇到异常页面不再报错,整体统计更加健壮,适合批量或自动化统计场景。
    • 对于 crash 后有空页的场景,统计也能继续完成并提示空闲空间比例。

    例子

    假设你想统计一个表的所有索引的空间利用率:

    SELECT c.relname, pgstattuple(c.oid)  
    FROM pg_class c  
    JOIN pg_index i ON i.indexrelid = c.oid  
    WHERE c.relkind = 'i';  
    • 在旧版本 pgstattuple 上,如果某个 hash/gist 索引有异常页面,这个 SQL 可能直接报错中断,必须手动排查。
    • 应用此 patch 后,遇到异常页面会自动忽略或计为空闲空间,整体 SQL 能顺利执行,并且报告更符合用户预期。

    具体例子

    比如有个 hash 索引因 crash 导致部分页面变成空页,执行:

    SELECT * FROM pgstattuple('my_hash_index');  

    旧版可能报错:

    ERROR:  hash page header size mismatch  

    新版输出中空页会被统计为 free space,整体统计结果依然可用:

    table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent   
    ----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+-------------  
       819200 |        1000 |    102400 |        12.5   |                0 |              0 |                  0 |     716800 |       87.5  

    此时 free_space 会包含空页导致的空间浪费,而不会直接报错。

    总结

    这个 patch 让 pgstattuple 在统计索引空间时对异常页面具有更强的容错能力,提升了批量统计和自动化诊断的实用性。