pghero_fork 2.7.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +391 -0
- data/CONTRIBUTING.md +42 -0
- data/LICENSE.txt +22 -0
- data/README.md +3 -0
- data/app/assets/images/pghero/favicon.png +0 -0
- data/app/assets/javascripts/pghero/Chart.bundle.js +20755 -0
- data/app/assets/javascripts/pghero/application.js +158 -0
- data/app/assets/javascripts/pghero/chartkick.js +2436 -0
- data/app/assets/javascripts/pghero/highlight.pack.js +2 -0
- data/app/assets/javascripts/pghero/jquery.js +10872 -0
- data/app/assets/javascripts/pghero/nouislider.js +2672 -0
- data/app/assets/stylesheets/pghero/application.css +514 -0
- data/app/assets/stylesheets/pghero/arduino-light.css +86 -0
- data/app/assets/stylesheets/pghero/nouislider.css +310 -0
- data/app/controllers/pg_hero/home_controller.rb +449 -0
- data/app/helpers/pg_hero/home_helper.rb +30 -0
- data/app/views/layouts/pg_hero/application.html.erb +68 -0
- data/app/views/pg_hero/home/_connections_table.html.erb +16 -0
- data/app/views/pg_hero/home/_live_queries_table.html.erb +51 -0
- data/app/views/pg_hero/home/_queries_table.html.erb +72 -0
- data/app/views/pg_hero/home/_query_stats_slider.html.erb +16 -0
- data/app/views/pg_hero/home/_suggested_index.html.erb +18 -0
- data/app/views/pg_hero/home/connections.html.erb +32 -0
- data/app/views/pg_hero/home/explain.html.erb +27 -0
- data/app/views/pg_hero/home/index.html.erb +518 -0
- data/app/views/pg_hero/home/index_bloat.html.erb +72 -0
- data/app/views/pg_hero/home/live_queries.html.erb +11 -0
- data/app/views/pg_hero/home/maintenance.html.erb +55 -0
- data/app/views/pg_hero/home/queries.html.erb +33 -0
- data/app/views/pg_hero/home/relation_space.html.erb +14 -0
- data/app/views/pg_hero/home/show_query.html.erb +106 -0
- data/app/views/pg_hero/home/space.html.erb +83 -0
- data/app/views/pg_hero/home/system.html.erb +34 -0
- data/app/views/pg_hero/home/tune.html.erb +53 -0
- data/config/routes.rb +32 -0
- data/lib/generators/pghero/config_generator.rb +13 -0
- data/lib/generators/pghero/query_stats_generator.rb +18 -0
- data/lib/generators/pghero/space_stats_generator.rb +18 -0
- data/lib/generators/pghero/templates/config.yml.tt +46 -0
- data/lib/generators/pghero/templates/query_stats.rb.tt +15 -0
- data/lib/generators/pghero/templates/space_stats.rb.tt +13 -0
- data/lib/pghero.rb +246 -0
- data/lib/pghero/connection.rb +5 -0
- data/lib/pghero/database.rb +175 -0
- data/lib/pghero/engine.rb +16 -0
- data/lib/pghero/methods/basic.rb +160 -0
- data/lib/pghero/methods/connections.rb +77 -0
- data/lib/pghero/methods/constraints.rb +30 -0
- data/lib/pghero/methods/explain.rb +29 -0
- data/lib/pghero/methods/indexes.rb +332 -0
- data/lib/pghero/methods/kill.rb +28 -0
- data/lib/pghero/methods/maintenance.rb +93 -0
- data/lib/pghero/methods/queries.rb +75 -0
- data/lib/pghero/methods/query_stats.rb +349 -0
- data/lib/pghero/methods/replication.rb +74 -0
- data/lib/pghero/methods/sequences.rb +124 -0
- data/lib/pghero/methods/settings.rb +37 -0
- data/lib/pghero/methods/space.rb +141 -0
- data/lib/pghero/methods/suggested_indexes.rb +329 -0
- data/lib/pghero/methods/system.rb +287 -0
- data/lib/pghero/methods/tables.rb +68 -0
- data/lib/pghero/methods/users.rb +87 -0
- data/lib/pghero/query_stats.rb +5 -0
- data/lib/pghero/space_stats.rb +5 -0
- data/lib/pghero/stats.rb +6 -0
- data/lib/pghero/version.rb +3 -0
- data/lib/tasks/pghero.rake +27 -0
- data/licenses/LICENSE-chart.js.txt +9 -0
- data/licenses/LICENSE-chartkick.js.txt +22 -0
- data/licenses/LICENSE-highlight.js.txt +29 -0
- data/licenses/LICENSE-jquery.txt +20 -0
- data/licenses/LICENSE-moment.txt +22 -0
- data/licenses/LICENSE-nouislider.txt +21 -0
- metadata +130 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module PgHero
|
2
|
+
module HomeHelper
|
3
|
+
def pghero_pretty_ident(table, schema: nil)
|
4
|
+
ident = table
|
5
|
+
if schema && schema != "public"
|
6
|
+
ident = "#{schema}.#{table}"
|
7
|
+
end
|
8
|
+
if ident =~ /\A[a-z0-9_]+\z/
|
9
|
+
ident
|
10
|
+
else
|
11
|
+
@database.quote_ident(ident)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def pghero_js_var(name, value)
|
16
|
+
"var #{name} = #{json_escape(value.to_json(root: false))};".html_safe
|
17
|
+
end
|
18
|
+
|
19
|
+
def pghero_remove_index(query)
|
20
|
+
if query[:columns]
|
21
|
+
columns = query[:columns].map(&:to_sym)
|
22
|
+
columns = columns.first if columns.size == 1
|
23
|
+
end
|
24
|
+
ret = String.new("remove_index #{query[:table].to_sym.inspect}")
|
25
|
+
ret << ", name: #{(query[:name] || query[:index]).to_s.inspect}"
|
26
|
+
ret << ", column: #{columns.inspect}" if columns
|
27
|
+
ret
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= [@databases.size > 1 ? @database.name : "PgHero", @title].compact.join(" / ") %></title>
|
5
|
+
|
6
|
+
<meta charset="utf-8" />
|
7
|
+
<link rel="shortcut icon" type="image/x-icon" href="<%= root_path %>favicon.png">
|
8
|
+
<link rel="stylesheet" media="screen" href="<%= root_path %>application.css">
|
9
|
+
<script type="text/javascript" src="<%= root_path %>application.js"></script>
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
<div class="container">
|
13
|
+
<div class="alert alert-<%= alert ? "danger" : "info" %>">
|
14
|
+
<% if alert %>
|
15
|
+
<%= alert %>
|
16
|
+
<% elsif notice %>
|
17
|
+
<%= notice %>
|
18
|
+
<% elsif Rails.env.development? %>
|
19
|
+
Do not use development information to make decisions about your production environment
|
20
|
+
<% else %>
|
21
|
+
<%= link_to "PgHero", root_path %>
|
22
|
+
<% end %>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<div class="grid">
|
26
|
+
<div class="col-3-12">
|
27
|
+
<% if @databases.size > 1 %>
|
28
|
+
<p class="nav-header"><%= @database.name %></p>
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
<ul class="nav">
|
32
|
+
<!-- poor man's active_link_to -->
|
33
|
+
<li class="<%= controller.action_name == "index" ? "active" : "" %>"><%= link_to "Overview", root_path %></li>
|
34
|
+
<% if @system_stats_enabled %>
|
35
|
+
<li class="<%= controller.action_name == "system" ? "active" : "" %>"><%= link_to "System", system_path %></li>
|
36
|
+
<% end %>
|
37
|
+
<% if @query_stats_enabled %>
|
38
|
+
<li class="<%= controller.action_name == "queries" ? "active" : "" %>"><%= link_to "Queries", queries_path %></li>
|
39
|
+
<% end %>
|
40
|
+
<li class="<%= controller.action_name == "space" ? "active" : "" %>"><%= link_to "Space", space_path %></li>
|
41
|
+
<li class="<%= controller.action_name == "connections" ? "active" : "" %>"><%= link_to "Connections", connections_path %></li>
|
42
|
+
<li class="<%= controller.action_name == "live_queries" ? "active" : "" %>"><%= link_to "Live Queries", live_queries_path %></li>
|
43
|
+
<% unless @database.replica? %>
|
44
|
+
<li class="<%= controller.action_name == "maintenance" ? "active" : "" %>"><%= link_to "Maintenance", maintenance_path %></li>
|
45
|
+
<% end %>
|
46
|
+
<li class="<%= controller.action_name == "explain" ? "active" : "" %>"><%= link_to "Explain", explain_path %></li>
|
47
|
+
<li class="<%= controller.action_name == "tune" ? "active" : "" %>"><%= link_to "Tune", tune_path %></li>
|
48
|
+
</ul>
|
49
|
+
|
50
|
+
<% if @databases.size > 1 %>
|
51
|
+
<p class="nav-header">Databases</p>
|
52
|
+
<ul class="nav">
|
53
|
+
<% @databases.each do |database| %>
|
54
|
+
<li class="<%= ("active-database" if @database.id == database.id) %>">
|
55
|
+
<%= link_to database.name, action_name == "show_query" ? root_path(database: database.id) : {database: database.id} %>
|
56
|
+
</li>
|
57
|
+
<% end %>
|
58
|
+
</ul>
|
59
|
+
<% end %>
|
60
|
+
</div>
|
61
|
+
|
62
|
+
<div class="col-9-12">
|
63
|
+
<%= yield %>
|
64
|
+
</div>
|
65
|
+
</div>
|
66
|
+
</div>
|
67
|
+
</body>
|
68
|
+
</html>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<table class="table">
|
2
|
+
<thead>
|
3
|
+
<tr>
|
4
|
+
<th>Top Sources</th>
|
5
|
+
<th style="width: 20%;">Connections</th>
|
6
|
+
</tr>
|
7
|
+
</thead>
|
8
|
+
<tbody>
|
9
|
+
<% connection_sources.each do |source| %>
|
10
|
+
<tr>
|
11
|
+
<td><%= source[:source] %> <div class="text-muted"><%= [source[:user], source[:database], source[:ip]].compact.join(" - ") %></div></td>
|
12
|
+
<td><%= number_with_delimiter(source[:total_connections]) %></td>
|
13
|
+
</tr>
|
14
|
+
<% end %>
|
15
|
+
</tbody>
|
16
|
+
</table>
|
@@ -0,0 +1,51 @@
|
|
1
|
+
<table class="table queries">
|
2
|
+
<thead>
|
3
|
+
<tr>
|
4
|
+
<th style="width: 25%;">Pid</th>
|
5
|
+
<th style="width: 25%;">Duration</th>
|
6
|
+
<th style="width: 25%;">State</th>
|
7
|
+
<th style="width: 25%;"></th>
|
8
|
+
</tr>
|
9
|
+
</thead>
|
10
|
+
<tbody>
|
11
|
+
<% queries.reverse.each do |query| %>
|
12
|
+
<tr>
|
13
|
+
<td><%= query[:pid] %></td>
|
14
|
+
<td>
|
15
|
+
<% sec = query[:duration_ms] / 1000.0 %>
|
16
|
+
<% if sec < 1.minute %>
|
17
|
+
<%= sec.round(1) %> s
|
18
|
+
<% elsif sec < 1.day %>
|
19
|
+
<%= Time.at(sec).utc.strftime("%H:%M:%S") %>
|
20
|
+
<% else %>
|
21
|
+
<% days = (sec / 1.day).floor %>
|
22
|
+
<%= days %>d <%= Time.at(sec - days.days).utc.strftime("%H:%M:%S") %>
|
23
|
+
<% end %>
|
24
|
+
</td>
|
25
|
+
<td>
|
26
|
+
<%= query[:state] %>
|
27
|
+
<% if vacuum_progress[query[:pid]] %>
|
28
|
+
<br />
|
29
|
+
<strong><%= vacuum_progress[query[:pid]][:phase] %></strong>
|
30
|
+
<% end %>
|
31
|
+
</td>
|
32
|
+
<td class="text-right">
|
33
|
+
<% unless @database.filter_data %>
|
34
|
+
<%= button_to "Explain", explain_path, params: {query: query[:query]}, form: {target: "_blank"}, class: "btn btn-info" %>
|
35
|
+
<% end %>
|
36
|
+
<%= button_to "Kill", kill_path(pid: query[:pid]), class: "btn btn-danger" %>
|
37
|
+
</td>
|
38
|
+
</tr>
|
39
|
+
<tr>
|
40
|
+
<td colspan="6" style="border-top: none; padding: 0;">
|
41
|
+
<%= query[:source] %> <span class="text-muted"><%= query[:user] %></span>
|
42
|
+
<pre style="margin-top: 1em;"><code><%= query[:query] %></code></pre>
|
43
|
+
</td>
|
44
|
+
</tr>
|
45
|
+
<% end %>
|
46
|
+
</tbody>
|
47
|
+
</table>
|
48
|
+
|
49
|
+
<script>
|
50
|
+
highlightQueries();
|
51
|
+
</script>
|
@@ -0,0 +1,72 @@
|
|
1
|
+
<table class="table queries-table">
|
2
|
+
<% unless local_assigns[:xhr] %>
|
3
|
+
<thead>
|
4
|
+
<tr>
|
5
|
+
<th style="width: 33.33%;"><%= local_assigns[:sort_headers] ? link_to("Total Time", {sort: nil}, data: {sort: nil}) : "Total Time" %></th>
|
6
|
+
<th style="width: 33.33%;"><%= local_assigns[:sort_headers] ? link_to("Average Time", {sort: "average_time"}, data: {sort: "average_time"}) : "Average Time" %></th>
|
7
|
+
<th style="width: 33.33%;"><%= local_assigns[:sort_headers] ? link_to("Calls", {sort: "calls"}, data: {sort: "calls"}) : "Calls" %></th>
|
8
|
+
</tr>
|
9
|
+
</thead>
|
10
|
+
<% end %>
|
11
|
+
<tbody id="queries">
|
12
|
+
<% if queries.empty? %>
|
13
|
+
<tr>
|
14
|
+
<td colspan="3">
|
15
|
+
<p class="queries-info text-muted">
|
16
|
+
<% if local_assigns[:xhr] %>
|
17
|
+
No data available for this time.
|
18
|
+
<% else %>
|
19
|
+
...
|
20
|
+
<% end %>
|
21
|
+
</p>
|
22
|
+
</td>
|
23
|
+
</tr>
|
24
|
+
<% end %>
|
25
|
+
<% queries.each do |query| %>
|
26
|
+
<tr>
|
27
|
+
<td>
|
28
|
+
<%= number_with_delimiter(query[:total_minutes].round) %> min
|
29
|
+
<span class="percent">
|
30
|
+
<% percent = query[:total_percent] %>
|
31
|
+
<% if percent > 1 %>
|
32
|
+
<%= percent.round %>%
|
33
|
+
<% elsif percent > 0.1 %>
|
34
|
+
<%= percent.round(1) %>%
|
35
|
+
<% else %>
|
36
|
+
< 0.1%
|
37
|
+
<% end %>
|
38
|
+
</span>
|
39
|
+
</td>
|
40
|
+
<td>
|
41
|
+
<%= number_with_delimiter(query[:average_time].round) %> ms
|
42
|
+
</td>
|
43
|
+
<td>
|
44
|
+
<%= number_with_delimiter(query[:calls]) %>
|
45
|
+
|
46
|
+
<span class="user">
|
47
|
+
<% if query[:user] %>
|
48
|
+
<%= query[:user] %>
|
49
|
+
<% if @show_details %>
|
50
|
+
·
|
51
|
+
<% end %>
|
52
|
+
<% end %>
|
53
|
+
<% if @show_details && query[:query_hash] %>
|
54
|
+
<%= link_to "details", show_query_path(query[:query_hash], user: query[:user]), target: "_blank" %>
|
55
|
+
<% end %>
|
56
|
+
</span>
|
57
|
+
</td>
|
58
|
+
</tr>
|
59
|
+
<tr>
|
60
|
+
<td colspan="3" style="border-top: none; padding: 0;">
|
61
|
+
<pre><code style="max-height: 230px; overflow: hidden;" onclick="this.style.maxHeight = 'none';"><%= query[:query] %></code></pre>
|
62
|
+
<% if query[:query] == "<insufficient privilege>" %>
|
63
|
+
<p class="text-muted">For security reasons, only superusers can see queries executed by other users.</p>
|
64
|
+
<% end %>
|
65
|
+
<% if local_assigns[:suggested_indexes] != false && (details = @suggested_indexes_by_query[query[:query]]) && (details[:index] || @debug) %>
|
66
|
+
<%= render partial: "suggested_index", locals: {index: details[:index], details: details} %>
|
67
|
+
<% end %>
|
68
|
+
</td>
|
69
|
+
</tr>
|
70
|
+
<% end %>
|
71
|
+
</tbody>
|
72
|
+
</table>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<div id="slider-container">
|
2
|
+
<div id="slider"></div>
|
3
|
+
<div id="range-end"></div>
|
4
|
+
<div id="range-start"></div>
|
5
|
+
</div>
|
6
|
+
|
7
|
+
<script>
|
8
|
+
<%= pghero_js_var("sort", @sort) %>
|
9
|
+
<%= pghero_js_var("minAverageTime", @min_average_time) %>
|
10
|
+
<%= pghero_js_var("minCalls", @min_calls) %>
|
11
|
+
<%= pghero_js_var("debug", @debug) %>
|
12
|
+
<%= pghero_js_var("startAt", params[:start_at] ? @start_at.to_i * 1000 : nil) %>
|
13
|
+
<%= pghero_js_var("endAt", @end_at.to_i * 1000) %>
|
14
|
+
|
15
|
+
initSlider();
|
16
|
+
</script>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<% if index && !details[:covering_index] %>
|
2
|
+
<% unless @debug %>
|
3
|
+
<div style="float: right; color: #f0ad4e; margin-top: 0px; padding: 10px; cursor: pointer;" onclick="document.getElementById('details-<%= index.object_id %>').style.display = 'block'; this.style.display = 'none';">Details</div>
|
4
|
+
<% end %>
|
5
|
+
<code><pre style="color: #eee; background-color: #333;">CREATE INDEX CONCURRENTLY ON <%= index[:table] %><% if index[:using] %> USING <%= index[:using] %><% end %> (<%= index[:columns].join(", ") %>)</pre></code>
|
6
|
+
<% end %>
|
7
|
+
<div id="details-<%= index.object_id %>" style="<%= "display: none;" unless @debug %>">
|
8
|
+
<code><pre style="color: #f0ad4e; background-color: #333;"><% if details[:explanation] %><%= details[:explanation] %>
|
9
|
+
<% end %><% if details[:row_estimates] %>Rows: <%= details[:rows] %>
|
10
|
+
Row progression: <%= details[:row_progression].to_a.join(", ") %>
|
11
|
+
|
12
|
+
Row estimates
|
13
|
+
<%= details[:row_estimates].to_a.map { |k, v| "- #{k}: #{v}" }.join("\n") %><% end %><% if details[:table_indexes] %>
|
14
|
+
|
15
|
+
Existing indexes
|
16
|
+
<% details[:table_indexes].sort_by { |i| [i[:primary] ? 0 : 1, i[:columns]] }.each do |i3| %>- <%= i3[:columns].join(", ") %><% if i3[:using] != "btree" %> <%= i3[:using].to_s.upcase %><% end %><% if i3[:primary] %> PRIMARY<% elsif i3[:unique] %> UNIQUE<% end %>
|
17
|
+
<% end %><% end %></pre></code>
|
18
|
+
</div>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<div class="content">
|
2
|
+
<h1>Connections</h1>
|
3
|
+
|
4
|
+
<p><%= pluralize(@total_connections, "connection") %></p>
|
5
|
+
|
6
|
+
<% if @total_connections > 0 %>
|
7
|
+
<h3>By Database</h3>
|
8
|
+
|
9
|
+
<div id="chart-1" class="chart" style="height: 260px; line-height: 260px; margin-bottom: 20px;">Loading...</div>
|
10
|
+
<script>
|
11
|
+
new Chartkick.PieChart("chart-1", <%= json_escape(@connections_by_database.to_json).html_safe %>);
|
12
|
+
</script>
|
13
|
+
|
14
|
+
<h3>By User</h3>
|
15
|
+
|
16
|
+
<div id="chart-2" class="chart" style="height: 260px; line-height: 260px; margin-bottom: 20px;">Loading...</div>
|
17
|
+
<script>
|
18
|
+
new Chartkick.PieChart("chart-2", <%= json_escape(@connections_by_user.to_json).html_safe %>);
|
19
|
+
</script>
|
20
|
+
|
21
|
+
<% if @connections_by_ssl_status %>
|
22
|
+
<h3>By Security</h3>
|
23
|
+
|
24
|
+
<div id="chart-3" class="chart" style="height: 260px; line-height: 260px; margin-bottom: 20px;">Loading...</div>
|
25
|
+
<script>
|
26
|
+
new Chartkick.PieChart("chart-3", <%= json_escape(@connections_by_ssl_status.to_json).html_safe %>);
|
27
|
+
</script>
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
<%= render partial: "connections_table", locals: {connection_sources: @connection_sources} %>
|
31
|
+
<% end %>
|
32
|
+
</div>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<div class="content">
|
2
|
+
<h1>Explain</h1>
|
3
|
+
|
4
|
+
<%= form_tag explain_path do %>
|
5
|
+
<div class="field"><%= text_area_tag :query, @query, placeholder: "Enter a SQL query" %></div>
|
6
|
+
<p>
|
7
|
+
<%= submit_tag "Explain", class: "btn btn-info", style: "margin-right: 10px;" %>
|
8
|
+
<%= submit_tag "Analyze", class: "btn btn-danger", style: "margin-right: 10px;" %>
|
9
|
+
<%= submit_tag "Visualize", class: "btn btn-danger" %>
|
10
|
+
</p>
|
11
|
+
<% end %>
|
12
|
+
|
13
|
+
<% if @explanation %>
|
14
|
+
<% if @visualize %>
|
15
|
+
<p>Paste the output below into the <%= link_to "Postgres Explain Visualizer", "https://tatiyants.com/pev/#/plans/new", target: "_blank" %></p>
|
16
|
+
<% end %>
|
17
|
+
<pre><code><%= @explanation %></code></pre>
|
18
|
+
<% unless @visualize %>
|
19
|
+
<p><%= link_to "See how to interpret this", "https://www.postgresql.org/docs/current/static/using-explain.html", target: "_blank" %></p>
|
20
|
+
<% end %>
|
21
|
+
<% if (index = @suggested_index) %>
|
22
|
+
<%= render partial: "suggested_index", locals: {index: index, details: index[:details]} %>
|
23
|
+
<% end %>
|
24
|
+
<% elsif @error %>
|
25
|
+
<div class="alert alert-danger"><%= @error %></div>
|
26
|
+
<% end %>
|
27
|
+
</div>
|
@@ -0,0 +1,518 @@
|
|
1
|
+
<div id="status">
|
2
|
+
<% if @replica %>
|
3
|
+
<div class="alert alert-<%= @good_replication_lag ? "success" : "warning" %>">
|
4
|
+
<% if @replication_lag %>
|
5
|
+
<% if @good_replication_lag %>
|
6
|
+
Healthy replication lag
|
7
|
+
<% else %>
|
8
|
+
High replication lag
|
9
|
+
<% end %>
|
10
|
+
<span class="tiny"><%= number_with_delimiter((@replication_lag * 1000).round) %> ms</span>
|
11
|
+
<% else %>
|
12
|
+
Replication lag not supported
|
13
|
+
<% end %>
|
14
|
+
</div>
|
15
|
+
<% elsif @inactive_replication_slots.any? %>
|
16
|
+
<div class="alert alert-warning">
|
17
|
+
<%= pluralize(@inactive_replication_slots.size, "inactive replication slot") %>
|
18
|
+
</div>
|
19
|
+
<% end %>
|
20
|
+
<div class="alert alert-<%= @long_running_queries.empty? ? "success" : "warning" %>">
|
21
|
+
<% if @long_running_queries.any? %>
|
22
|
+
<%= pluralize(@long_running_queries.size, "long running query") %>
|
23
|
+
<% else %>
|
24
|
+
No long running queries
|
25
|
+
<% end %>
|
26
|
+
<% if @autovacuum_queries.any? %>
|
27
|
+
<span class="tiny"><%= @autovacuum_queries.size %> autovacuum</span>
|
28
|
+
<% end %>
|
29
|
+
</div>
|
30
|
+
<% if @extended %>
|
31
|
+
<div class="alert alert-<%= @good_cache_rate ? "success" : "warning" %>">
|
32
|
+
<% if @good_cache_rate %>
|
33
|
+
Cache hit rate above <%= @database.cache_hit_rate_threshold %>%
|
34
|
+
<% else %>
|
35
|
+
Low cache hit rate
|
36
|
+
<% end %>
|
37
|
+
</div>
|
38
|
+
<% end %>
|
39
|
+
<div class="alert alert-<%= @good_total_connections && @good_idle_connections ? "success" : "warning" %>">
|
40
|
+
<% if !@good_total_connections %>
|
41
|
+
High number of connections <span class="tiny"><%= @total_connections %></span>
|
42
|
+
<% elsif !@good_idle_connections %>
|
43
|
+
High number of connections idle in transaction <span class="tiny"><%= @idle_connections %></span>
|
44
|
+
<% else %>
|
45
|
+
Connections healthy <span class="tiny"><%= @total_connections %></span>
|
46
|
+
<% end %>
|
47
|
+
</div>
|
48
|
+
<div class="alert alert-<%= @transaction_id_danger.empty? ? "success" : "warning" %>">
|
49
|
+
<% if @transaction_id_danger.any? %>
|
50
|
+
<%= pluralize(@transaction_id_danger.size, "table") %> not vacuuming properly
|
51
|
+
<% else %>
|
52
|
+
Vacuuming healthy
|
53
|
+
<% end %>
|
54
|
+
</div>
|
55
|
+
<div class="alert alert-<%= @sequence_danger && @sequence_danger.empty? ? "success" : "warning" %>">
|
56
|
+
<% if @sequence_danger.any? %>
|
57
|
+
<%= pluralize(@sequence_danger.size, "column") %> approaching overflow
|
58
|
+
<% else %>
|
59
|
+
No columns near integer overflow
|
60
|
+
<% end %>
|
61
|
+
<% if @unreadable_sequences.any? %>
|
62
|
+
(<%= link_to pluralize(@unreadable_sequences.size, "unreadable sequence", "unreadable sequences"), {unreadable: "t"} %>)
|
63
|
+
<% end %>
|
64
|
+
</div>
|
65
|
+
<div class="alert alert-<%= @invalid_indexes.empty? && @invalid_constraints.empty? ? "success" : "warning" %>">
|
66
|
+
<% if @invalid_indexes.empty? && @invalid_constraints.empty? %>
|
67
|
+
No invalid indexes or constraints
|
68
|
+
<% else %>
|
69
|
+
<% if @invalid_indexes.any? %>
|
70
|
+
<%= pluralize(@invalid_indexes.size, "invalid index", "invalid indexes") %>
|
71
|
+
<% end %>
|
72
|
+
<% if @invalid_constraints.any? %>
|
73
|
+
<% if @invalid_indexes.any? %>and<% end %>
|
74
|
+
<%= pluralize(@invalid_constraints.size, "invalid constraint", "invalid constraints") %>
|
75
|
+
<% end %>
|
76
|
+
<% end %>
|
77
|
+
</div>
|
78
|
+
<% if @duplicate_indexes %>
|
79
|
+
<div class="alert alert-<%= @duplicate_indexes.empty? ? "success" : "warning" %>">
|
80
|
+
<% if @duplicate_indexes.any? %>
|
81
|
+
<%= pluralize(@duplicate_indexes.size, "duplicate index", "duplicate indexes") %>
|
82
|
+
<% else %>
|
83
|
+
No duplicate indexes
|
84
|
+
<% end %>
|
85
|
+
</div>
|
86
|
+
<% end %>
|
87
|
+
<% if @database.suggested_indexes_enabled? %>
|
88
|
+
<div class="alert alert-<%= @suggested_indexes.empty? ? "success" : "warning" %>">
|
89
|
+
<% if @suggested_indexes.any? %>
|
90
|
+
<%= pluralize(@suggested_indexes.size, "suggested index", "suggested indexes") %>
|
91
|
+
<% else %>
|
92
|
+
No suggested indexes
|
93
|
+
<% end %>
|
94
|
+
</div>
|
95
|
+
<% end %>
|
96
|
+
<div class="alert alert-<%= @query_stats_enabled && @slow_queries.empty? ? "success" : "warning" %>">
|
97
|
+
<% if !@query_stats_enabled %>
|
98
|
+
Query stats must be enabled for slow queries
|
99
|
+
<% elsif @slow_queries.any? %>
|
100
|
+
<%= pluralize(@slow_queries.size, "slow query") %>
|
101
|
+
<% else %>
|
102
|
+
No slow queries
|
103
|
+
<% end %>
|
104
|
+
</div>
|
105
|
+
<% if @extended %>
|
106
|
+
<div class="alert alert-<%= @unused_indexes.empty? ? "success" : "warning" %>">
|
107
|
+
<% if @unused_indexes.any? %>
|
108
|
+
<%= pluralize(@unused_indexes.size, "unused index", "unused indexes") %>
|
109
|
+
<% else %>
|
110
|
+
No unused indexes
|
111
|
+
<% end %>
|
112
|
+
</div>
|
113
|
+
<% end %>
|
114
|
+
</div>
|
115
|
+
|
116
|
+
<% if params[:unreadable] && @unreadable_sequences.any? %>
|
117
|
+
<div class="content">
|
118
|
+
<h1>Unreadable Sequences</h1>
|
119
|
+
|
120
|
+
<p>This is likely due to missing privileges. Make sure your user has the SELECT privilege for each sequence.</p>
|
121
|
+
|
122
|
+
<table class="table">
|
123
|
+
<thead>
|
124
|
+
<tr>
|
125
|
+
<th style="width: 33%;">Column</th>
|
126
|
+
<th>Sequence</th>
|
127
|
+
</tr>
|
128
|
+
</thead>
|
129
|
+
<tbody>
|
130
|
+
<% @unreadable_sequences.each do |sequence| %>
|
131
|
+
<tr>
|
132
|
+
<td>
|
133
|
+
<%= sequence[:table] %>.<%= sequence[:column] %>
|
134
|
+
<% if sequence[:table_schema] != "public" %>
|
135
|
+
<span class="text-muted"><%= sequence[:table_schema] %></span>
|
136
|
+
<% end %>
|
137
|
+
</td>
|
138
|
+
<td>
|
139
|
+
<% if sequence[:sequence] %>
|
140
|
+
<%= sequence[:sequence] %>
|
141
|
+
<% if sequence[:schema] && sequence[:schema] != "public" %>
|
142
|
+
<span class="text-muted"><%= sequence[:schema] %></span>
|
143
|
+
<% end %>
|
144
|
+
<% else %>
|
145
|
+
Unable to parse: <%= sequence[:default_value] %>
|
146
|
+
<% end %>
|
147
|
+
</td>
|
148
|
+
</tr>
|
149
|
+
<% end %>
|
150
|
+
</tbody>
|
151
|
+
</table>
|
152
|
+
</div>
|
153
|
+
<% end %>
|
154
|
+
|
155
|
+
<% if @replica && !@good_replication_lag %>
|
156
|
+
<div class="content">
|
157
|
+
<h1>High Replication Lag</h1>
|
158
|
+
|
159
|
+
<p><%= pluralize(@replication_lag.round, "second") %></p>
|
160
|
+
</div>
|
161
|
+
<% end %>
|
162
|
+
|
163
|
+
<% if @inactive_replication_slots && @inactive_replication_slots.any? %>
|
164
|
+
<div class="content">
|
165
|
+
<h1>Inactive Replication Slots</h1>
|
166
|
+
<p>Inactive replication slots can cause a lot of disk space to be consumed.</p>
|
167
|
+
<p>For each, run:</p>
|
168
|
+
<pre><code>SELECT pg_drop_replication_slot('slot_name');</code></pre>
|
169
|
+
<table class="table">
|
170
|
+
<thead>
|
171
|
+
<tr>
|
172
|
+
<th>Name</th>
|
173
|
+
</tr>
|
174
|
+
</thead>
|
175
|
+
<tbody>
|
176
|
+
<% @inactive_replication_slots.each do |slot| %>
|
177
|
+
<tr>
|
178
|
+
<td><%= slot[:slot_name] %></td>
|
179
|
+
</tr>
|
180
|
+
<% end %>
|
181
|
+
</tbody>
|
182
|
+
</table>
|
183
|
+
</div>
|
184
|
+
<% end %>
|
185
|
+
|
186
|
+
<% if @long_running_queries.any? %>
|
187
|
+
<div class="content">
|
188
|
+
<%= button_to "Kill All", kill_long_running_queries_path, class: "btn btn-danger", style: "float: right;" %>
|
189
|
+
<h1>Long Running Queries</h1>
|
190
|
+
|
191
|
+
<p>We recommend setting a statement timeout on all non-superusers with:</p>
|
192
|
+
|
193
|
+
<pre><code>ALTER ROLE <user> SET statement_timeout TO '60s';</code></pre>
|
194
|
+
|
195
|
+
<%= render partial: "live_queries_table", locals: {queries: @long_running_queries, vacuum_progress: {}} %>
|
196
|
+
</div>
|
197
|
+
<% end %>
|
198
|
+
|
199
|
+
<% if @extended && !@good_cache_rate %>
|
200
|
+
<div class="content">
|
201
|
+
<h1>Low Cache Hit Rate</h1>
|
202
|
+
|
203
|
+
<p>
|
204
|
+
Index Hit Rate: <%= (@index_hit_rate * 100).round(1) %>%
|
205
|
+
<br />
|
206
|
+
Table Hit Rate: <%= (@table_hit_rate * 100).round(1) %>%
|
207
|
+
</p>
|
208
|
+
|
209
|
+
<p>
|
210
|
+
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.
|
211
|
+
<!-- TODO better suggestions -->
|
212
|
+
</p>
|
213
|
+
</div>
|
214
|
+
<% end %>
|
215
|
+
|
216
|
+
<% if !@good_total_connections %>
|
217
|
+
<div class="content">
|
218
|
+
<h1>High Number of Connections</h1>
|
219
|
+
<p><%= pluralize(@total_connections, "connection") %></p>
|
220
|
+
|
221
|
+
<p><%= link_to "Use connection pooling", "http://www.craigkerstiens.com/2014/05/22/on-connection-pooling/", target: "_blank" %> for better performance. <%= link_to "PgBouncer", "https://wiki.postgresql.org/wiki/PgBouncer", target: "_blank" %> is a solid option.</p>
|
222
|
+
|
223
|
+
<%= render partial: "connections_table", locals: {connection_sources: @database.connection_sources.first(10)} %>
|
224
|
+
</div>
|
225
|
+
<% end %>
|
226
|
+
|
227
|
+
<% if !@good_idle_connections %>
|
228
|
+
<div class="content">
|
229
|
+
<h1>High Number of Connections Idle in Transaction</h1>
|
230
|
+
<p><%= pluralize(@idle_connections, "connection") %> idle in transaction</p>
|
231
|
+
<p>Avoid opening transactions and doing work outside the database.</p>
|
232
|
+
<p><%= link_to "View queries", live_queries_path(state: "idle in transaction") %></p>
|
233
|
+
</div>
|
234
|
+
<% end %>
|
235
|
+
|
236
|
+
<% if @transaction_id_danger.any? %>
|
237
|
+
<div class="content">
|
238
|
+
<h2>Vacuuming Needed</h2>
|
239
|
+
<p>The database <strong>will shutdown</strong> 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" %>.</p>
|
240
|
+
|
241
|
+
<p>Try <%= link_to "tuning autovacuum", "https://blog.2ndquadrant.com/autovacuum-tuning-basics/", target: "_blank" %> - specifically autovacuum_vacuum_cost_limit.</p>
|
242
|
+
|
243
|
+
<p>If that doesn’t work, for each table, run:</p>
|
244
|
+
<pre><code>VACUUM FREEZE VERBOSE table;</code></pre>
|
245
|
+
<table class="table">
|
246
|
+
<thead>
|
247
|
+
<tr>
|
248
|
+
<th>Table</th>
|
249
|
+
<th style="width: 20%;">Transactions Left</th>
|
250
|
+
</tr>
|
251
|
+
</thead>
|
252
|
+
<tbody>
|
253
|
+
<% @transaction_id_danger.each do |query| %>
|
254
|
+
<tr>
|
255
|
+
<td>
|
256
|
+
<%= query[:table] %>
|
257
|
+
<% if query[:schema] != "public" %>
|
258
|
+
<span class="text-muted"><%= query[:schema] %></span>
|
259
|
+
<% end %>
|
260
|
+
</td>
|
261
|
+
<td><%= number_with_delimiter(query[:transactions_left]) %></td>
|
262
|
+
</tr>
|
263
|
+
<% end %>
|
264
|
+
</tbody>
|
265
|
+
</table>
|
266
|
+
</div>
|
267
|
+
<% end %>
|
268
|
+
|
269
|
+
<% if @sequence_danger && @sequence_danger.any? %>
|
270
|
+
<div class="content">
|
271
|
+
<h2>Columns Near Overflow</h2>
|
272
|
+
<p>Consider changing columns to bigint to support a larger range of values.</p>
|
273
|
+
<table class="table">
|
274
|
+
<thead>
|
275
|
+
<tr>
|
276
|
+
<th>Column</th>
|
277
|
+
<th style="width: 20%;">Type</th>
|
278
|
+
<th style="width: 20%;">Values Left</th>
|
279
|
+
<th style="width: 10%;">% Left</th>
|
280
|
+
</tr>
|
281
|
+
</thead>
|
282
|
+
<tbody>
|
283
|
+
<% @sequence_danger.sort_by { |s| [s[:table], s[:column]] }.each do |query| %>
|
284
|
+
<tr>
|
285
|
+
<td>
|
286
|
+
<%= query[:table] %>.<%= query[:column] %>
|
287
|
+
<% if query[:table_schema] != "public" %>
|
288
|
+
<span class="text-muted"><%= query[:table_schema] %></span>
|
289
|
+
<% end %>
|
290
|
+
</td>
|
291
|
+
<td>
|
292
|
+
<%= query[:column_type] %>
|
293
|
+
</td>
|
294
|
+
<td>
|
295
|
+
<%= number_with_delimiter(query[:max_value] - query[:last_value]) %>
|
296
|
+
</td>
|
297
|
+
<td>
|
298
|
+
<%= number_to_percentage((query[:max_value] - query[:last_value]) * 100.0 / query[:max_value], precision: 2, significant: true) %>
|
299
|
+
</td>
|
300
|
+
</tr>
|
301
|
+
<% end %>
|
302
|
+
</tbody>
|
303
|
+
</table>
|
304
|
+
</div>
|
305
|
+
<% end %>
|
306
|
+
|
307
|
+
<% if @invalid_indexes.any? %>
|
308
|
+
<div class="content">
|
309
|
+
<h1>Invalid Indexes</h1>
|
310
|
+
|
311
|
+
<p>These indexes exist, but can’t be used. You should recreate them.</p>
|
312
|
+
|
313
|
+
<table class="table">
|
314
|
+
<thead>
|
315
|
+
<tr>
|
316
|
+
<th>Name</th>
|
317
|
+
</tr>
|
318
|
+
</thead>
|
319
|
+
<tbody>
|
320
|
+
<% @invalid_indexes.each do |index| %>
|
321
|
+
<tr>
|
322
|
+
<td>
|
323
|
+
<%= index[:name] %>
|
324
|
+
<% if index[:schema] != "public" %>
|
325
|
+
<span class="text-muted"><%= index[:schema] %></span>
|
326
|
+
<% end %>
|
327
|
+
</td>
|
328
|
+
</tr>
|
329
|
+
<tr>
|
330
|
+
<td style="border-top: none; padding: 0;">
|
331
|
+
<pre><code>DROP INDEX CONCURRENTLY <%= pghero_pretty_ident(index[:name], schema: index[:schema]) %>;
|
332
|
+
<%= index[:definition].sub("CREATE INDEX ", "CREATE INDEX CONCURRENTLY ") %>;</code></pre>
|
333
|
+
</td>
|
334
|
+
</tr>
|
335
|
+
<% end %>
|
336
|
+
</tbody>
|
337
|
+
</table>
|
338
|
+
</div>
|
339
|
+
<% end %>
|
340
|
+
|
341
|
+
<% if @invalid_constraints.any? %>
|
342
|
+
<div class="content">
|
343
|
+
<h1>Invalid Constraints</h1>
|
344
|
+
|
345
|
+
<p>These constraints are marked as <code>NOT VALID</code>. You should validate them.</p>
|
346
|
+
|
347
|
+
<table class="table">
|
348
|
+
<thead>
|
349
|
+
<tr>
|
350
|
+
<th>Name</th>
|
351
|
+
</tr>
|
352
|
+
</thead>
|
353
|
+
<tbody>
|
354
|
+
<% @invalid_constraints.each do |constraint| %>
|
355
|
+
<tr>
|
356
|
+
<td>
|
357
|
+
<%= constraint[:name] %>
|
358
|
+
<% if constraint[:schema] != "public" %>
|
359
|
+
<span class="text-muted"><%= constraint[:schema] %></span>
|
360
|
+
<% end %>
|
361
|
+
</td>
|
362
|
+
</tr>
|
363
|
+
<tr>
|
364
|
+
<td style="border-top: none; padding: 0;">
|
365
|
+
<pre><code>ALTER TABLE <%= pghero_pretty_ident(constraint[:table], schema: constraint[:schema]) %> VALIDATE CONSTRAINT <%= pghero_pretty_ident(constraint[:name]) %>;</code></pre>
|
366
|
+
</td>
|
367
|
+
</tr>
|
368
|
+
<% end %>
|
369
|
+
</tbody>
|
370
|
+
</table>
|
371
|
+
</div>
|
372
|
+
<% end %>
|
373
|
+
|
374
|
+
<% if @duplicate_indexes && @duplicate_indexes.any? %>
|
375
|
+
<div class="content">
|
376
|
+
<h1>Duplicate Indexes</h1>
|
377
|
+
|
378
|
+
<p>
|
379
|
+
These indexes exist, but aren’t needed. Remove them
|
380
|
+
<% if @show_migrations %>
|
381
|
+
<a href="javascript: void(0);" onclick="document.getElementById('migration2').style.display = 'block';">with a migration</a>
|
382
|
+
<% end %>
|
383
|
+
for faster writes.
|
384
|
+
</p>
|
385
|
+
|
386
|
+
<div id="migration2" style="display: none;">
|
387
|
+
<pre>rails generate migration remove_unneeded_indexes</pre>
|
388
|
+
<p>And paste</p>
|
389
|
+
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @duplicate_indexes.each do |query| %>
|
390
|
+
<%= pghero_remove_index(query[:unneeded_index]) %><% end %></pre>
|
391
|
+
</div>
|
392
|
+
|
393
|
+
<table class="table duplicate-indexes">
|
394
|
+
<thead>
|
395
|
+
<tr>
|
396
|
+
<th>Details</th>
|
397
|
+
</tr>
|
398
|
+
</thead>
|
399
|
+
<tbody>
|
400
|
+
<% @duplicate_indexes.each do |index| %>
|
401
|
+
<% unneeded_index = index[:unneeded_index] %>
|
402
|
+
<% covering_index = index[:covering_index] %>
|
403
|
+
<tr>
|
404
|
+
<td>
|
405
|
+
On <%= unneeded_index[:table] %>
|
406
|
+
<pre><%= unneeded_index[:name] %> (<%= unneeded_index[:columns].join(", ") %>)</pre>
|
407
|
+
is covered by
|
408
|
+
<pre><%= covering_index[:name] %> (<%= covering_index[:columns].join(", ") %>)</pre>
|
409
|
+
</td>
|
410
|
+
</tr>
|
411
|
+
<% end %>
|
412
|
+
</tbody>
|
413
|
+
</table>
|
414
|
+
</div>
|
415
|
+
<% end %>
|
416
|
+
|
417
|
+
<% if @suggested_indexes.any? %>
|
418
|
+
<div class="content">
|
419
|
+
<h1>Suggested Indexes</h1>
|
420
|
+
<p>
|
421
|
+
Add indexes to speed up queries.
|
422
|
+
<% if @show_migrations %>
|
423
|
+
Here’s a
|
424
|
+
<a href="javascript: void(0);" onclick="document.getElementById('migration3').style.display = 'block';">migration</a> to help.
|
425
|
+
<% end %>
|
426
|
+
</p>
|
427
|
+
|
428
|
+
<div id="migration3" style="display: none;">
|
429
|
+
<pre>rails generate migration add_suggested_indexes</pre>
|
430
|
+
<p>And paste</p>
|
431
|
+
<pre style="overflow: scroll; white-space: pre; word-break: normal;">commit_db_transaction
|
432
|
+
<% @suggested_indexes.each do |index| %>
|
433
|
+
<% if index[:using] && index[:using] != "btree" %>
|
434
|
+
add_index <%= index[:table].to_sym.inspect %>, <%= index[:columns].first.inspect %>, using: <%= index[:using].inspect %>, algorithm: :concurrently
|
435
|
+
<% else %>
|
436
|
+
add_index <%= index[:table].to_sym.inspect %>, [<%= index[:columns].map(&:to_sym).map(&:inspect).join(", ") %>], algorithm: :concurrently<% end %>
|
437
|
+
<% end %></pre>
|
438
|
+
</div>
|
439
|
+
|
440
|
+
<% @suggested_indexes.each_with_index do |index, i| %>
|
441
|
+
<hr />
|
442
|
+
<%= render partial: "suggested_index", locals: {index: index, details: index[:details]} %>
|
443
|
+
<p>to speed up</p>
|
444
|
+
<%= render partial: "queries_table", locals: {queries: index[:queries].map { |q| @query_stats_by_query[q] }, suggested_indexes: false} %>
|
445
|
+
<% end %>
|
446
|
+
</div>
|
447
|
+
<% end %>
|
448
|
+
|
449
|
+
<% if !@query_stats_enabled %>
|
450
|
+
<div class="content">
|
451
|
+
<h1>Query Stats</h1>
|
452
|
+
|
453
|
+
<% if @query_stats_available && !@query_stats_extension_enabled %>
|
454
|
+
<p>
|
455
|
+
Query stats are available but not enabled.
|
456
|
+
<%= button_to "Enable", enable_query_stats_path, class: "btn btn-info" %>
|
457
|
+
</p>
|
458
|
+
<% else %>
|
459
|
+
<p>Make them available by adding the following lines to <code>postgresql.conf</code>:</p>
|
460
|
+
<pre>shared_preload_libraries = 'pg_stat_statements'
|
461
|
+
pg_stat_statements.track = all</pre>
|
462
|
+
<p>Restart the server for the changes to take effect.</p>
|
463
|
+
<% end %>
|
464
|
+
</div>
|
465
|
+
<% end %>
|
466
|
+
|
467
|
+
<% if @query_stats_enabled && @slow_queries.any? %>
|
468
|
+
<div class="content">
|
469
|
+
<h1>Slow Queries</h1>
|
470
|
+
|
471
|
+
<p>Slow queries take <%= @database.slow_query_ms %> ms or more on average and have been called at least <%= @database.slow_query_calls %> times.</p>
|
472
|
+
<p><%= link_to "Explain queries", explain_path %> to see where to add indexes.</p>
|
473
|
+
|
474
|
+
<%= render partial: "queries_table", locals: {queries: @slow_queries} %>
|
475
|
+
</div>
|
476
|
+
<% end %>
|
477
|
+
|
478
|
+
<% if @extended && @unused_indexes.any? %>
|
479
|
+
<div class="content">
|
480
|
+
<h1>Unused Indexes</h1>
|
481
|
+
|
482
|
+
<p>
|
483
|
+
Unused indexes cause unnecessary overhead. Remove them
|
484
|
+
<% if @show_migrations %>
|
485
|
+
<a href="javascript: void(0);" onclick="document.getElementById('migration').style.display = 'block';">with a migration</a>
|
486
|
+
<% end %>
|
487
|
+
for faster writes.
|
488
|
+
</p>
|
489
|
+
|
490
|
+
<div id="migration" style="display: none;">
|
491
|
+
<pre>rails generate migration remove_unused_indexes</pre>
|
492
|
+
<p>And paste</p>
|
493
|
+
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.each do |query| %>
|
494
|
+
<%= pghero_remove_index(query)%><% end %></pre>
|
495
|
+
</div>
|
496
|
+
|
497
|
+
<table class="table">
|
498
|
+
<thead>
|
499
|
+
<tr>
|
500
|
+
<th>Name</th>
|
501
|
+
<th style="width: 20%;">Index Size</th>
|
502
|
+
</tr>
|
503
|
+
</thead>
|
504
|
+
<tbody>
|
505
|
+
<% @unused_indexes.each do |query| %>
|
506
|
+
<tr>
|
507
|
+
<td><%= query[:index] %><div class="text-muted">on <%= query[:table] %></div></td>
|
508
|
+
<td><%= query[:size] %></td>
|
509
|
+
</tr>
|
510
|
+
<% end %>
|
511
|
+
</tbody>
|
512
|
+
</table>
|
513
|
+
</div>
|
514
|
+
<% end %>
|
515
|
+
|
516
|
+
<script>
|
517
|
+
highlightQueries();
|
518
|
+
</script>
|