<% if @replica %>
">
<% if @replication_lag %>
<% if @good_replication_lag %>
Healthy replication lag
<% else %>
High replication lag
<% end %>
<%= number_with_delimiter((@replication_lag * 1000).round) %> ms
<% else %>
Replication lag not supported
<% end %>
<% elsif @inactive_replication_slots.any? %>
<%= pluralize(@inactive_replication_slots.size, "inactive replication slot") %>
<% end %>
">
<% if @long_running_queries.any? %>
<%= pluralize(@long_running_queries.size, "long running query") %>
<% else %>
No long running queries
<% end %>
<% if @autovacuum_queries.any? %>
<%= @autovacuum_queries.size %> autovacuum
<% end %>
<% if @walsender_queries.any? %>
<%= @walsender_queries.size %> walsender
<% end %>
<% if @extended %>
">
<% if @good_cache_rate %>
Cache hit rate above <%= @database.cache_hit_rate_threshold %>%
<% else %>
Low cache hit rate
<% end %>
<% end %>
">
<% if !@good_total_connections %>
High number of connections <%= @total_connections %>
<% elsif !@good_idle_connections %>
High number of connections idle in transaction <%= @idle_connections %>
<% else %>
Connections healthy <%= @total_connections %>
<% end %>
">
<% if @transaction_id_danger.any? %>
<%= pluralize(@transaction_id_danger.size, "table") %> not vacuuming properly
<% else %>
Vacuuming healthy
<% end %>
">
<% if @sequence_danger.any? %>
<%= pluralize(@sequence_danger.size, "column") %> approaching overflow
<% elsif @sequences_timeout %>
Sequences not available (system catalog locked)
<% else %>
No columns near integer overflow
<% end %>
<% if @unreadable_sequences.any? %>
(<%= link_to pluralize(@unreadable_sequences.size, "unreadable sequence", "unreadable sequences"), {unreadable: "t"} %>)
<% end %>
">
<% if @invalid_indexes.empty? && @invalid_constraints.empty? %>
No invalid indexes or constraints
<% else %>
<% if @invalid_indexes.any? %>
<%= pluralize(@invalid_indexes.size, "invalid index", "invalid indexes") %>
<% end %>
<% if @invalid_constraints.any? %>
<% if @invalid_indexes.any? %>and<% end %>
<%= pluralize(@invalid_constraints.size, "invalid constraint", "invalid constraints") %>
<% end %>
<% end %>
<% if @duplicate_indexes %>
">
<% if @duplicate_indexes.any? %>
<%= pluralize(@duplicate_indexes.size, "duplicate index", "duplicate indexes") %>
<% else %>
No duplicate indexes
<% end %>
<% end %>
<% if @database.suggested_indexes_enabled? %>
">
<% if @suggested_indexes.any? %>
<%= pluralize(@suggested_indexes.size, "suggested index", "suggested indexes") %>
<% else %>
No suggested indexes
<% end %>
<% end %>
">
<% if !@query_stats_enabled %>
Query stats must be enabled for slow queries
<% elsif @slow_queries.any? %>
<%= pluralize(@slow_queries.size, "slow query") %>
<% else %>
No slow queries
<% end %>
<% if @extended %>
">
<% if @unused_indexes.any? %>
<%= pluralize(@unused_indexes.size, "unused index", "unused indexes") %>
<% else %>
No unused indexes
<% end %>
<% end %>
<% if params[:unreadable] && @unreadable_sequences.any? %>
Unreadable Sequences
This is likely due to missing privileges. Make sure your user has the SELECT privilege for each sequence.
| Column |
Sequence |
<% @unreadable_sequences.each do |sequence| %>
|
<%= sequence[:table] %>.<%= sequence[:column] %>
<% if sequence[:table_schema] != "public" %>
<%= sequence[:table_schema] %>
<% end %>
|
<% if sequence[:sequence] %>
<%= sequence[:sequence] %>
<% if sequence[:schema] && sequence[:schema] != "public" %>
<%= sequence[:schema] %>
<% end %>
<% else %>
Unable to parse: <%= sequence[:default_value] %>
<% end %>
|
<% end %>
<% end %>
<% if @replica && !@good_replication_lag %>
High Replication Lag
<%= pluralize(@replication_lag.round, "second") %>
<% end %>
<% if @inactive_replication_slots && @inactive_replication_slots.any? %>
Inactive Replication Slots
Inactive replication slots can cause a lot of disk space to be consumed.
For each, run:
SELECT pg_drop_replication_slot('slot_name');
| Name |
<% @inactive_replication_slots.each do |slot| %>
| <%= slot[:slot_name] %> |
<% end %>
<% end %>
<% if @long_running_queries.any? %>
<%= button_to "Kill All", kill_long_running_queries_path, class: "btn btn-danger", style: "float: right;" %>
Long Running Queries
We recommend setting a statement timeout on all non-superusers with:
ALTER ROLE <user> SET statement_timeout TO '60s';
<%= render partial: "live_queries_table", locals: {queries: @long_running_queries, vacuum_progress: {}} %>
<% end %>
<% if @extended && !@good_cache_rate %>
Low Cache Hit Rate
Index Hit Rate: <%= (@index_hit_rate * 100).round(1) %>%
Table Hit Rate: <%= (@table_hit_rate * 100).round(1) %>%
The cache hit rate <%= link_to "should be above 99%", "https://devcenter.heroku.com/articles/understanding-postgres-data-caching", target: "_blank" %> in most cases. You can often increase this by adding more memory.
<% end %>
<% if !@good_total_connections %>
High Number of Connections
<%= pluralize(@total_connections, "connection") %>
<%= link_to "Use connection pooling", "https://www.craigkerstiens.com/2014/05/22/postgres-and-connection-pooling/", target: "_blank" %> for better performance. <%= link_to "PgBouncer", "https://wiki.postgresql.org/wiki/PgBouncer", target: "_blank" %> is a solid option.
<%= render partial: "connections_table", locals: {connection_sources: @database.connection_sources.first(10)} %>
<% end %>
<% if !@good_idle_connections %>
High Number of Connections Idle in Transaction
<%= pluralize(@idle_connections, "connection") %> idle in transaction
Avoid opening transactions and doing work outside the database.
<%= link_to "View queries", live_queries_path(state: "idle in transaction") %>
<% end %>
<% if @transaction_id_danger.any? %>
Vacuuming Needed
The database will shutdown when there are fewer than 1,000,000 transactions left. <%= link_to "Read more", "https://www.postgresql.org/docs/current/static/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND", target: "_blank" %>.
Try <%= link_to "tuning autovacuum", "https://blog.2ndquadrant.com/autovacuum-tuning-basics/", target: "_blank" %> - specifically autovacuum_vacuum_cost_limit.
If that doesn’t work, for each table, run:
VACUUM FREEZE VERBOSE table;
| Table |
Transactions Left |
<% @transaction_id_danger.each do |query| %>
|
<%= query[:table] %>
<% if query[:schema] != "public" %>
<%= query[:schema] %>
<% end %>
|
<%= number_with_delimiter(query[:transactions_left]) %> |
<% end %>
<% end %>
<% if @sequence_danger && @sequence_danger.any? %>
Columns Near Overflow
Consider changing columns to bigint to support a larger range of values.
| Column |
Type |
Values Left |
% Left |
<% @sequence_danger.sort_by { |s| [s[:table], s[:column]] }.each do |query| %>
|
<%= query[:table] %>.<%= query[:column] %>
<% if query[:table_schema] != "public" %>
<%= query[:table_schema] %>
<% end %>
|
<%= query[:column_type] %>
|
<%= number_with_delimiter(query[:max_value] - query[:last_value]) %>
|
<%= number_to_percentage((query[:max_value] - query[:last_value]) * 100.0 / query[:max_value], precision: 2, significant: true) %>
|
<% end %>
<% end %>
<% if @invalid_indexes.any? %>
Invalid Indexes
These indexes exist, but can’t be used. You should recreate them.
| Name |
<% @invalid_indexes.each do |index| %>
|
<%= index[:name] %>
<% if index[:schema] != "public" %>
<%= index[:schema] %>
<% end %>
|
DROP INDEX CONCURRENTLY <%= pghero_pretty_ident(index[:name], schema: index[:schema]) %>;
<%= index[:definition].sub("CREATE INDEX ", "CREATE INDEX CONCURRENTLY ") %>;
|
<% end %>
<% end %>
<% if @invalid_constraints.any? %>
Invalid Constraints
These constraints are marked as NOT VALID. You should validate them.
| Name |
<% @invalid_constraints.each do |constraint| %>
|
<%= constraint[:name] %>
<% if constraint[:schema] != "public" %>
<%= constraint[:schema] %>
<% end %>
|
ALTER TABLE <%= pghero_pretty_ident(constraint[:table], schema: constraint[:schema]) %> VALIDATE CONSTRAINT <%= pghero_pretty_ident(constraint[:name]) %>;
|
<% end %>
<% end %>
<% if @duplicate_indexes && @duplicate_indexes.any? %>
Duplicate Indexes
These indexes exist, but aren’t needed. Remove them
<% if @show_migrations %>
with a migration
<% end %>
for faster writes.
rails generate migration remove_unneeded_indexes
And paste
<% @duplicate_indexes.each do |query| %>
<%= pghero_remove_index(query[:unneeded_index]) %><% end %>
| Details |
<% @duplicate_indexes.each do |index| %>
<% unneeded_index = index[:unneeded_index] %>
<% covering_index = index[:covering_index] %>
On <%= unneeded_index[:table] %>
<%= unneeded_index[:name] %> (<%= unneeded_index[:columns].join(", ") %>)
is covered by
<%= covering_index[:name] %> (<%= covering_index[:columns].join(", ") %>)
|
<% end %>
<% end %>
<% if @suggested_indexes.any? %>
Suggested Indexes
Add indexes to speed up queries.
<% if @show_migrations %>
Here’s a
migration to help.
<% end %>
rails generate migration add_suggested_indexes
And paste
commit_db_transaction
<% @suggested_indexes.each do |index| %>
<% if index[:using] && index[:using] != "btree" %>
add_index <%= index[:table].to_sym.inspect %>, <%= index[:columns].first.inspect %>, using: <%= index[:using].inspect %>, algorithm: :concurrently
<% else %>
add_index <%= index[:table].to_sym.inspect %>, [<%= index[:columns].map(&:to_sym).map(&:inspect).join(", ") %>], algorithm: :concurrently<% end %>
<% end %>
<% @suggested_indexes.each_with_index do |index, i| %>
<%= render partial: "suggested_index", locals: {index: index, details: index[:details]} %>
to speed up
<%= render partial: "queries_table", locals: {queries: index[:queries].map { |q| @query_stats_by_query[q] }, suggested_indexes: false} %>
<% end %>
<% end %>
<% if !@query_stats_enabled %>
Query Stats
<% if @query_stats_available && !@query_stats_extension_enabled %>
Query stats are available but not enabled.
<%= button_to "Enable", enable_query_stats_path, class: "btn btn-info" %>
<% else %>
Make them available by adding the following lines to postgresql.conf:
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = all
Restart the server for the changes to take effect.
<% end %>
<% end %>
<% if @query_stats_enabled && @slow_queries.any? %>
Slow Queries
Slow queries take <%= @database.slow_query_ms %> ms or more on average and have been called at least <%= @database.slow_query_calls %> times.
<%= link_to "Explain queries", explain_path %> to see where to add indexes.
<%= render partial: "queries_table", locals: {queries: @slow_queries} %>
<% end %>
<% if @extended && @unused_indexes.any? %>
Unused Indexes
Unused indexes cause unnecessary overhead. Remove them
<% if @show_migrations %>
with a migration
<% end %>
for faster writes.
rails generate migration remove_unused_indexes
And paste
<% @unused_indexes.each do |query| %>
<%= pghero_remove_index(query)%><% end %>
| Name |
Index Size |
<% @unused_indexes.each do |query| %>
| <%= query[:index] %> on <%= query[:table] %> |
<%= query[:size] %> |
<% end %>
<% end %>
<%= javascript_tag nonce: true do %>
highlightQueries();
<% end %>