pghero 1.5.3 → 1.6.0
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.
Potentially problematic release.
This version of pghero might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +2 -2
- data/app/controllers/pg_hero/home_controller.rb +8 -10
- data/app/views/layouts/pg_hero/application.html.erb +0 -1
- data/app/views/pg_hero/home/index.html.erb +17 -48
- data/app/views/pg_hero/home/maintenance.html.erb +4 -0
- data/guides/Linux.md +20 -0
- data/lib/pghero/methods/indexes.rb +1 -1
- data/lib/pghero/methods/queries.rb +26 -17
- data/lib/pghero/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b86a04757fce4dfc91177eb7794c20ae289ec2d5
|
4
|
+
data.tar.gz: ba8f9f333064208ea84fd6a4c222cd531f18637b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d97627eb0174decf527ea8ca4554fcb8d45bc95c28eeb4fc250b7921c544f685af741ec85c5cfd1815a7749af36b4f75a4e90ef2b261adae873a9718ded34de3
|
7
|
+
data.tar.gz: e8fedfa37a433af1d4c2b647242b06bc6cc8ba1697f45e564123d3d77a8db599e722322a047d7c51b4c528805324df5db1f5f08b7bda5ae62ffda093d7f4a718
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
## 1.6.0
|
2
|
+
|
3
|
+
- Removed mostly inactionable items (cache hit rate and index usage)
|
4
|
+
- Restored duplicate indexes to homepage
|
5
|
+
- Fixed issue with exact duplicate indexes
|
6
|
+
- Way better `blocked_queries` method
|
7
|
+
|
1
8
|
## 1.5.3
|
2
9
|
|
3
10
|
- Fixed Rails 5 error with multiple databases
|
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# PgHero
|
2
2
|
|
3
|
-
A performance dashboard for Postgres
|
3
|
+
A performance dashboard for Postgres - health checks, suggested indexes, and more :tada:
|
4
4
|
|
5
5
|
[See it in action](https://pghero.herokuapp.com/)
|
6
6
|
|
7
|
-
[](https://pghero.herokuapp.com/)
|
8
8
|
|
9
9
|
:speech_balloon: Get [handcrafted updates](http://chartkick.us7.list-manage.com/subscribe?u=952c861f99eb43084e0a49f98&id=6ea6541e8e&group[0][16]=true) for new features
|
10
10
|
|
@@ -16,21 +16,19 @@ module PgHero
|
|
16
16
|
|
17
17
|
def index
|
18
18
|
@title = "Overview"
|
19
|
+
@extended = params[:extended]
|
19
20
|
@query_stats = @database.query_stats(historical: true, start_at: 3.hours.ago)
|
20
21
|
@slow_queries = @database.slow_queries(query_stats: @query_stats)
|
21
22
|
@long_running_queries = @database.long_running_queries
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
@database.missing_indexes
|
29
|
-
end
|
23
|
+
if @extended
|
24
|
+
@index_hit_rate = @database.index_hit_rate
|
25
|
+
@table_hit_rate = @database.table_hit_rate
|
26
|
+
@good_cache_rate = @table_hit_rate >= @database.cache_hit_rate_threshold.to_f / 100 && @index_hit_rate >= @database.cache_hit_rate_threshold.to_f / 100
|
27
|
+
end
|
28
|
+
|
30
29
|
@unused_indexes = @database.unused_indexes.select { |q| q["index_scans"].to_i == 0 }
|
31
30
|
@invalid_indexes = @database.invalid_indexes
|
32
|
-
@duplicate_indexes = @database.duplicate_indexes
|
33
|
-
@good_cache_rate = @table_hit_rate >= @database.cache_hit_rate_threshold.to_f / 100 && @index_hit_rate >= @database.cache_hit_rate_threshold.to_f / 100
|
31
|
+
@duplicate_indexes = @database.duplicate_indexes
|
34
32
|
unless @query_stats_enabled
|
35
33
|
@query_stats_available = @database.query_stats_available?
|
36
34
|
@query_stats_extension_enabled = @database.query_stats_extension_enabled? if @query_stats_available
|
@@ -36,7 +36,6 @@
|
|
36
36
|
<% if @query_stats_enabled %>
|
37
37
|
<li class="<%= controller.action_name == "queries" ? "active" : "" %>"><%= link_to "Queries", queries_path %></li>
|
38
38
|
<% end %>
|
39
|
-
<li class="<%= controller.action_name == "index_usage" ? "active" : "" %>"><%= link_to "Index Usage", index_usage_path %></li>
|
40
39
|
<li class="<%= controller.action_name == "space" ? "active" : "" %>"><%= link_to "Space", space_path %></li>
|
41
40
|
<li class="<%= controller.action_name == "connections" ? "active" : "" %>"><%= link_to "Connections", connections_path %></li>
|
42
41
|
<li class="<%= controller.action_name == "live_queries" ? "active" : "" %>"><%= link_to "Live Queries", live_queries_path %></li>
|
@@ -16,16 +16,18 @@
|
|
16
16
|
No long running queries
|
17
17
|
<% end %>
|
18
18
|
</div>
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
<% if @extended %>
|
20
|
+
<div class="alert alert-<%= @good_cache_rate ? "success" : "warning" %>">
|
21
|
+
<% if @good_cache_rate %>
|
22
|
+
Cache hit rate above <%= @database.cache_hit_rate_threshold %>%
|
23
|
+
<% else %>
|
24
|
+
Low cache hit rate
|
25
|
+
<% end %>
|
26
|
+
</div>
|
27
|
+
<% end %>
|
26
28
|
<div class="alert alert-<%= @good_total_connections ? "success" : "warning" %>">
|
27
29
|
<% if @good_total_connections %>
|
28
|
-
|
30
|
+
Number of connections healthy
|
29
31
|
<% else %>
|
30
32
|
High number of connections
|
31
33
|
<% end %>
|
@@ -42,7 +44,7 @@
|
|
42
44
|
<% if @sequence_danger.any? %>
|
43
45
|
<%= pluralize(@sequence_danger.size, "columns") %> approaching overflow
|
44
46
|
<% else %>
|
45
|
-
No columns near overflow
|
47
|
+
No columns near integer overflow
|
46
48
|
<% end %>
|
47
49
|
</div>
|
48
50
|
<div class="alert alert-<%= @invalid_indexes.empty? ? "success" : "warning" %>">
|
@@ -69,14 +71,6 @@
|
|
69
71
|
No suggested indexes
|
70
72
|
<% end %>
|
71
73
|
</div>
|
72
|
-
<% else %>
|
73
|
-
<div class="alert alert-<%= @missing_indexes.empty? ? "success" : "warning" %>">
|
74
|
-
<% if @missing_indexes.any? %>
|
75
|
-
<%= pluralize(@missing_indexes.size, "table appears", "tables appear") %> to be missing indexes
|
76
|
-
<% else %>
|
77
|
-
No missing indexes
|
78
|
-
<% end %>
|
79
|
-
</div>
|
80
74
|
<% end %>
|
81
75
|
<div class="alert alert-<%= @query_stats_enabled && @slow_queries.empty? ? "success" : "warning" %>">
|
82
76
|
<% if !@query_stats_enabled %>
|
@@ -106,7 +100,7 @@
|
|
106
100
|
</div>
|
107
101
|
<% end %>
|
108
102
|
|
109
|
-
<% if !@good_cache_rate %>
|
103
|
+
<% if @extended && !@good_cache_rate %>
|
110
104
|
<div class="content">
|
111
105
|
<h1>Low Cache Hit Rate</h1>
|
112
106
|
|
@@ -135,7 +129,7 @@
|
|
135
129
|
<h2>Vacuuming Needed</h2>
|
136
130
|
<p>The database <strong>will shutdown</strong> when there are fewer than 1,000,000 transactions left. <%= link_to "Read more", "http://www.postgresql.org/docs/9.1/static/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND", target: "_blank" %>.</p>
|
137
131
|
<p>For each table, run:</p>
|
138
|
-
<code><pre>VACUUM FREEZE VERBOSE table
|
132
|
+
<code><pre>VACUUM FREEZE VERBOSE table;</pre></code>
|
139
133
|
<table class="table">
|
140
134
|
<thead>
|
141
135
|
<tr>
|
@@ -270,11 +264,13 @@ remove_index <%= query["unneeded_index"]["table"].to_sym.inspect %>, name: <%= q
|
|
270
264
|
<div id="migration3" style="display: none;">
|
271
265
|
<pre>rails g migration add_suggested_indexes</pre>
|
272
266
|
<p>And paste</p>
|
273
|
-
<pre style="overflow: scroll; white-space: pre; word-break: normal;"
|
267
|
+
<pre style="overflow: scroll; white-space: pre; word-break: normal;">commit_db_transaction
|
268
|
+
<% @suggested_indexes.each do |index| %>
|
274
269
|
<% if index[:using] == "gist" %>
|
275
270
|
connection.execute("CREATE INDEX CONCURRENTLY ON <%= index[:table] %><% if index[:using] %> USING <%= index[:using] %><% end %> (<%= index[:columns].join(", ") %>)")
|
276
271
|
<% else %>
|
277
|
-
add_index <%= index[:table].to_sym.inspect %>, [<%= index[:columns].map(&:to_sym).map(&:inspect).join(", ") %>], algorithm: :concurrently<% end
|
272
|
+
add_index <%= index[:table].to_sym.inspect %>, [<%= index[:columns].map(&:to_sym).map(&:inspect).join(", ") %>], algorithm: :concurrently<% end %>
|
273
|
+
<% end %></pre>
|
278
274
|
</div>
|
279
275
|
|
280
276
|
<% @suggested_indexes.each_with_index do |index, i| %>
|
@@ -286,33 +282,6 @@ add_index <%= index[:table].to_sym.inspect %>, [<%= index[:columns].map(&:to_sym
|
|
286
282
|
</div>
|
287
283
|
<% end %>
|
288
284
|
|
289
|
-
<% if @missing_indexes.any? %>
|
290
|
-
<div class="content">
|
291
|
-
<h1>Missing Indexes</h1>
|
292
|
-
|
293
|
-
<p>These tables have a large number of rows but indexes are not used often. Add indexes for faster queries.</p>
|
294
|
-
|
295
|
-
<table class="table">
|
296
|
-
<thead>
|
297
|
-
<tr>
|
298
|
-
<th>Table</th>
|
299
|
-
<th>% of Time Index Used</th>
|
300
|
-
<th>Rows in Table</th>
|
301
|
-
</tr>
|
302
|
-
</thead>
|
303
|
-
<tbody>
|
304
|
-
<% @missing_indexes.each do |query| %>
|
305
|
-
<tr>
|
306
|
-
<td><%= query["table"] %></td>
|
307
|
-
<td style="width: 30%;"><%= query["percent_of_times_index_used"] %></td>
|
308
|
-
<td style="width: 20%;"><%= number_with_delimiter(query["rows_in_table"]) %></td>
|
309
|
-
</tr>
|
310
|
-
<% end %>
|
311
|
-
</tbody>
|
312
|
-
</table>
|
313
|
-
</div>
|
314
|
-
<% end %>
|
315
|
-
|
316
285
|
<% if !@query_stats_enabled %>
|
317
286
|
<div class="content">
|
318
287
|
<h1>Query Stats</h1>
|
@@ -22,12 +22,16 @@
|
|
22
22
|
<% time = [table["last_autovacuum"], table["last_vacuum"]].compact.max %>
|
23
23
|
<% if time %>
|
24
24
|
<%= @time_zone.parse(time).strftime("%m/%-e %l:%M %P") %>
|
25
|
+
<% else %>
|
26
|
+
<span class="text-muted">Unknown</span>
|
25
27
|
<% end %>
|
26
28
|
</td>
|
27
29
|
<td>
|
28
30
|
<% time = [table["last_autoanalyze"], table["last_analyze"]].compact.max %>
|
29
31
|
<% if time %>
|
30
32
|
<%= @time_zone.parse(time).strftime("%m/%-e %l:%M %P") %>
|
33
|
+
<% else %>
|
34
|
+
<span class="text-muted">Unknown</span>
|
31
35
|
<% end %>
|
32
36
|
</td>
|
33
37
|
</tr>
|
data/guides/Linux.md
CHANGED
@@ -186,6 +186,26 @@ sudo pghero config:set PGHERO_SECRET_ACCESS_KEY=secret123
|
|
186
186
|
sudo pghero config:set PGHERO_DB_INSTANCE_IDENTIFIER=epona
|
187
187
|
```
|
188
188
|
|
189
|
+
## Multiple Databases
|
190
|
+
|
191
|
+
Create a `pghero.yml` with:
|
192
|
+
|
193
|
+
```yml
|
194
|
+
production:
|
195
|
+
databases:
|
196
|
+
primary:
|
197
|
+
url: postgres://...
|
198
|
+
replica:
|
199
|
+
url: postgres://...
|
200
|
+
```
|
201
|
+
|
202
|
+
And run:
|
203
|
+
|
204
|
+
```sh
|
205
|
+
cat pghero.yml | sudo pghero run sh -c "cat > config/pghero.yml"
|
206
|
+
sudo service pghero restart
|
207
|
+
```
|
208
|
+
|
189
209
|
## Customize
|
190
210
|
|
191
211
|
Minimum time for long running queries
|
@@ -142,7 +142,7 @@ module PgHero
|
|
142
142
|
indexes_by_table = self.indexes.group_by { |i| i["table"] }
|
143
143
|
indexes_by_table.values.flatten.select { |i| PgHero.falsey?(i["primary"]) && PgHero.falsey?(i["unique"]) && !i["indexprs"] && !i["indpred"] && PgHero.truthy?(i["valid"]) }.each do |index|
|
144
144
|
covering_index = indexes_by_table[index["table"]].find { |i| index_covers?(i["columns"], index["columns"]) && i["using"] == index["using"] && i["name"] != index["name"] && !i["indexprs"] && !i["indpred"] && PgHero.truthy?(i["valid"]) }
|
145
|
-
if covering_index
|
145
|
+
if covering_index && (covering_index["columns"] != index["columns"] || index["name"] > covering_index["name"])
|
146
146
|
indexes << {"unneeded_index" => index, "covering_index" => covering_index}
|
147
147
|
end
|
148
148
|
end
|
@@ -57,28 +57,37 @@ module PgHero
|
|
57
57
|
end
|
58
58
|
|
59
59
|
# from https://wiki.postgresql.org/wiki/Lock_Monitoring
|
60
|
+
# and http://big-elephants.com/2013-09/exploring-query-locks-in-postgres/
|
60
61
|
def blocked_queries
|
61
62
|
select_all <<-SQL
|
62
63
|
SELECT
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
age(now(),
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
64
|
+
COALESCE(blockingl.relation::regclass::text,blockingl.locktype) as locked_item,
|
65
|
+
blockeda.pid AS blocked_pid,
|
66
|
+
blockeda.usename AS blocked_user,
|
67
|
+
blockeda.query as blocked_query,
|
68
|
+
age(now(), blockeda.query_start) AS blocked_duration,
|
69
|
+
blockedl.mode as blocked_mode,
|
70
|
+
blockinga.pid AS blocking_pid,
|
71
|
+
blockinga.usename AS blocking_user,
|
72
|
+
blockinga.state AS state_of_blocking_process,
|
73
|
+
blockinga.query AS current_or_recent_query_in_blocking_process,
|
74
|
+
age(now(), blockinga.query_start) AS blocking_duration,
|
75
|
+
blockingl.mode as blocking_mode
|
72
76
|
FROM
|
73
|
-
pg_catalog.pg_locks
|
74
|
-
JOIN
|
75
|
-
|
76
|
-
JOIN
|
77
|
-
pg_catalog.pg_locks
|
78
|
-
|
79
|
-
|
77
|
+
pg_catalog.pg_locks blockedl
|
78
|
+
LEFT JOIN
|
79
|
+
pg_stat_activity blockeda ON blockedl.pid = blockeda.pid
|
80
|
+
LEFT JOIN
|
81
|
+
pg_catalog.pg_locks blockingl ON blockedl.pid != blockingl.pid AND (
|
82
|
+
blockingl.transactionid = blockedl.transactionid
|
83
|
+
OR (blockingl.relation = blockedl.relation AND blockingl.locktype = blockedl.locktype)
|
84
|
+
)
|
85
|
+
LEFT JOIN
|
86
|
+
pg_stat_activity blockinga ON blockingl.pid = blockinga.pid AND blockinga.datid = blockeda.datid
|
80
87
|
WHERE
|
81
|
-
NOT
|
88
|
+
NOT blockedl.granted
|
89
|
+
AND blockeda.query <> '<insufficient privilege>'
|
90
|
+
AND blockeda.datname = current_database()
|
82
91
|
ORDER BY
|
83
92
|
blocked_duration DESC
|
84
93
|
SQL
|
data/lib/pghero/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pghero
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|