|
| 1 | +-- Corruption: Full B-tree + heap check (amcheck, takes locks – use on standby!) |
| 2 | +-- Requires: CREATE EXTENSION amcheck |
| 3 | +-- Uses bt_index_parent_check(heapallindexed := true) — thorough, takes locks. |
| 4 | +-- ⚠️ Takes ShareLock on each index. Run on standbys or during maintenance windows. |
| 5 | +-- Checks parent-child consistency, sibling pointers, root descent, unique constraints. |
| 6 | +-- Verifies all heap tuples have corresponding index entries. |
| 7 | +-- On PG14+, also runs verify_heapam() with full TOAST checking. |
| 8 | + |
| 9 | +do $$ |
| 10 | +declare |
| 11 | + rec record; |
| 12 | + corruption record; |
| 13 | + idx_count int := 0; |
| 14 | + err_count int := 0; |
| 15 | + skip_count int := 0; |
| 16 | + tbl_count int := 0; |
| 17 | + tbl_err_count int := 0; |
| 18 | + tbl_skip_count int := 0; |
| 19 | + has_errors boolean; |
| 20 | + pg_version int; |
| 21 | +begin |
| 22 | + -- Check extension |
| 23 | + if not exists (select 1 from pg_extension where extname = 'amcheck') then |
| 24 | + raise notice '❌ amcheck extension is not installed. Run: CREATE EXTENSION amcheck;'; |
| 25 | + return; |
| 26 | + end if; |
| 27 | + |
| 28 | + select current_setting('server_version_num')::int into pg_version; |
| 29 | + |
| 30 | + raise warning '⚠️ This check takes locks! Use on standbys or during maintenance windows.'; |
| 31 | + raise notice ''; |
| 32 | + raise notice '=== Full B-tree index integrity (bt_index_parent_check + heapallindexed) ==='; |
| 33 | + raise notice 'Checking all btree indexes with parent-child + heap verification...'; |
| 34 | + raise notice ''; |
| 35 | + |
| 36 | + for rec in |
| 37 | + select |
| 38 | + n.nspname as schema_name, |
| 39 | + c.relname as index_name, |
| 40 | + t.relname as table_name, |
| 41 | + c.oid as index_oid, |
| 42 | + pg_relation_size(c.oid) as index_size |
| 43 | + from pg_index i |
| 44 | + join pg_class c on c.oid = i.indexrelid |
| 45 | + join pg_class t on t.oid = i.indrelid |
| 46 | + join pg_namespace n on n.oid = c.relnamespace |
| 47 | + join pg_am a on a.oid = c.relam |
| 48 | + where a.amname = 'btree' |
| 49 | + and n.nspname not in ('pg_catalog', 'information_schema') |
| 50 | + and n.nspname !~ '^pg_toast' |
| 51 | + and c.relpersistence != 't' |
| 52 | + and i.indisvalid |
| 53 | + order by pg_relation_size(c.oid) asc -- smallest first |
| 54 | + loop |
| 55 | + begin |
| 56 | + if pg_version >= 140000 then |
| 57 | + perform bt_index_parent_check( |
| 58 | + rec.index_oid, |
| 59 | + heapallindexed := true, |
| 60 | + rootdescend := true, |
| 61 | + checkunique := true |
| 62 | + ); |
| 63 | + elsif pg_version >= 110000 then |
| 64 | + perform bt_index_parent_check( |
| 65 | + rec.index_oid, |
| 66 | + heapallindexed := true, |
| 67 | + rootdescend := true |
| 68 | + ); |
| 69 | + else |
| 70 | + perform bt_index_parent_check(rec.index_oid, heapallindexed := true); |
| 71 | + end if; |
| 72 | + idx_count := idx_count + 1; |
| 73 | + exception |
| 74 | + when insufficient_privilege then |
| 75 | + raise notice '⚠️ Permission denied for %.% — need superuser or amcheck privileges', rec.schema_name, rec.index_name; |
| 76 | + skip_count := skip_count + 1; |
| 77 | + when others then |
| 78 | + raise warning '❌ CORRUPTION in %.% (table %.%, size %): %', |
| 79 | + rec.schema_name, rec.index_name, |
| 80 | + rec.schema_name, rec.table_name, |
| 81 | + pg_size_pretty(rec.index_size), |
| 82 | + sqlerrm; |
| 83 | + err_count := err_count + 1; |
| 84 | + end; |
| 85 | + end loop; |
| 86 | + |
| 87 | + if err_count = 0 and skip_count = 0 then |
| 88 | + raise notice '✅ All % btree indexes passed full integrity check.', idx_count; |
| 89 | + elsif err_count = 0 then |
| 90 | + raise notice '✅ % btree indexes passed, % skipped (insufficient privileges).', idx_count, skip_count; |
| 91 | + else |
| 92 | + raise warning '❌ % of % btree indexes have corruption!', err_count, idx_count + err_count + skip_count; |
| 93 | + end if; |
| 94 | + |
| 95 | + -- Full heap verification (PG14+ only) |
| 96 | + if pg_version >= 140000 then |
| 97 | + raise notice ''; |
| 98 | + raise notice '=== Full heap integrity (verify_heapam + TOAST) ==='; |
| 99 | + raise notice 'Checking all user tables for heap and TOAST corruption...'; |
| 100 | + raise notice ''; |
| 101 | + |
| 102 | + for rec in |
| 103 | + select |
| 104 | + n.nspname as schema_name, |
| 105 | + c.relname as table_name, |
| 106 | + c.oid as table_oid, |
| 107 | + pg_relation_size(c.oid) as table_size |
| 108 | + from pg_class c |
| 109 | + join pg_namespace n on n.oid = c.relnamespace |
| 110 | + where c.relkind = 'r' |
| 111 | + and n.nspname not in ('pg_catalog', 'information_schema') |
| 112 | + and c.relpersistence != 't' |
| 113 | + order by n.nspname, c.relname |
| 114 | + loop |
| 115 | + has_errors := false; |
| 116 | + begin |
| 117 | + for corruption in |
| 118 | + select * from verify_heapam( |
| 119 | + rec.table_oid, |
| 120 | + on_error_stop := false, |
| 121 | + check_toast := true, |
| 122 | + skip := 'none' |
| 123 | + ) |
| 124 | + loop |
| 125 | + if not has_errors then |
| 126 | + raise warning '❌ CORRUPTION in %.% (size %):', rec.schema_name, rec.table_name, pg_size_pretty(rec.table_size); |
| 127 | + has_errors := true; |
| 128 | + tbl_err_count := tbl_err_count + 1; |
| 129 | + end if; |
| 130 | + raise warning ' block %, offset %, attnum %: %', |
| 131 | + corruption.blkno, corruption.offnum, corruption.attnum, corruption.msg; |
| 132 | + end loop; |
| 133 | + exception |
| 134 | + when insufficient_privilege then |
| 135 | + raise notice '⚠️ Permission denied for %.% — need superuser or amcheck privileges', rec.schema_name, rec.table_name; |
| 136 | + tbl_skip_count := tbl_skip_count + 1; |
| 137 | + when others then |
| 138 | + raise warning 'ERROR checking %.%: %', rec.schema_name, rec.table_name, sqlerrm; |
| 139 | + tbl_err_count := tbl_err_count + 1; |
| 140 | + end; |
| 141 | + tbl_count := tbl_count + 1; |
| 142 | + end loop; |
| 143 | + |
| 144 | + if tbl_err_count = 0 and tbl_skip_count = 0 then |
| 145 | + raise notice '✅ All % tables passed full heap integrity check.', tbl_count; |
| 146 | + elsif tbl_err_count = 0 then |
| 147 | + raise notice '✅ % tables passed, % skipped (insufficient privileges).', tbl_count - tbl_skip_count, tbl_skip_count; |
| 148 | + else |
| 149 | + raise warning '❌ % of % tables have corruption!', tbl_err_count, tbl_count; |
| 150 | + end if; |
| 151 | + else |
| 152 | + raise notice ''; |
| 153 | + raise notice 'ℹ️ Heap verification (verify_heapam) requires PostgreSQL 14+. Skipped.'; |
| 154 | + end if; |
| 155 | +end; |
| 156 | +$$; |
0 commit comments