pg_reports 0.5.4 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -0
- data/README.md +123 -370
- data/app/controllers/pg_reports/dashboard_controller.rb +21 -21
- data/app/views/layouts/pg_reports/application.html.erb +135 -69
- data/app/views/pg_reports/dashboard/_show_modals.html.erb +22 -22
- data/app/views/pg_reports/dashboard/_show_scripts.html.erb +105 -55
- data/app/views/pg_reports/dashboard/_show_styles.html.erb +49 -11
- data/app/views/pg_reports/dashboard/index.html.erb +123 -114
- data/app/views/pg_reports/dashboard/show.html.erb +30 -26
- data/config/locales/en.yml +597 -0
- data/config/locales/ru.yml +562 -0
- data/config/locales/uk.yml +607 -0
- data/lib/pg_reports/compatibility.rb +63 -0
- data/lib/pg_reports/configuration.rb +2 -0
- data/lib/pg_reports/dashboard/reports_registry.rb +112 -5
- data/lib/pg_reports/definitions/indexes/fk_without_indexes.yml +30 -0
- data/lib/pg_reports/definitions/indexes/index_correlation.yml +31 -0
- data/lib/pg_reports/definitions/indexes/inefficient_indexes.yml +45 -0
- data/lib/pg_reports/definitions/queries/temp_file_queries.yml +39 -0
- data/lib/pg_reports/definitions/schema_analysis/always_null_columns.yml +31 -0
- data/lib/pg_reports/definitions/schema_analysis/unused_columns.yml +32 -0
- data/lib/pg_reports/definitions/system/wraparound_risk.yml +31 -0
- data/lib/pg_reports/definitions/tables/tables_without_pk.yml +28 -0
- data/lib/pg_reports/definitions/tables/unused_tables.yml +30 -0
- data/lib/pg_reports/definitions/tables/update_hotspots.yml +32 -0
- data/lib/pg_reports/engine.rb +6 -0
- data/lib/pg_reports/module_generator.rb +2 -1
- data/lib/pg_reports/modules/indexes.rb +3 -0
- data/lib/pg_reports/modules/queries.rb +1 -0
- data/lib/pg_reports/modules/schema_analysis.rb +261 -2
- data/lib/pg_reports/modules/system.rb +27 -0
- data/lib/pg_reports/modules/tables.rb +1 -0
- data/lib/pg_reports/query_monitor.rb +64 -36
- data/lib/pg_reports/report_definition.rb +20 -24
- data/lib/pg_reports/sql/indexes/fk_without_indexes.sql +23 -0
- data/lib/pg_reports/sql/indexes/index_correlation.sql +27 -0
- data/lib/pg_reports/sql/indexes/inefficient_indexes.sql +22 -0
- data/lib/pg_reports/sql/queries/temp_file_queries.sql +16 -0
- data/lib/pg_reports/sql/schema_analysis/always_null_columns.sql +25 -0
- data/lib/pg_reports/sql/schema_analysis/unused_columns.sql +36 -0
- data/lib/pg_reports/sql/system/checkpoint_stats.sql +20 -0
- data/lib/pg_reports/sql/system/checkpoint_stats_legacy.sql +19 -0
- data/lib/pg_reports/sql/system/wraparound_risk.sql +21 -0
- data/lib/pg_reports/sql/tables/tables_without_pk.sql +20 -0
- data/lib/pg_reports/sql/tables/unused_tables.sql +19 -0
- data/lib/pg_reports/sql/tables/update_hotspots.sql +26 -0
- data/lib/pg_reports/version.rb +1 -1
- data/lib/pg_reports.rb +5 -0
- metadata +24 -1
data/config/locales/en.yml
CHANGED
|
@@ -11,6 +11,7 @@ en:
|
|
|
11
11
|
- "High mean time can be caused by: missing index, suboptimal plan, locks, disk I/O load."
|
|
12
12
|
- "For composite indexes, column order matters — index (a, b) is effective for WHERE a = ? and WHERE a = ? AND b = ?, but not for WHERE b = ?."
|
|
13
13
|
- "Queries with LIKE '%pattern%' cannot use B-tree indexes — consider pg_trgm or full-text search."
|
|
14
|
+
ai_prompt: "Optimize these slow PostgreSQL queries. For each query: analyze the query plan, identify missing indexes, suggest query rewrites. If an index is needed, generate a Rails migration. If the query can be rewritten, show the optimized version."
|
|
14
15
|
|
|
15
16
|
heavy_queries:
|
|
16
17
|
title: "Heavy Queries"
|
|
@@ -21,6 +22,7 @@ en:
|
|
|
21
22
|
- "N+1 problem: if you see many similar queries with different IDs — this is a sign of N+1, need eager loading."
|
|
22
23
|
- "Consider batch operations instead of multiple single queries."
|
|
23
24
|
- "Prepared statements can reduce parsing overhead for frequent queries."
|
|
25
|
+
ai_prompt: "Reduce the call frequency of these heavy PostgreSQL queries. For each query: identify the caller in application code, suggest caching strategies (Redis/Memcached), detect N+1 patterns and suggest eager loading fixes. Show code changes needed."
|
|
24
26
|
|
|
25
27
|
expensive_queries:
|
|
26
28
|
title: "Expensive Queries"
|
|
@@ -30,6 +32,7 @@ en:
|
|
|
30
32
|
- "A query can be fast but called so often that it takes a lot of time in total."
|
|
31
33
|
- "Optimizing expensive queries has the greatest effect on overall system performance."
|
|
32
34
|
- "Consider: adding indexes, rewriting the query, caching results, materialized views."
|
|
35
|
+
ai_prompt: "Optimize these expensive PostgreSQL queries that consume the most total database time. For each query: analyze the execution plan, suggest indexes or query rewrites, estimate the expected improvement. Generate Rails migrations for any new indexes."
|
|
33
36
|
|
|
34
37
|
missing_index_queries:
|
|
35
38
|
title: "Missing Index Queries"
|
|
@@ -40,6 +43,7 @@ en:
|
|
|
40
43
|
- "Composite index (a, b, c) covers: WHERE a, WHERE a AND b, WHERE a AND b AND c, but does NOT cover WHERE b or WHERE c."
|
|
41
44
|
- "EXPLAIN ANALYZE will show the actual query execution plan."
|
|
42
45
|
- "Partial indexes (CREATE INDEX ... WHERE condition) save space and speed up specific queries."
|
|
46
|
+
ai_prompt: "Add missing indexes for these PostgreSQL queries that perform sequential scans. For each table: analyze the query pattern, determine optimal index columns, generate a Rails migration with add_index. Consider partial indexes where appropriate."
|
|
43
47
|
|
|
44
48
|
low_cache_hit_queries:
|
|
45
49
|
title: "Low Cache Hit Queries"
|
|
@@ -50,6 +54,18 @@ en:
|
|
|
50
54
|
- "Low cache hit may indicate: insufficient shared_buffers, tables too large, inefficient queries scanning too much data."
|
|
51
55
|
- "Increasing shared_buffers helps, but there's a limit to effectiveness (typically 25% of RAM)."
|
|
52
56
|
- "Covering indexes (INCLUDE) allow index-only scans without accessing the table."
|
|
57
|
+
ai_prompt: "Improve cache hit ratio for these PostgreSQL queries. For each query: suggest covering indexes (with INCLUDE columns) to enable index-only scans, or suggest query rewrites to reduce the data scanned. Generate Rails migrations for new indexes."
|
|
58
|
+
|
|
59
|
+
temp_file_queries:
|
|
60
|
+
title: "Temp File Queries"
|
|
61
|
+
what: "Queries that spill intermediate results to temporary files on disk."
|
|
62
|
+
how: "Analyzes temp_blks_written and temp_blks_read from pg_stat_statements. Temporary files are created when work_mem is insufficient for sorting, hashing, or materializing."
|
|
63
|
+
nuances:
|
|
64
|
+
- "Temp file writes are orders of magnitude slower than in-memory operations."
|
|
65
|
+
- "Increasing work_mem can eliminate temp files, but affects all sessions — use SET LOCAL for specific queries."
|
|
66
|
+
- "Common causes: large sorts (ORDER BY), hash joins on big tables, DISTINCT on many rows, complex CTEs."
|
|
67
|
+
- "Consider adding indexes to avoid sorts, or rewriting queries to reduce intermediate result sets."
|
|
68
|
+
ai_prompt: "Fix these PostgreSQL queries that spill to temporary files on disk. For each query: identify the cause (large sort, hash join, DISTINCT), suggest an index to avoid the sort, or suggest SET LOCAL work_mem for specific code paths. Show the code changes."
|
|
53
69
|
|
|
54
70
|
all_queries:
|
|
55
71
|
title: "All Queries"
|
|
@@ -70,6 +86,7 @@ en:
|
|
|
70
86
|
- "Indexes are still updated on INSERT/UPDATE/DELETE, even if not used for reading — this is overhead."
|
|
71
87
|
- "Unique indexes (UNIQUE) may have 0 scans but are needed for constraints."
|
|
72
88
|
- "Foreign key indexes are important for DELETE/UPDATE performance on parent tables."
|
|
89
|
+
ai_prompt: "Remove these unused PostgreSQL indexes. For each index: generate a Rails migration with remove_index. Group related indexes into one migration. Add a safety comment noting the index can be recreated if needed."
|
|
73
90
|
|
|
74
91
|
duplicate_indexes:
|
|
75
92
|
title: "Duplicate Indexes"
|
|
@@ -79,6 +96,7 @@ en:
|
|
|
79
96
|
- "Duplicates waste disk space and slow down INSERT/UPDATE/DELETE."
|
|
80
97
|
- "However, sometimes a 'duplicate' is needed: different index types (B-tree vs GIN), partial vs full index."
|
|
81
98
|
- "Index (a, b) does NOT fully replace (b, a) — column order is critical."
|
|
99
|
+
ai_prompt: "Remove these duplicate PostgreSQL indexes. For each pair: determine which index is redundant (the shorter prefix), generate a Rails migration with remove_index for the redundant one. Verify the remaining index covers all use cases."
|
|
82
100
|
|
|
83
101
|
invalid_indexes:
|
|
84
102
|
title: "Invalid Indexes"
|
|
@@ -88,6 +106,7 @@ en:
|
|
|
88
106
|
- "Usually appear after a failed CREATE INDEX CONCURRENTLY."
|
|
89
107
|
- "Solution: DROP INDEX and recreate."
|
|
90
108
|
- "REINDEX CONCURRENTLY can help for existing invalid indexes (PostgreSQL 12+)."
|
|
109
|
+
ai_prompt: "Fix these invalid PostgreSQL indexes. For each index: generate a Rails migration that drops the invalid index and recreates it using `algorithm: :concurrently` to avoid locking the table."
|
|
91
110
|
|
|
92
111
|
missing_indexes:
|
|
93
112
|
title: "Missing Indexes"
|
|
@@ -97,6 +116,7 @@ en:
|
|
|
97
116
|
- "Not all seq_scans are bad — for small tables it's optimal."
|
|
98
117
|
- "Analyze specific queries via pg_stat_statements to understand which columns to index."
|
|
99
118
|
- "Use EXPLAIN ANALYZE to verify if an index will be used."
|
|
119
|
+
ai_prompt: "Add missing indexes for these PostgreSQL tables with high sequential scan counts. For each table: analyze the common query patterns, determine optimal index columns, generate a Rails migration with add_index using `algorithm: :concurrently`."
|
|
100
120
|
|
|
101
121
|
index_usage:
|
|
102
122
|
title: "Index Usage"
|
|
@@ -107,6 +127,18 @@ en:
|
|
|
107
127
|
- "idx_tup_read — rows read from the index."
|
|
108
128
|
- "idx_tup_fetch — rows fetched from table after index scan (for non-covering indexes)."
|
|
109
129
|
|
|
130
|
+
inefficient_indexes:
|
|
131
|
+
title: "Inefficient Indexes"
|
|
132
|
+
what: "Indexes that are used but scan far more entries than they actually fetch."
|
|
133
|
+
how: "Compares idx_tup_read (index entries scanned) vs idx_tup_fetch (heap rows fetched) from pg_stat_user_indexes. A high ratio means the index column order doesn't match query predicates, forcing PostgreSQL to scan large index ranges."
|
|
134
|
+
nuances:
|
|
135
|
+
- "A read-to-fetch ratio >10 means the index reads 10x more entries than it returns — strong sign of misaligned column order."
|
|
136
|
+
- "For composite indexes (a, b, c), queries filtering on (b) or (c) without (a) cannot seek directly and must scan wider ranges."
|
|
137
|
+
- "Solution: create a targeted index with columns matching the most selective WHERE predicates first."
|
|
138
|
+
- "Use EXPLAIN ANALYZE to confirm the index scan is reading excessive entries before making changes."
|
|
139
|
+
- "Index-only scans (where idx_tup_fetch = 0) are excluded — they indicate covering indexes working efficiently."
|
|
140
|
+
ai_prompt: "Fix these inefficient PostgreSQL index scans. For each index: analyze the index definition and identify the misaligned column order, create a new targeted index with columns matching the most selective WHERE predicates first, generate a Rails migration that adds the new index and removes the old one."
|
|
141
|
+
|
|
110
142
|
bloated_indexes:
|
|
111
143
|
title: "Bloated Indexes"
|
|
112
144
|
what: "Indexes with high bloat level due to dead tuples."
|
|
@@ -116,6 +148,30 @@ en:
|
|
|
116
148
|
- "Regular VACUUM keeps bloat at acceptable levels."
|
|
117
149
|
- "pg_repack allows rebuilding indexes without locks."
|
|
118
150
|
- "Bloat >30-50% — reason for action."
|
|
151
|
+
ai_prompt: "Rebuild these bloated PostgreSQL indexes. Generate a rake task or Rails migration that runs REINDEX CONCURRENTLY for each bloated index to reclaim disk space without locking the table."
|
|
152
|
+
|
|
153
|
+
fk_without_indexes:
|
|
154
|
+
title: "FK Without Indexes"
|
|
155
|
+
what: "Foreign keys on child tables that lack a supporting index."
|
|
156
|
+
how: "Compares pg_constraint (foreign keys) with pg_index to find FK columns without a matching leading index column."
|
|
157
|
+
nuances:
|
|
158
|
+
- "Without an index, DELETE or UPDATE on the parent table triggers a sequential scan on the child table."
|
|
159
|
+
- "On large child tables this can cause severe lock contention and slow cascading operations."
|
|
160
|
+
- "The index must have the FK column as the leading (first) column to be effective."
|
|
161
|
+
- "Small tables (< 10K rows) may not benefit from adding an index — PostgreSQL will prefer seq scan anyway."
|
|
162
|
+
ai_prompt: "Add missing indexes on foreign key columns in these PostgreSQL tables. For each FK: generate a Rails migration with add_index on the child table's FK column. Use `algorithm: :concurrently` for large tables."
|
|
163
|
+
|
|
164
|
+
index_correlation:
|
|
165
|
+
title: "Index Correlation"
|
|
166
|
+
what: "Indexes where physical row order poorly matches the index order."
|
|
167
|
+
how: "Reads pg_stats.correlation for leading index columns. Correlation near 0 means random physical order relative to the index, causing excessive random I/O on range scans."
|
|
168
|
+
nuances:
|
|
169
|
+
- "Correlation ranges from -1 to 1. Values near 0 mean random order; near 1 or -1 mean ordered."
|
|
170
|
+
- "Low correlation mainly impacts range scans (BETWEEN, >, <) and ORDER BY — point lookups are less affected."
|
|
171
|
+
- "CLUSTER table USING index physically reorders rows but locks the table and is not maintained automatically."
|
|
172
|
+
- "For append-only tables with timestamp columns, correlation is naturally high — no action needed."
|
|
173
|
+
- "Only tables > 10MB with > 100 index scans are shown to reduce noise."
|
|
174
|
+
ai_prompt: "Address low physical correlation for these PostgreSQL indexes. For each: evaluate if CLUSTER is appropriate (for read-heavy tables), or suggest switching to BRIN index for range-scanned timestamp columns. If CLUSTER is chosen, generate a rake task since it locks the table and should be run during maintenance."
|
|
119
175
|
|
|
120
176
|
index_sizes:
|
|
121
177
|
title: "Index Sizes"
|
|
@@ -145,6 +201,7 @@ en:
|
|
|
145
201
|
- "VACUUM FULL physically reduces file size, but locks the table."
|
|
146
202
|
- "autovacuum should handle this automatically — check its settings if you see high bloat."
|
|
147
203
|
- "Dead tuple ratio >20% — signal of vacuum problems."
|
|
204
|
+
ai_prompt: "Fix bloat on these PostgreSQL tables with high dead tuple percentage. Generate a rake task that runs VACUUM ANALYZE on each table. For severely bloated tables, suggest VACUUM FULL during maintenance window. Also review autovacuum settings."
|
|
148
205
|
|
|
149
206
|
vacuum_needed:
|
|
150
207
|
title: "Vacuum Needed"
|
|
@@ -154,6 +211,7 @@ en:
|
|
|
154
211
|
- "last_vacuum and last_autovacuum show when vacuum last ran."
|
|
155
212
|
- "If autovacuum can't keep up, you can: lower autovacuum_vacuum_threshold, increase autovacuum_vacuum_scale_factor, add workers."
|
|
156
213
|
- "For critical tables, you can set individual parameters: ALTER TABLE SET (autovacuum_vacuum_threshold = 1000)."
|
|
214
|
+
ai_prompt: "Fix vacuum issues on these PostgreSQL tables. Generate a rake task that runs VACUUM ANALYZE on each table. For tables with extreme dead rows, suggest tuning per-table autovacuum parameters via a Rails migration using execute."
|
|
157
215
|
|
|
158
216
|
row_counts:
|
|
159
217
|
title: "Row Counts"
|
|
@@ -172,6 +230,7 @@ en:
|
|
|
172
230
|
- "Target value: >95% for actively used tables."
|
|
173
231
|
- "Low cache hit: table too large for cache or rarely used."
|
|
174
232
|
- "Solutions: increase shared_buffers, optimize queries, add indexes to reduce scanned data."
|
|
233
|
+
ai_prompt: "Improve cache hit ratios for these PostgreSQL tables. Suggest specific shared_buffers tuning, recommend indexes to reduce data scanned, and identify queries hitting these tables that could benefit from optimization."
|
|
175
234
|
|
|
176
235
|
seq_scans:
|
|
177
236
|
title: "Sequential Scans"
|
|
@@ -182,6 +241,18 @@ en:
|
|
|
182
241
|
- "seq_tup_read shows how many rows were read — this is more important than scan count."
|
|
183
242
|
- "High seq_tup_read/seq_scan = many rows per scan = probably normal."
|
|
184
243
|
- "Low seq_tup_read/seq_scan = many small scans = possibly N+1."
|
|
244
|
+
ai_prompt: "Add indexes for these PostgreSQL tables with high sequential scan counts. For each table: identify the queries causing sequential scans, determine optimal index columns, generate a Rails migration with add_index."
|
|
245
|
+
|
|
246
|
+
tables_without_pk:
|
|
247
|
+
title: "Tables Without Primary Keys"
|
|
248
|
+
what: "Tables that have no primary key defined."
|
|
249
|
+
how: "Checks pg_index for missing indisprimary entries on user tables."
|
|
250
|
+
nuances:
|
|
251
|
+
- "Logical replication requires a primary key or REPLICA IDENTITY to identify rows."
|
|
252
|
+
- "Without a PK, UPDATE and DELETE need another way to uniquely identify rows, often resulting in full table scans."
|
|
253
|
+
- "Join tables (many-to-many) often lack PKs intentionally — consider adding a composite PK."
|
|
254
|
+
- "Some ORMs (e.g., Rails) assume an 'id' primary key — tables without it may cause framework errors."
|
|
255
|
+
ai_prompt: "Add primary keys to these PostgreSQL tables. For each table: determine the appropriate primary key (auto-increment id or composite key for join tables), generate a Rails migration that adds the primary key."
|
|
185
256
|
|
|
186
257
|
recently_modified:
|
|
187
258
|
title: "Recently Modified"
|
|
@@ -192,6 +263,31 @@ en:
|
|
|
192
263
|
- "High UPDATE activity creates dead tuples — monitor vacuum."
|
|
193
264
|
- "HOT updates (Heap Only Tuple) are more efficient — indexes are not updated."
|
|
194
265
|
|
|
266
|
+
update_hotspots:
|
|
267
|
+
title: "Update Hotspots"
|
|
268
|
+
what: "Tables where the same rows are updated repeatedly, or where indexed columns are being hot-updated."
|
|
269
|
+
how: "Computes two ratios from pg_stat_user_tables: updates_per_row = n_tup_upd / n_live_tup (signal for write amplification on individual rows) and hot_update_pct = n_tup_hot_upd / n_tup_upd (low value means an indexed column is in the SET clause, defeating HOT)."
|
|
270
|
+
nuances:
|
|
271
|
+
- "updates_per_row >> 1 means individual rows are being rewritten many times — common with counters, status flags, last_seen_at, position trackers."
|
|
272
|
+
- "Refactor patterns for hot rows: move the volatile column to a sibling 1:1 'state' table; use an event/log table with periodic rollups; batch writes via a background buffer; debounce updates application-side."
|
|
273
|
+
- "Low hot_update_pct (e.g. <50%) means indexed columns are being updated. Either drop the index or move that column to a less-indexed table."
|
|
274
|
+
- "PostgreSQL does not track per-column UPDATE counts. To find which column is the culprit, parse pg_stat_statements for UPDATE queries on the table and look at SET clauses (queries are normalized but column names are preserved)."
|
|
275
|
+
- "Hot rows + low fillfactor (default 100) eliminates HOT optimization. ALTER TABLE ... SET (fillfactor=80) leaves room for in-place updates."
|
|
276
|
+
- "High n_dead_tup correlates with hot rows — autovacuum must keep up or bloat will grow fast."
|
|
277
|
+
ai_prompt: "Reduce write amplification on these PostgreSQL tables. For each: identify the columns most commonly updated by parsing pg_stat_statements UPDATE queries, propose a refactor (split hot/cold columns into a sibling table, switch to an append-only event table, batch writes), and generate the migration. If hot_update_pct is low, identify the indexed column being updated and recommend either dropping the index or moving the column."
|
|
278
|
+
|
|
279
|
+
unused_tables:
|
|
280
|
+
title: "Unused Tables"
|
|
281
|
+
what: "Tables with zero seq_scan and zero idx_scan since the last statistics reset — application code never reads them."
|
|
282
|
+
how: "Filters pg_stat_user_tables for seq_scan = 0 AND idx_scan = 0. Cross-references db_stats_since (pg_stat_database.stats_reset) so you know how much history the verdict is based on."
|
|
283
|
+
nuances:
|
|
284
|
+
- "Verify db_stats_since covers a representative window — at minimum a full week, ideally a full reporting/billing cycle. A recent stats_reset, restart, or PG upgrade invalidates the result."
|
|
285
|
+
- "Tables read only by replicas are NOT counted on the primary. Check replicas separately before dropping."
|
|
286
|
+
- "Tables touched only by COPY, pg_dump, or logical replication slots may still appear unused — these don't increment scan counters."
|
|
287
|
+
- "Foreign keys referencing the table can keep it 'in use' through cascades even without direct queries."
|
|
288
|
+
- "Safe sequence: rename → wait one full cycle → drop. Or move to a separate archive schema first."
|
|
289
|
+
ai_prompt: "Archive these unread PostgreSQL tables. For each: verify the table is not referenced by FKs, code, or scheduled jobs, then generate a Rails migration that either drops the table or moves it to an `archive` schema. Add a rollback that restores the table. Recommend resetting pg_stat_user_tables before final removal to confirm zero reads over a fresh window."
|
|
290
|
+
|
|
195
291
|
# === CONNECTIONS ===
|
|
196
292
|
active_connections:
|
|
197
293
|
title: "Active Connections"
|
|
@@ -220,6 +316,7 @@ en:
|
|
|
220
316
|
- "Long queries can hold locks and interfere with other processes."
|
|
221
317
|
- "statement_timeout can automatically terminate queries running too long."
|
|
222
318
|
- "For reports/analytics, use a read replica or configure a separate connection pool."
|
|
319
|
+
ai_prompt: "Investigate and optimize these long-running PostgreSQL queries. For each query: analyze the execution plan, suggest indexes or query rewrites, recommend statement_timeout configuration. If the query is analytical, suggest moving it to a read replica."
|
|
223
320
|
|
|
224
321
|
blocking_queries:
|
|
225
322
|
title: "Blocking Queries"
|
|
@@ -230,6 +327,7 @@ en:
|
|
|
230
327
|
- "Common causes: long transactions, missing indexes on UPDATE/DELETE with FK."
|
|
231
328
|
- "pg_cancel_backend(pid) cancels query, pg_terminate_backend(pid) terminates connection."
|
|
232
329
|
- "Consider optimistic locking at the application level."
|
|
330
|
+
ai_prompt: "Resolve these blocking query chains in PostgreSQL. Identify the root blocker, suggest application-level fixes (shorter transactions, optimistic locking, advisory locks), and recommend indexes on FK columns to speed up cascading operations."
|
|
233
331
|
|
|
234
332
|
locks:
|
|
235
333
|
title: "Locks"
|
|
@@ -248,6 +346,7 @@ en:
|
|
|
248
346
|
- "A moderate number of idle connections is normal for connection pooling."
|
|
249
347
|
- "Too many idle = application not closing connections or pool is too large."
|
|
250
348
|
- "idle_session_timeout (PostgreSQL 14+) can automatically close idle connections."
|
|
349
|
+
ai_prompt: "Reduce idle PostgreSQL connections. Suggest database.yml pool size tuning, recommend idle_session_timeout or PgBouncer configuration, and identify application code that may be leaking connections."
|
|
251
350
|
|
|
252
351
|
pool_usage:
|
|
253
352
|
title: "Connection Pool Usage"
|
|
@@ -278,6 +377,7 @@ en:
|
|
|
278
377
|
- "High idle in transaction count = application transaction handling issues."
|
|
279
378
|
- "superuser_reserved_connections reduce available pool capacity."
|
|
280
379
|
- "Monitor trends — sudden spikes may indicate connection leaks."
|
|
380
|
+
ai_prompt: "Fix connection pool saturation in PostgreSQL. Suggest specific max_connections and pool size tuning for database.yml, recommend PgBouncer setup if not present, and identify application patterns causing pool exhaustion."
|
|
281
381
|
|
|
282
382
|
connection_churn:
|
|
283
383
|
title: "Connection Churn Analysis"
|
|
@@ -288,6 +388,7 @@ en:
|
|
|
288
388
|
- "Churn rate over 50% = missing/misconfigured connection pooling."
|
|
289
389
|
- "Many short connections = increased CPU and authentication overhead."
|
|
290
390
|
- "Web apps should maintain connection pools, not create per-request connections."
|
|
391
|
+
ai_prompt: "Fix excessive connection churn in PostgreSQL. Configure PgBouncer or Rails connection pooling properly, identify application code creating short-lived connections, suggest database.yml pool and checkout_timeout tuning."
|
|
291
392
|
|
|
292
393
|
# === SYSTEM ===
|
|
293
394
|
database_sizes:
|
|
@@ -326,6 +427,30 @@ en:
|
|
|
326
427
|
- "Useful for quick assessment of database state."
|
|
327
428
|
- "Compare with baseline to identify anomalies."
|
|
328
429
|
|
|
430
|
+
wraparound_risk:
|
|
431
|
+
title: "Wraparound Risk"
|
|
432
|
+
what: "Transaction ID age proximity to the 2-billion wraparound limit."
|
|
433
|
+
how: "Reads age(datfrozenxid) from pg_database. When this approaches 2^31 (~2.1 billion), PostgreSQL will shut down to prevent data corruption."
|
|
434
|
+
nuances:
|
|
435
|
+
- "autovacuum_freeze_max_age (default 200M) triggers aggressive anti-wraparound VACUUM automatically."
|
|
436
|
+
- "If age exceeds freeze_max_age, anti-wraparound VACUUM should already be running — if not, investigate why."
|
|
437
|
+
- "Long-running transactions prevent VACUUM from advancing the frozen XID — monitor idle-in-transaction."
|
|
438
|
+
- "In emergencies, run VACUUM FREEZE on the largest/oldest tables first."
|
|
439
|
+
- "Pct > 50% is a warning; > 75% is critical and requires immediate action."
|
|
440
|
+
ai_prompt: "Address transaction ID wraparound risk on these PostgreSQL databases. Generate a rake task that runs VACUUM FREEZE on the affected databases, prioritizing by pct_towards_wraparound. Include monitoring checks to verify the XID age decreased after the operation."
|
|
441
|
+
|
|
442
|
+
checkpoint_stats:
|
|
443
|
+
title: "Checkpoint Stats"
|
|
444
|
+
what: "Checkpoint frequency and background writer performance metrics."
|
|
445
|
+
how: "Data from pg_stat_bgwriter shows checkpoint counts, timing, and buffer write distribution."
|
|
446
|
+
nuances:
|
|
447
|
+
- "checkpoints_timed = scheduled checkpoints (healthy); checkpoints_req = forced checkpoints (under pressure)."
|
|
448
|
+
- "High requested_pct means WAL fills up before checkpoint_timeout — increase max_wal_size."
|
|
449
|
+
- "buffers_backend > 0 means backends write dirty buffers themselves — increase shared_buffers or bgwriter activity."
|
|
450
|
+
- "bgwriter_stops (maxwritten_clean) > 0 means bgwriter hit its per-round limit — increase bgwriter_lru_maxpages."
|
|
451
|
+
- "Stats accumulate since stats_reset — compare over time for meaningful analysis."
|
|
452
|
+
ai_prompt: "Optimize PostgreSQL checkpoint and background writer configuration based on these stats. Suggest specific postgresql.conf parameter changes (max_wal_size, checkpoint_completion_target, bgwriter_lru_maxpages) with recommended values and explain the expected impact."
|
|
453
|
+
|
|
329
454
|
cache_stats:
|
|
330
455
|
title: "Cache Stats"
|
|
331
456
|
what: "Database caching statistics."
|
|
@@ -334,8 +459,82 @@ en:
|
|
|
334
459
|
- "Cache hit ratio = blks_hit / (blks_hit + blks_read)."
|
|
335
460
|
- "Target value: >99% for OLTP, >95% for mixed workload."
|
|
336
461
|
- "Low cache hit: increase shared_buffers (up to 25% RAM), or the problem is in queries."
|
|
462
|
+
ai_prompt: "Improve PostgreSQL cache hit ratio. Suggest specific shared_buffers and effective_cache_size tuning for postgresql.conf based on the current stats. Recommend application-level caching for frequently accessed data."
|
|
337
463
|
|
|
338
464
|
# === SCHEMA ANALYSIS ===
|
|
465
|
+
unused_columns:
|
|
466
|
+
title: "Unused Columns"
|
|
467
|
+
what: "Columns whose data has only ever held a single distinct value — strong sign nothing in the application updates them anymore."
|
|
468
|
+
how: "Reads pg_stats.n_distinct = 1 (only one value across all sampled rows) and joins with pg_attrdef to show the column default. Excludes primary keys and unique-index columns to avoid noise."
|
|
469
|
+
nuances:
|
|
470
|
+
- "n_distinct is a sample-based estimate — ANALYZE the table before trusting the result. Stale stats can produce false positives."
|
|
471
|
+
- "Tables under ~1000 rows are excluded because a single value across few rows is statistically meaningless."
|
|
472
|
+
- "False positives: feature flags, enum columns where only one value has shipped so far, columns defaulted in code with one valid option."
|
|
473
|
+
- "True positives: columns whose Ruby/ORM accessor was deleted but the migration to drop the column was never written; status fields replaced by a different mechanism but left in the schema."
|
|
474
|
+
- "PostgreSQL has no per-column write tracking, so this is a heuristic. Confirm by grepping the codebase for the column name before dropping."
|
|
475
|
+
- "Consider also `always_null_columns` — same diagnosis from a different angle."
|
|
476
|
+
ai_prompt: "Drop these unused PostgreSQL columns. For each column: search the application code for any reference to the column name (Ruby, SQL, fixtures, views), and only if zero references found generate a Rails migration that removes the column with a `safety_assured` block. Include the column type and default in the rollback."
|
|
477
|
+
|
|
478
|
+
always_null_columns:
|
|
479
|
+
title: "Always-NULL Columns"
|
|
480
|
+
what: "Nullable columns where 100% of rows are NULL — the application has stopped (or never started) writing them."
|
|
481
|
+
how: "Reads pg_stats.null_frac >= 0.999 (effectively all NULL) and excludes columns with a NOT NULL constraint. Joins pg_attrdef to surface any leftover column default."
|
|
482
|
+
nuances:
|
|
483
|
+
- "null_frac is sampled — run ANALYZE first if you doubt the value. Re-check after a representative workload window."
|
|
484
|
+
- "A column may be NULL because the writing code path is rarely hit (e.g. premium-only fields, opt-in features). Check before dropping."
|
|
485
|
+
- "If null_pct = 100 AND there is a non-NULL default, the default is unused — also worth removing alongside the column."
|
|
486
|
+
- "Some ORMs serialize empty strings as '' rather than NULL; this report won't catch always-empty columns. Add a separate report or query if you need that signal."
|
|
487
|
+
- "Compare with `unused_columns` — that report catches single-value columns; this one catches all-NULL columns. Together they cover the most common dead-column patterns."
|
|
488
|
+
ai_prompt: "Drop these always-NULL PostgreSQL columns. For each: confirm via codebase search that no writer references the column, then generate a Rails migration removing the column. If the rollback should restore data, note that there is none — the column was empty."
|
|
489
|
+
|
|
490
|
+
polymorphic_without_index:
|
|
491
|
+
title: "Polymorphic Without Index"
|
|
492
|
+
what: "Polymorphic `belongs_to` associations whose `(*_type, *_id)` pair has no composite index."
|
|
493
|
+
how: "Walks `ActiveRecord::Base.descendants`, collects `belongs_to` reflections with `polymorphic: true`, and checks `pg_index` for an index covering both the type and id columns."
|
|
494
|
+
nuances:
|
|
495
|
+
- "PostgreSQL cannot use two single-column indexes as efficiently as one composite index for `WHERE x_type = ? AND x_id = ?` lookups — this is the canonical fetch pattern for polymorphic associations."
|
|
496
|
+
- "Coverage hint clarifies the gap: \"neither indexed\" / \"only id indexed\" / \"only type indexed\" / \"type and id indexed separately\". The last two are partial fixes, not full ones."
|
|
497
|
+
- "On small association tables this is harmless; on large ones (Comment, Note, Activity, AuditLog) it turns every association load into a seq scan."
|
|
498
|
+
- "The suggested migration uses the `(type, id)` order — type usually has lower cardinality so the index tree narrows faster."
|
|
499
|
+
- "Eager-loads the application before walking models so dev environments produce a complete result."
|
|
500
|
+
ai_prompt: "Add composite indexes for these polymorphic associations. For each: generate a Rails migration with `add_index :<table>, [:<x>_type, :<x>_id]` using `algorithm: :concurrently` for large tables. Verify the existing single-column indexes (if any) can be dropped after the composite is in place."
|
|
501
|
+
|
|
502
|
+
counter_cache_issues:
|
|
503
|
+
title: "Counter Cache Issues"
|
|
504
|
+
what: "`belongs_to ..., counter_cache: ...` declarations whose target column is missing on the parent table."
|
|
505
|
+
how: "Walks all models, finds `belongs_to` with a `counter_cache` option, resolves the expected column name (`<child_table>_count` for `counter_cache: true`, or the explicit symbol/string), and checks the parent's columns."
|
|
506
|
+
nuances:
|
|
507
|
+
- "Missing column = the counter is silently broken: writes go nowhere, `parent.<assoc>_count` returns nil, and any code reading the cached value gets stale or zero."
|
|
508
|
+
- "Reverse direction (a `*_count` column with no `counter_cache` declaration pointing at it) is intentionally not flagged — too many false positives from manually maintained counters."
|
|
509
|
+
- "Resolves the parent class via `assoc.klass`; if the class is missing or fails to load, the row is skipped."
|
|
510
|
+
- "Default name for `counter_cache: true`: parent gets a column named after the **child** table pluralized + `_count`. Example: `Comment belongs_to :user, counter_cache: true` → `users.comments_count`."
|
|
511
|
+
- "Eager-loads the application before walking models so dev environments produce a complete result."
|
|
512
|
+
ai_prompt: "Fix these broken counter caches. For each row: generate a Rails migration that adds the missing column with `default: 0, null: false`, then a backfill task that runs `Parent.find_each { |p| Parent.reset_counters(p.id, :<assoc>) }`. Run the backfill before deploying any code that reads the counter."
|
|
513
|
+
|
|
514
|
+
soft_delete_without_scope:
|
|
515
|
+
title: "Soft Delete Without Scope"
|
|
516
|
+
what: "Tables with a `deleted_at` / `discarded_at` / `archived_at` column whose model has no scope filtering soft-deleted rows."
|
|
517
|
+
how: "For each table, checks if any of the canonical soft-delete column names exist. If so, finds the model and checks for `acts_as_paranoid` (paranoia gem), `discard_column` (discard gem), or a `default_scope` whose generated SQL references the column."
|
|
518
|
+
nuances:
|
|
519
|
+
- "Without a default scope, every plain `Model.where(...)` query returns soft-deleted rows. This silently leaks 'deleted' data into reports, indexes, search results, and exports."
|
|
520
|
+
- "If the team has decided **against** a default scope (because they prefer explicit `kept`/`with_deleted` calls everywhere), this report will produce one row per such model — those are intentional and can be ignored."
|
|
521
|
+
- "Detection method for default_scope: builds `model.all.to_sql` and looks for the column name. Catches `default_scope { where(deleted_at: nil) }` and equivalents but misses scopes added at runtime via concerns that aren't loaded yet."
|
|
522
|
+
- "Tables without a Rails model are reported separately with status `no_model` — usually means the table is queried only by raw SQL and needs manual review."
|
|
523
|
+
- "Eager-loads the application before walking models so dev environments produce a complete result."
|
|
524
|
+
ai_prompt: "Add soft-delete scopes to these Rails models. For each: add `default_scope { where(<column>: nil) }` and a `with_deleted` unscoped helper, OR adopt the `discard` gem (`include Discard::Model; self.discard_column = :<column>`). Verify no production code path expects to see soft-deleted rows by default."
|
|
525
|
+
|
|
526
|
+
orphan_tables:
|
|
527
|
+
title: "Orphan Tables"
|
|
528
|
+
what: "Database tables with no corresponding Rails model class."
|
|
529
|
+
how: "Lists all tables from `pg_class` (excluding `schema_migrations` and `ar_internal_metadata`) and tries common naming conventions to find a model. Tables that resolve to nothing are reported."
|
|
530
|
+
nuances:
|
|
531
|
+
- "Three classifications: `join_table_candidate` (exactly two `*_id` columns and nothing else — likely a legitimate HABTM table), `join_model_without_class` (multiple FK columns plus extra fields — was probably meant to be a join model), and `legacy` (everything else)."
|
|
532
|
+
- "`join_table_candidate` is usually intentional — Rails doesn't require a model for HABTM. Skim and ignore."
|
|
533
|
+
- "`legacy` rows are the interesting ones: tables created before the Rails model was added, renamed-but-not-dropped, created out-of-band by another service, or owned by a removed feature."
|
|
534
|
+
- "Cross-reference with `unused_tables` (Tables category) — a table that's both orphan AND has zero reads is a strong drop candidate."
|
|
535
|
+
- "False positives: namespaced models (`Admin::User` → `admin_users`), STI subclasses sharing a base table, models declared inside engines that aren't eager-loaded."
|
|
536
|
+
- "Row count is approximate (n_live_tup from pg_stat_user_tables) — newly written tables may show 0 until the next ANALYZE."
|
|
537
|
+
|
|
339
538
|
missing_validations:
|
|
340
539
|
title: "Missing Validations"
|
|
341
540
|
what: "Unique indexes in the database without corresponding uniqueness validations in Rails models."
|
|
@@ -347,6 +546,7 @@ en:
|
|
|
347
546
|
- "Some tables may not have models — this is normal for join tables or legacy tables."
|
|
348
547
|
- "Validations in concerns and parent models are also detected."
|
|
349
548
|
- "Primary keys don't need validations — they are automatically unique."
|
|
549
|
+
ai_prompt: "Add missing uniqueness validations to these Rails models. For each unique index: add `validates :column, uniqueness: true` (or with `scope:` for composite indexes) to the corresponding model. Handle STI and concern-based models correctly."
|
|
350
550
|
|
|
351
551
|
# Problem explanations for highlighting
|
|
352
552
|
problems:
|
|
@@ -356,6 +556,13 @@ en:
|
|
|
356
556
|
low_cache_hit: "Low cache hit ratio. Query frequently reads from disk instead of cache."
|
|
357
557
|
high_seq_scan: "Many sequential scans. Possibly missing an index."
|
|
358
558
|
unused_index: "Index is not used. Candidate for removal."
|
|
559
|
+
inefficient_index: "Index reads far more entries than it fetches. Composite index column order likely misaligned with query predicates."
|
|
560
|
+
fk_without_index: "Foreign key column has no supporting index. DELETE/UPDATE on parent table will cause sequential scan."
|
|
561
|
+
low_correlation: "Low physical correlation between index and row order. Range scans will cause excessive random I/O."
|
|
562
|
+
temp_file_heavy: "Query spills data to temporary files on disk. Consider increasing work_mem or optimizing the query."
|
|
563
|
+
missing_pk: "Table has no primary key. This breaks logical replication and may cause issues with ORMs."
|
|
564
|
+
wraparound_risk: "Transaction ID age is approaching the wraparound limit. VACUUM FREEZE required."
|
|
565
|
+
high_checkpoint_req: "High percentage of forced checkpoints. Consider increasing max_wal_size."
|
|
359
566
|
high_bloat: "High bloat. REINDEX or VACUUM required."
|
|
360
567
|
many_dead_tuples: "Many dead tuples. VACUUM required."
|
|
361
568
|
long_running: "Long-running query. May block other operations."
|
|
@@ -366,3 +573,393 @@ en:
|
|
|
366
573
|
pool_saturation: "Connection pool is saturated. Risk of connection exhaustion and application errors."
|
|
367
574
|
high_connection_churn: "High connection churn rate. Implement connection pooling to reduce overhead."
|
|
368
575
|
too_many_short_connections: "Too many short-lived connections. Application should reuse connections via pooling."
|
|
576
|
+
unused_column: "Column has only one distinct value across all rows. Likely never updated since creation — candidate for removal."
|
|
577
|
+
always_null_column: "Column is 100% NULL. Application no longer writes to this field — candidate for removal."
|
|
578
|
+
hot_rows: "Same rows are updated repeatedly. Consider splitting hot/cold columns, batching writes, or using an event-log table."
|
|
579
|
+
low_hot_update: "HOT update rate is low — indexed columns are being updated. Drop the index or move the column to reduce write amplification."
|
|
580
|
+
unused_table: "Table has zero reads since the last stats reset. Candidate for archival or removal — verify the stats window first."
|
|
581
|
+
polymorphic_no_index: "Polymorphic association lacks a composite (type, id) index. Lookups will seq-scan as the table grows."
|
|
582
|
+
counter_cache_missing_column: "Counter cache column is missing on the parent table. The counter is silently broken — writes go nowhere."
|
|
583
|
+
soft_delete_unprotected: "Soft-delete column has no model scope filtering it. Plain queries return deleted rows."
|
|
584
|
+
orphan_table_legacy: "Table has no Rails model. Likely legacy — verify before dropping."
|
|
585
|
+
|
|
586
|
+
# UI strings shown in the dashboard chrome (buttons, modals, toasts, etc.)
|
|
587
|
+
ui:
|
|
588
|
+
branding:
|
|
589
|
+
title: "PgReports"
|
|
590
|
+
subtitle: "PostgreSQL Analysis Dashboard"
|
|
591
|
+
page_title: "PgReports Dashboard"
|
|
592
|
+
navigation:
|
|
593
|
+
dashboard: "Dashboard"
|
|
594
|
+
back: "← Back"
|
|
595
|
+
actions:
|
|
596
|
+
cancel: "Cancel"
|
|
597
|
+
retry: "Retry"
|
|
598
|
+
copy: "📋 Copy"
|
|
599
|
+
copy_query: "📋 Copy Query"
|
|
600
|
+
copy_code: "📋 Copy Code"
|
|
601
|
+
copy_to_clipboard_title: "Copy to clipboard"
|
|
602
|
+
copied_feedback: "✓ Copied!"
|
|
603
|
+
clear_all: "Clear All"
|
|
604
|
+
run_report: "▶ Run Report"
|
|
605
|
+
export: "⬇ Export"
|
|
606
|
+
download_text: "📄 Text (.txt)"
|
|
607
|
+
download_csv: "📊 CSV (.csv)"
|
|
608
|
+
download_json: "📋 JSON (.json)"
|
|
609
|
+
download: "📥 Download"
|
|
610
|
+
copy_ai_prompt: "Copy Prompt"
|
|
611
|
+
send_telegram: "📨 Telegram"
|
|
612
|
+
sending: "Sending..."
|
|
613
|
+
reset_statistics: "🗑️ Reset Statistics"
|
|
614
|
+
resetting: "Resetting..."
|
|
615
|
+
confirm_reset: "Yes, Reset"
|
|
616
|
+
create_extension: "⚡ Create Extension"
|
|
617
|
+
creating: "Creating..."
|
|
618
|
+
ide_settings_button_title: "IDE Settings"
|
|
619
|
+
explain_analyze: "📊 EXPLAIN ANALYZE"
|
|
620
|
+
execute_query: "▶ Execute Query"
|
|
621
|
+
create_migration_file: "📁 Create File & Open in IDE"
|
|
622
|
+
start_monitoring: "▶ Start Monitoring"
|
|
623
|
+
stop_monitoring: "⏹ Stop Monitoring"
|
|
624
|
+
starting: "Starting..."
|
|
625
|
+
stopping: "Stopping..."
|
|
626
|
+
load_history: "📜 Load History (50)"
|
|
627
|
+
loading: "Loading..."
|
|
628
|
+
running: "Running..."
|
|
629
|
+
save_for_comparison: "📌 Save for Comparison"
|
|
630
|
+
saved_marker: "📌 Saved"
|
|
631
|
+
status:
|
|
632
|
+
pg_stat_ready: "pg_stat_statements ready"
|
|
633
|
+
extension_installed: "Extension installed, not preloaded"
|
|
634
|
+
preloaded: "Preloaded, extension not created"
|
|
635
|
+
not_configured: "Not configured"
|
|
636
|
+
monitoring_unavailable: "Live Monitoring Unavailable"
|
|
637
|
+
modals:
|
|
638
|
+
enable_pg_stat_title: "Enable pg_stat_statements"
|
|
639
|
+
enable_pg_stat_intro: "To enable pg_stat_statements, follow these steps:"
|
|
640
|
+
edit_postgresql_conf: "Edit postgresql.conf:"
|
|
641
|
+
restart_postgresql: "Restart PostgreSQL:"
|
|
642
|
+
create_extension_step: "Create extension:"
|
|
643
|
+
enable_button_note: "Or click \"Enable\" button after restart."
|
|
644
|
+
reset_stats_title: "⚠️ Reset Statistics"
|
|
645
|
+
reset_stats_confirm: "Are you sure you want to reset pg_stat_statements statistics?"
|
|
646
|
+
reset_stats_warning: "This action will clear all collected query statistics and cannot be undone."
|
|
647
|
+
ide_settings_title: "⚙️ IDE Settings"
|
|
648
|
+
problem_detected_title: "⚠️ Problem Detected"
|
|
649
|
+
query_analyzer_title: "📊 Query Analyzer"
|
|
650
|
+
query_label: "Query:"
|
|
651
|
+
parameters_label: "Parameters:"
|
|
652
|
+
migration_title: "🗑️ Drop Index Migration"
|
|
653
|
+
migration_subtitle: "Generated migration to remove the index:"
|
|
654
|
+
migration_warning: "Creating a migration will generate a migration file in your project. Running this migration will drop the index from the database, which may significantly impact application performance."
|
|
655
|
+
migration_warning_dev_only: "This operation should only be performed in a local development environment."
|
|
656
|
+
query_execution_disabled_title: "⚠️ Query execution is disabled"
|
|
657
|
+
query_execution_disabled_intro: "To enable this feature, add to your configuration:"
|
|
658
|
+
settings:
|
|
659
|
+
default_ide_label: "Default IDE for source links:"
|
|
660
|
+
ide_show_menu: "Show menu (default)"
|
|
661
|
+
ide_vscode_wsl: "VS Code (WSL)"
|
|
662
|
+
ide_vscode: "VS Code"
|
|
663
|
+
ide_rubymine: "RubyMine"
|
|
664
|
+
ide_intellij: "IntelliJ IDEA"
|
|
665
|
+
ide_cursor_wsl: "Cursor (WSL)"
|
|
666
|
+
ide_cursor: "Cursor"
|
|
667
|
+
monitoring:
|
|
668
|
+
live_title: "Live Monitoring"
|
|
669
|
+
update_interval: "Updates every 5s"
|
|
670
|
+
toggle_title: "Toggle live monitoring"
|
|
671
|
+
query_monitor_title: "SQL Query Monitor"
|
|
672
|
+
session_label: "Session:"
|
|
673
|
+
queries_label: "Queries:"
|
|
674
|
+
feed_empty: "Click \"Start Monitoring\" to begin capturing SQL queries"
|
|
675
|
+
feed_no_queries: "No queries captured yet..."
|
|
676
|
+
unknown_source: "Unknown source"
|
|
677
|
+
expand_collapse_title: "Expand/Collapse"
|
|
678
|
+
metrics:
|
|
679
|
+
connections_label: "Connections"
|
|
680
|
+
tps_label: "TPS"
|
|
681
|
+
tps_unit: "tx/s"
|
|
682
|
+
commit_label: "commit:"
|
|
683
|
+
rollback_label: "rollback:"
|
|
684
|
+
cache_hit_label: "Cache Hit"
|
|
685
|
+
cache_hit_detail: "heap blocks from cache"
|
|
686
|
+
long_queries_label: "Long Queries"
|
|
687
|
+
queries_unit: "queries"
|
|
688
|
+
long_running_threshold: "> 60s runtime"
|
|
689
|
+
blocked_label: "Blocked"
|
|
690
|
+
processes_unit: "processes"
|
|
691
|
+
waiting_for_locks: "waiting for locks"
|
|
692
|
+
percent_used_suffix: "% used"
|
|
693
|
+
categories:
|
|
694
|
+
requires_pg_stat: "🔒 Requires pg_stat_statements"
|
|
695
|
+
reports_count_suffix: "reports"
|
|
696
|
+
documentation:
|
|
697
|
+
toggle_title: "📖 What does this report show?"
|
|
698
|
+
what_section: "📋 What"
|
|
699
|
+
why_section: "❓ Why It Matters"
|
|
700
|
+
nuances_section: "⚠️ Nuances"
|
|
701
|
+
thresholds_section: "📊 Thresholds"
|
|
702
|
+
threshold_warning_label: "⚠️ Warning:"
|
|
703
|
+
threshold_critical_label: "🔴 Critical:"
|
|
704
|
+
threshold_inverted_note: "(lower is worse)"
|
|
705
|
+
filters:
|
|
706
|
+
title: "🔍 Filter Parameters"
|
|
707
|
+
current_value: "current"
|
|
708
|
+
saved:
|
|
709
|
+
title: "📌 Saved for Comparison"
|
|
710
|
+
saved_at_prefix: "▸ Saved:"
|
|
711
|
+
click_to_expand: "Click to expand"
|
|
712
|
+
confirm_clear_all: "Remove all saved records for this report?"
|
|
713
|
+
remove_title: "Remove"
|
|
714
|
+
results:
|
|
715
|
+
title: "Results"
|
|
716
|
+
click_run_hint: "Click \"Run Report\" to fetch data"
|
|
717
|
+
empty_message: "No issues found. Everything looks good!"
|
|
718
|
+
showing_first_of_total: "Showing first %{count} of %{total} rows"
|
|
719
|
+
no_rows_returned: "No rows returned"
|
|
720
|
+
rows_label: "Rows:"
|
|
721
|
+
execution_time_label: "Execution time:"
|
|
722
|
+
null_placeholder: "<null>"
|
|
723
|
+
sections:
|
|
724
|
+
recommendation: "💡 Recommendation"
|
|
725
|
+
detected_issues: "⚠️ Detected Issues"
|
|
726
|
+
execution_plan: "📊 Execution Plan"
|
|
727
|
+
line_label: "Line"
|
|
728
|
+
current_label: "Current:"
|
|
729
|
+
threshold_label: "Threshold:"
|
|
730
|
+
threshold_inverted_long: "(inverted: lower values are worse)"
|
|
731
|
+
warning_eq: "warning"
|
|
732
|
+
critical_eq: "critical"
|
|
733
|
+
levels:
|
|
734
|
+
critical: "🔴 Critical"
|
|
735
|
+
warning: "⚠️ Warning"
|
|
736
|
+
errors:
|
|
737
|
+
error_prefix: "Error:"
|
|
738
|
+
unable_fetch_metrics: "Unable to fetch database statistics."
|
|
739
|
+
possible_causes: "This may be due to:"
|
|
740
|
+
cause_permissions: "Insufficient database permissions"
|
|
741
|
+
cause_views: "Database statistics views not accessible"
|
|
742
|
+
cause_connection: "Connection issues"
|
|
743
|
+
fetch_metrics_failed: "Failed to fetch live metrics"
|
|
744
|
+
fetch_metrics_check_perms: "Unable to fetch database statistics. Check database permissions."
|
|
745
|
+
insufficient_database_perms: "Insufficient database permissions to access statistics views"
|
|
746
|
+
network_error_prefix: "Network error:"
|
|
747
|
+
copy_failed: "Failed to copy"
|
|
748
|
+
run_report_first: "Run the report first"
|
|
749
|
+
run_report_failed: "Failed to run report"
|
|
750
|
+
report_not_found: "Report not found"
|
|
751
|
+
send_telegram_failed: "Failed to send to Telegram"
|
|
752
|
+
reset_stats_failed: "Failed to reset statistics"
|
|
753
|
+
start_monitoring_failed: "Failed to start monitoring"
|
|
754
|
+
stop_monitoring_failed: "Failed to stop monitoring"
|
|
755
|
+
load_history_failed: "Failed to load history:"
|
|
756
|
+
no_query_history: "No query history found"
|
|
757
|
+
decode_query_failed: "Failed to decode query"
|
|
758
|
+
explain_analyze_failed: "Failed to run EXPLAIN ANALYZE"
|
|
759
|
+
execute_query_failed: "Failed to execute query"
|
|
760
|
+
create_migration_failed: "Failed to create migration"
|
|
761
|
+
explain_disabled_toast: "⚠️ EXPLAIN ANALYZE is disabled. Enable in configuration: config.allow_raw_query_execution = true"
|
|
762
|
+
execute_disabled_toast: "⚠️ Query execution is disabled. Enable in configuration: config.allow_raw_query_execution = true"
|
|
763
|
+
query_monitoring_error: "Query monitoring error"
|
|
764
|
+
query_hash_required: "Query hash is required"
|
|
765
|
+
query_execution_disabled: "Query execution from dashboard is disabled. Enable it in configuration with 'config.allow_raw_query_execution = true'"
|
|
766
|
+
query_not_found_expired: "Query not found or expired. Please refresh the page."
|
|
767
|
+
security_violation_prefix: "Security violation:"
|
|
768
|
+
trigger_variables_not_allowed: "Cannot EXPLAIN ANALYZE queries with trigger variables (NEW, OLD). These are only available within trigger functions."
|
|
769
|
+
missing_parameter_values: "Please provide values for all parameter placeholders ($1, $2, etc.)"
|
|
770
|
+
migration_dev_only: "Migration creation is only allowed in development environment"
|
|
771
|
+
filename_code_required: "File name and code are required"
|
|
772
|
+
invalid_filename_format: "Invalid migration file name format"
|
|
773
|
+
migrations_dir_not_found: "Migrations directory not found"
|
|
774
|
+
success:
|
|
775
|
+
statistics_reset: "Statistics have been reset successfully"
|
|
776
|
+
report_generated: "Report generated successfully"
|
|
777
|
+
ai_prompt_copied: "AI prompt copied to clipboard"
|
|
778
|
+
explain_copied: "EXPLAIN output copied to clipboard"
|
|
779
|
+
migration_copied: "Migration code copied to clipboard"
|
|
780
|
+
migration_created: "Migration created successfully"
|
|
781
|
+
telegram_sent: "Report sent to Telegram"
|
|
782
|
+
queries_loaded: "Loaded %{count} queries from history"
|
|
783
|
+
record_saved: "Record saved for comparison"
|
|
784
|
+
record_removed: "Record removed"
|
|
785
|
+
record_removed_saved: "Record removed from saved"
|
|
786
|
+
all_saved_cleared: "All saved records cleared"
|
|
787
|
+
|
|
788
|
+
# Category names (shown on the dashboard grid)
|
|
789
|
+
categories:
|
|
790
|
+
queries: "Queries"
|
|
791
|
+
indexes: "Indexes"
|
|
792
|
+
tables: "Tables"
|
|
793
|
+
connections: "Connections"
|
|
794
|
+
system: "System"
|
|
795
|
+
schema_analysis: "Schema Analysis"
|
|
796
|
+
|
|
797
|
+
# Report names and short descriptions (shown on the dashboard listing)
|
|
798
|
+
reports:
|
|
799
|
+
slow_queries:
|
|
800
|
+
name: "Slow Queries"
|
|
801
|
+
description: "Queries with high mean execution time"
|
|
802
|
+
heavy_queries:
|
|
803
|
+
name: "Heavy Queries"
|
|
804
|
+
description: "Most frequently called queries"
|
|
805
|
+
expensive_queries:
|
|
806
|
+
name: "Expensive Queries"
|
|
807
|
+
description: "Queries consuming most total time"
|
|
808
|
+
missing_index_queries:
|
|
809
|
+
name: "Missing Index Queries"
|
|
810
|
+
description: "Queries potentially missing indexes"
|
|
811
|
+
low_cache_hit_queries:
|
|
812
|
+
name: "Low Cache Hit"
|
|
813
|
+
description: "Queries with poor cache utilization"
|
|
814
|
+
temp_file_queries:
|
|
815
|
+
name: "Temp File Queries"
|
|
816
|
+
description: "Queries spilling to disk"
|
|
817
|
+
all_queries:
|
|
818
|
+
name: "All Queries"
|
|
819
|
+
description: "All query statistics"
|
|
820
|
+
unused_indexes:
|
|
821
|
+
name: "Unused Indexes"
|
|
822
|
+
description: "Indexes rarely or never scanned"
|
|
823
|
+
duplicate_indexes:
|
|
824
|
+
name: "Duplicate Indexes"
|
|
825
|
+
description: "Redundant indexes"
|
|
826
|
+
invalid_indexes:
|
|
827
|
+
name: "Invalid Indexes"
|
|
828
|
+
description: "Indexes that failed to build"
|
|
829
|
+
missing_indexes:
|
|
830
|
+
name: "Missing Indexes"
|
|
831
|
+
description: "Tables potentially missing indexes"
|
|
832
|
+
inefficient_indexes:
|
|
833
|
+
name: "Inefficient Indexes"
|
|
834
|
+
description: "Indexes with high read-to-fetch ratio"
|
|
835
|
+
index_usage:
|
|
836
|
+
name: "Index Usage"
|
|
837
|
+
description: "Index scan statistics"
|
|
838
|
+
bloated_indexes:
|
|
839
|
+
name: "Bloated Indexes"
|
|
840
|
+
description: "Indexes with high bloat"
|
|
841
|
+
fk_without_indexes:
|
|
842
|
+
name: "FK Without Indexes"
|
|
843
|
+
description: "Foreign keys missing indexes"
|
|
844
|
+
index_correlation:
|
|
845
|
+
name: "Index Correlation"
|
|
846
|
+
description: "Low physical correlation indexes"
|
|
847
|
+
index_sizes:
|
|
848
|
+
name: "Index Sizes"
|
|
849
|
+
description: "Index disk usage"
|
|
850
|
+
table_sizes:
|
|
851
|
+
name: "Table Sizes"
|
|
852
|
+
description: "Table disk usage"
|
|
853
|
+
bloated_tables:
|
|
854
|
+
name: "Bloated Tables"
|
|
855
|
+
description: "Tables with high dead tuple ratio"
|
|
856
|
+
vacuum_needed:
|
|
857
|
+
name: "Vacuum Needed"
|
|
858
|
+
description: "Tables needing vacuum"
|
|
859
|
+
row_counts:
|
|
860
|
+
name: "Row Counts"
|
|
861
|
+
description: "Table row counts"
|
|
862
|
+
cache_hit_ratios:
|
|
863
|
+
name: "Cache Hit Ratios"
|
|
864
|
+
description: "Table cache statistics"
|
|
865
|
+
seq_scans:
|
|
866
|
+
name: "Sequential Scans"
|
|
867
|
+
description: "Tables with high sequential scans"
|
|
868
|
+
tables_without_pk:
|
|
869
|
+
name: "No Primary Key"
|
|
870
|
+
description: "Tables missing primary keys"
|
|
871
|
+
recently_modified:
|
|
872
|
+
name: "Recently Modified"
|
|
873
|
+
description: "Tables with recent activity"
|
|
874
|
+
update_hotspots:
|
|
875
|
+
name: "Update Hotspots"
|
|
876
|
+
description: "Same rows or indexed columns updated repeatedly"
|
|
877
|
+
unused_tables:
|
|
878
|
+
name: "Unused Tables"
|
|
879
|
+
description: "Tables never queried since the last stats reset"
|
|
880
|
+
active_connections:
|
|
881
|
+
name: "Active Connections"
|
|
882
|
+
description: "Current database connections"
|
|
883
|
+
connection_stats:
|
|
884
|
+
name: "Connection Stats"
|
|
885
|
+
description: "Connection statistics by state"
|
|
886
|
+
long_running_queries:
|
|
887
|
+
name: "Long Running"
|
|
888
|
+
description: "Queries running for extended period"
|
|
889
|
+
blocking_queries:
|
|
890
|
+
name: "Blocking Queries"
|
|
891
|
+
description: "Queries blocking others"
|
|
892
|
+
locks:
|
|
893
|
+
name: "Locks"
|
|
894
|
+
description: "Current database locks"
|
|
895
|
+
idle_connections:
|
|
896
|
+
name: "Idle Connections"
|
|
897
|
+
description: "Idle connections"
|
|
898
|
+
pool_usage:
|
|
899
|
+
name: "Pool Usage"
|
|
900
|
+
description: "Connection pool utilization"
|
|
901
|
+
pool_wait_times:
|
|
902
|
+
name: "Wait Times"
|
|
903
|
+
description: "Resource wait analysis"
|
|
904
|
+
pool_saturation:
|
|
905
|
+
name: "Pool Saturation"
|
|
906
|
+
description: "Pool health warnings"
|
|
907
|
+
connection_churn:
|
|
908
|
+
name: "Connection Churn"
|
|
909
|
+
description: "Connection lifecycle analysis"
|
|
910
|
+
database_sizes:
|
|
911
|
+
name: "Database Sizes"
|
|
912
|
+
description: "Size of all databases"
|
|
913
|
+
settings:
|
|
914
|
+
name: "Settings"
|
|
915
|
+
description: "PostgreSQL configuration"
|
|
916
|
+
extensions:
|
|
917
|
+
name: "Extensions"
|
|
918
|
+
description: "Installed extensions"
|
|
919
|
+
activity_overview:
|
|
920
|
+
name: "Activity Overview"
|
|
921
|
+
description: "Current activity summary"
|
|
922
|
+
wraparound_risk:
|
|
923
|
+
name: "Wraparound Risk"
|
|
924
|
+
description: "Transaction ID wraparound proximity"
|
|
925
|
+
checkpoint_stats:
|
|
926
|
+
name: "Checkpoint Stats"
|
|
927
|
+
description: "Checkpoint and bgwriter statistics"
|
|
928
|
+
cache_stats:
|
|
929
|
+
name: "Cache Stats"
|
|
930
|
+
description: "Database cache statistics"
|
|
931
|
+
missing_validations:
|
|
932
|
+
name: "Missing Validations"
|
|
933
|
+
description: "Unique indexes without model validations"
|
|
934
|
+
unused_columns:
|
|
935
|
+
name: "Unused Columns"
|
|
936
|
+
description: "Columns that have only ever held a single value"
|
|
937
|
+
always_null_columns:
|
|
938
|
+
name: "Always-NULL Columns"
|
|
939
|
+
description: "Nullable columns that contain only NULL"
|
|
940
|
+
polymorphic_without_index:
|
|
941
|
+
name: "Polymorphic Without Index"
|
|
942
|
+
description: "Polymorphic associations missing composite index"
|
|
943
|
+
counter_cache_issues:
|
|
944
|
+
name: "Counter Cache Issues"
|
|
945
|
+
description: "counter_cache declarations whose target column is missing"
|
|
946
|
+
soft_delete_without_scope:
|
|
947
|
+
name: "Soft Delete Without Scope"
|
|
948
|
+
description: "Soft-delete columns with no model scope filtering them"
|
|
949
|
+
orphan_tables:
|
|
950
|
+
name: "Orphan Tables"
|
|
951
|
+
description: "DB tables without a corresponding Rails model"
|
|
952
|
+
|
|
953
|
+
# Filter parameter labels and descriptions
|
|
954
|
+
parameters:
|
|
955
|
+
limit:
|
|
956
|
+
label: "Limit"
|
|
957
|
+
description: "Maximum number of results"
|
|
958
|
+
min_calls:
|
|
959
|
+
label: "Min Calls"
|
|
960
|
+
description: "Minimum number of query calls"
|
|
961
|
+
min_duration_seconds:
|
|
962
|
+
label: "Min Duration (seconds)"
|
|
963
|
+
description: "Minimum query duration in seconds"
|
|
964
|
+
threshold_label: "%{field} Threshold"
|
|
965
|
+
threshold_description: "Override threshold for %{field}"
|