feat(pg_table_bloat): add approx and exact methods via pgstattuple#19
feat(pg_table_bloat): add approx and exact methods via pgstattuple#19HighviewOne wants to merge 4 commits into
Conversation
Closes #10. pg_advisor.tables_without_primary_key filtered relkind IN ('r','p') and silently missed foreign tables (relkind='f'). A foreign table without a defined PK is the same design-drift signal as a heap table -- the PK is not enforced by the FDW but it communicates schema intent. Expand to relkind IN ('r','p','f'). pg_describe_table already had the 'foreign_table' branch in its CASE expression but no fixture exercised it, leaving that branch untested. Fixture additions (fixtures.ts): - setupFdwFixture(): best-effort self-referential postgres_fdw setup that parses DATABASE_URL and creates a SERVER, USER MAPPING, and foreign table remote_users pointing back at the same cluster. Returns false when postgres_fdw is unavailable so dependent tests skip gracefully. - fdwFixtureAvailable(): exported flag read by tests to skip FDW assertions. - setupFixtures() calls setupFdwFixture() at the end; teardownFixtures() drops the cluster-level SERVER (schema CASCADE handles the foreign table). New tests: - pg_advisor: remote_users (no defined PK) appears in tables_without_primary_key - pg_describe_table: remote_users has kind='foreign_table' with columns - pg_list_tables: assertion updated to include remote_users when FDW is available 88 integration tests pass (87 existing + 1 new).
|
Review punch list for this PR. Item 1 is the blocker. Note this PR is stacked on #18 (carries its commits), so it also inherits #18's blocker (foreign tables in
🤖 Generated with Claude Code |
PostgreSQL forbids PRIMARY KEY (and UNIQUE) constraints on foreign tables
entirely, so including relkind='f' in tables_without_primary_key produced
permanently unresolvable findings with no remediation path.
- admin.ts: drop 'f' from relkind IN ('r', 'p', 'f'); add comment
explaining the exclusion; update tool description accordingly
- admin.integration.test.ts: flip assertion -- remote_users (foreign
table) must NOT appear in tables_without_primary_key
- fixtures.ts: add console.error on FDW statement failure so skips are
debuggable; omit password option entirely for trust-auth setups
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes #12. pg_stat_user_tables dead-tuple counts are ANALYZE-driven estimates. The pgstattuple contrib extension provides exact dead-tuple counts (pgstattuple, full table scan) and fast sampling approximations (pgstattuple_approx). Add a `method` parameter with three values: - 'estimate' (default): existing pg_stat_user_tables path, no extension needed - 'approx': CROSS JOIN LATERAL pgstattuple_approx(relid::regclass) - 'exact': CROSS JOIN LATERAL pgstattuple(relid::regclass) Pattern mirrors pg_explain's HypoPG handling: check extension presence first, return a clear install-instructions error when absent, fall through to the estimate path when method='estimate' regardless of extension state. Re-applies the Zod default ("estimate") after destructuring so direct handler callers (integration tests, library consumers) that bypass Zod don't silently fall into the pgstattuple path -- same pattern as pg_explain's analyze/format defaults. The result shape is identical across all three methods (schema, table, live_tuples, dead_tuples, dead_ratio, size_pretty, size_bytes, last_vacuum, last_autovacuum, last_analyze) so callers can switch methods without changing response parsing. Integration tests added for approx and exact, gated on extension availability so they run on any cluster where pgstattuple is installed and skip cleanly where it isn't. 91 integration tests pass.
…crash, align sizes
Blocker fix:
- CROSS JOIN LATERAL pgstattuple/pgstattuple_approx now joins pg_class and
filters to relkind IN ('r', 'm') before the lateral; pg_stat_user_tables
includes partitioned parents (relkind='p') which raise an error in both
pgstattuple functions -- this caused method='approx'/'exact' to hard-fail
on any database containing a partitioned table
Additional fixes:
- size_pretty / size_bytes: pgstattuple paths now use
pg_total_relation_size (heap + indexes + toast), matching the estimate
path; previously used p.table_len (main fork only) -- same field names,
different meanings
- description: add explicit note to always pass schema with method='exact'
to avoid statement_timeout on full-DB scans
- admin.ts: replace dummy `const resultType = {} as {...}; void resultType`
with a named BloatRow type
- integration test title corrected: was "returns an error when pgstattuple
is not installed" but asserted ok===true; now "succeeds regardless of
pgstattuple extension state"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
a61db8f to
4ec6847
Compare
|
Addressed all review points. Both blockers resolved (including the inherited #18 blocker):
|
Closes #12.
What
pg_table_bloatusedpg_stat_user_tablesdead-tuple counts, which are ANALYZE-driven estimates. Thepgstattuplecontrib extension provides exact counts (pgstattuple, full scan) and fast sampling approximations (pgstattuple_approx).Changes
src/tools/admin.ts— adds amethodparameter:'estimate'(default): existingpg_stat_user_tablespath, no extension needed'approx':CROSS JOIN LATERAL pgstattuple_approx(relid::regclass)'exact':CROSS JOIN LATERAL pgstattuple(relid::regclass)Pattern mirrors
pg_explain's HypoPG handling: check extension presence first, return a clear install-instructions error when absent, leave the'estimate'path untouched when the extension isn't installed.The result shape is identical across all three methods so callers can switch without changing response parsing.
Re-applies the Zod default (
'estimate') after destructuring so direct handler callers that bypass Zod don't silently fall into the pgstattuple path — same pattern aspg_explain'sanalyze/formatdefaults.Integration tests — three new cases:
method='approx': passes on clusters with pgstattuple, checks response shape; verifies not-installed error message otherwisemethod='exact': samemethod='estimate': always succeeds regardless of extension state91 integration tests pass.
🤖 Generated with Claude Code