Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions internal/inspect/index_stats/index_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ var IndexStatsQuery string

type Result struct {
Name string
Table string
Columns string
Size string
Percent_used string
Index_scans int64
Expand All @@ -41,9 +43,9 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu
return err
}

table := "|Name|Size|Percent used|Index scans|Seq scans|Unused|\n|-|-|-|-|-|-|\n"
table := "|Name|Table|Columns|Size|Percent used|Index scans|Seq scans|Unused|\n|-|-|-|-|-|-|-|-|\n"
for _, r := range result {
table += fmt.Sprintf("|`%s`|`%s`|`%s`|`%d`|`%d`|`%t`|\n", r.Name, r.Size, r.Percent_used, r.Index_scans, r.Seq_scans, r.Unused)
table += fmt.Sprintf("|`%s`|`%s`|`%s`|`%s`|`%s`|`%d`|`%d`|`%t`|\n", r.Name, r.Table, r.Columns, r.Size, r.Percent_used, r.Index_scans, r.Seq_scans, r.Unused)
}
return utils.RenderTable(table)
}
11 changes: 10 additions & 1 deletion internal/inspect/index_stats/index_stats.sql
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
-- Combined index statistics: size, usage percent, seq scans, and mark unused
-- Combined index statistics: size, usage percent, seq scans, mark unused, expose table + columns
WITH idx_sizes AS (
SELECT
i.indexrelid AS oid,
FORMAT('%I.%I', n.nspname, c.relname) AS name,
FORMAT('%I.%I', tn.nspname, tc.relname) AS table_name,
(
SELECT STRING_AGG(pg_get_indexdef(i.indexrelid, ord::int, false), ',' ORDER BY ord)
FROM unnest(i.indkey::int[]) WITH ORDINALITY AS k(attnum, ord)
) AS columns,
pg_relation_size(i.indexrelid) AS index_size_bytes
FROM pg_stat_user_indexes ui
JOIN pg_index i ON ui.indexrelid = i.indexrelid
JOIN pg_class c ON ui.indexrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
JOIN pg_class tc ON tc.oid = i.indrelid
JOIN pg_namespace tn ON tn.oid = tc.relnamespace
WHERE NOT n.nspname LIKE ANY($1)
),
idx_usage AS (
Expand Down Expand Up @@ -37,6 +44,8 @@ usage_pct AS (
)
SELECT
s.name,
s.table_name AS "table",
s.columns,
pg_size_pretty(s.index_size_bytes) AS size,
COALESCE(up.percent_used, 0)::text || '%' AS percent_used,
COALESCE(u.idx_scans, 0) AS index_scans,
Expand Down
2 changes: 2 additions & 0 deletions internal/inspect/index_stats/index_stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ func TestIndexStatsCommand(t *testing.T) {
conn.Query(IndexStatsQuery, reset.LikeEscapeSchema(utils.InternalSchemas)).
Reply("SELECT 1", Result{
Name: "public.test_idx",
Table: "public.test",
Columns: "id",
Size: "1GB",
Percent_used: "50%",
Index_scans: 5,
Expand Down
2 changes: 2 additions & 0 deletions internal/inspect/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ func printSummary(ctx context.Context, outDir string) error {
}
if !match.Valid {
match.String = "-"
} else if len(match.String) > 20 {
match.String = fmt.Sprintf("%d matches", strings.Count(match.String, ",")+1)
}
table += fmt.Sprintf("|`%s`|`%s`|`%s`|\n", r.Name, status, match.String)
}
Expand Down
40 changes: 38 additions & 2 deletions internal/inspect/templates/rules.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ pass = "✔"
fail = "There is at least one unused index"

[[rules]]
query = "SELECT LISTAGG(name, ',') AS match FROM `db_stats.csv` WHERE index_hit_rate < 0.94 OR table_hit_rate < 0.94"
query = "SELECT LISTAGG(i.name, ',') AS match FROM `index_stats.csv` AS i JOIN (SELECT `table`, columns FROM `index_stats.csv` GROUP BY `table`, columns HAVING COUNT(*) > 1) AS d ON i.`table` = d.`table` AND i.columns = d.columns"
name = "No duplicate indexes"
pass = "✔"
fail = "There is at least one duplicate index (same columns on the same table)"

[[rules]]
query = "SELECT 'index: ' || index_hit_rate || ', table: ' || table_hit_rate AS match FROM `db_stats.csv` WHERE index_hit_rate < 0.94 OR table_hit_rate < 0.94"
name = "Check cache hit is within acceptable bounds"
pass = "✔"
fail = "There is a cache hit ratio (table or index) below 94%"
Expand All @@ -31,7 +37,7 @@ pass = "✔"
fail = "At least one table is showing sequential scans more than 10% of total row count"

[[rules]]
query = "SELECT LISTAGG(s.tbl, ',') AS match FROM `vacuum_stats.csv` s WHERE s.expect_autovacuum = 'yes' and s.rowcount > 1000;"
query = "SELECT LISTAGG(s.name, ',') AS match FROM `vacuum_stats.csv` s WHERE s.expect_autovacuum = 'yes' and s.rowcount > 1000;"
name = "No large tables waiting on autovacuum"
pass = "✔"
fail = "At least one table is waiting on autovacuum"
Expand All @@ -41,3 +47,33 @@ query = "SELECT LISTAGG(s.name, ',') AS match FROM `vacuum_stats.csv` s WHERE s.
name = "No tables yet to be vacuumed"
pass = "✔"
fail = "At least one table has never had autovacuum or vacuum run on it"

[[rules]]
query = "SELECT LISTAGG(s.name, ',') AS match FROM `vacuum_stats.csv` s WHERE FLOAT(REPLACE(s.rowcount, ',', '')) > 1000 AND FLOAT(REPLACE(s.dead_rowcount, ',', '')) > 0.2 * FLOAT(REPLACE(s.rowcount, ',', ''))"
name = "No tables with more than 20% dead rows"
pass = "✔"
fail = "At least one table has more than 20% dead rows"

[[rules]]
query = "SELECT LISTAGG(slot_name, ',') AS match FROM `replication_slots.csv` WHERE active = 'f'"
name = "No inactive replication slots"
pass = "✔"
fail = "There is at least one inactive replication slot"

[[rules]]
query = "SELECT LISTAGG(blocked_pid, ',') AS match FROM `blocking.csv`"
name = "No blocked queries"
pass = "✔"
fail = "There is at least one query blocked on another"

[[rules]]
query = "SELECT LISTAGG(pid, ',') AS match FROM `long_running_queries.csv`"
name = "No queries running longer than 5 minutes"
pass = "✔"
fail = "At least one query has been running for more than 5 minutes"

[[rules]]
query = "SELECT LISTAGG(name, ',') AS match FROM `bloat.csv` WHERE bloat > 4"
name = "No tables or indexes with bloat ratio above 4x"
pass = "✔"
fail = "At least one table or index is more than 4x its expected size"
2 changes: 1 addition & 1 deletion pkg/config/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ type (
Seed seed `toml:"seed" json:"seed"`
Settings settings `toml:"settings" json:"settings"`
NetworkRestrictions networkRestrictions `toml:"network_restrictions" json:"network_restrictions"`
SslEnforcement *sslEnforcement `toml:"ssl_enforcement" json:"ssl_enforcement"`
SslEnforcement *sslEnforcement `toml:"ssl_enforcement" json:"ssl_enforcement"`
Vault map[string]Secret `toml:"vault" json:"vault"`
}

Expand Down