ruby-pg-extras 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +373 -0
- data/Rakefile +5 -0
- data/lib/ruby-pg-extras.rb +94 -0
- data/lib/ruby-pg-extras/queries/all_locks.sql +16 -0
- data/lib/ruby-pg-extras/queries/bloat.sql +63 -0
- data/lib/ruby-pg-extras/queries/blocking.sql +16 -0
- data/lib/ruby-pg-extras/queries/cache_hit.sql +11 -0
- data/lib/ruby-pg-extras/queries/calls.sql +9 -0
- data/lib/ruby-pg-extras/queries/extensions.sql +4 -0
- data/lib/ruby-pg-extras/queries/index_size.sql +11 -0
- data/lib/ruby-pg-extras/queries/index_usage.sql +12 -0
- data/lib/ruby-pg-extras/queries/locks.sql +16 -0
- data/lib/ruby-pg-extras/queries/long_running_queries.sql +14 -0
- data/lib/ruby-pg-extras/queries/mandelbrot.sql +21 -0
- data/lib/ruby-pg-extras/queries/outliers.sql +10 -0
- data/lib/ruby-pg-extras/queries/records_rank.sql +9 -0
- data/lib/ruby-pg-extras/queries/seq_scans.sql +7 -0
- data/lib/ruby-pg-extras/queries/table_indexes_size.sql +10 -0
- data/lib/ruby-pg-extras/queries/table_size.sql +10 -0
- data/lib/ruby-pg-extras/queries/total_index_size.sql +8 -0
- data/lib/ruby-pg-extras/queries/total_table_size.sql +10 -0
- data/lib/ruby-pg-extras/queries/unused_indexes.sql +16 -0
- data/lib/ruby-pg-extras/queries/vacuum_stats.sql +40 -0
- data/lib/ruby-pg-extras/tasks/all.rake +12 -0
- data/lib/ruby-pg-extras/version.rb +3 -0
- data/ruby-pg-extras.gemspec +22 -0
- data/spec/smoke_spec.rb +28 -0
- data/spec/spec_helper.rb +19 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 771a487899452aae45b3491ef7b5c4704ff627abe1768c9342d64a6ae5c2e050
|
4
|
+
data.tar.gz: 98b7ccbc8b3c74e4b623be5cc932d73110676cf8de6610ceeffa0127c25c750b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4b7f48a2907f36db39033aa81801eef6cc397c8e2670024ad2b0f5d06984792bc7cfc664e3aab34b5f6d84268dc6bb312c345a078f7914fa858eb7dfa0aa1fe2
|
7
|
+
data.tar.gz: cf675a7c8364d01468602aee27961f008a33a6168ab11afd406cfaa7eb9b97c1197f6e0e455ba1801d9c4282d95502cb8041644b992e4455431e5eed6c5c4f1a
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright © Paweł Urbanek 2019
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,373 @@
|
|
1
|
+
# Rails PG Extras [![Gem Version](https://badge.fury.io/rb/ruby-pg-extras.svg)](https://badge.fury.io/rb/ruby-pg-extras) [![CircleCI](https://circleci.com/gh/pawurb/ruby-pg-extras.svg?style=svg)](https://circleci.com/gh/pawurb/ruby-pg-extras)
|
2
|
+
|
3
|
+
Rails port of [Heroku PG Extras](https://github.com/heroku/heroku-pg-extras). The goal of this project is to provide a powerful insights into PostgreSQL database for Ruby on Rails apps that are not using the default Heroku PostgreSQL plugin.
|
4
|
+
|
5
|
+
Included rake tasks and Ruby methods can be used to obtain information about a Postgres instance, that may be useful when analyzing performance issues. This includes information about locks, index usage, buffer cache hit ratios and vacuum statistics. Ruby API enables developers to easily integrate the tool into e.g. automatic monitoring tasks.
|
6
|
+
|
7
|
+
### Installation
|
8
|
+
|
9
|
+
In your Gemfile
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'ruby-pg-extras'
|
13
|
+
```
|
14
|
+
|
15
|
+
### Usage
|
16
|
+
|
17
|
+
Each command can be used as a rake task, or a directly from the Ruby code.
|
18
|
+
|
19
|
+
```bash
|
20
|
+
rake pg_extras:cache_hit
|
21
|
+
```
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
RubyPGExtras.cache_hit
|
25
|
+
```
|
26
|
+
```bash
|
27
|
+
+----------------+------------------------+
|
28
|
+
| Index and table hit rate |
|
29
|
+
+----------------+------------------------+
|
30
|
+
| name | ratio |
|
31
|
+
+----------------+------------------------+
|
32
|
+
| index hit rate | 0.97796610169491525424 |
|
33
|
+
| table hit rate | 0.96724294813466787989 |
|
34
|
+
+----------------+------------------------+
|
35
|
+
```
|
36
|
+
|
37
|
+
|
38
|
+
By default the ASCII table is displayed, to change to format you need to specify the `in_format` parameter (`[:display_table, :hash, :array, :raw]` options are available):
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
RubyPGExtras.cache_hit(in_format: :hash) =>
|
42
|
+
|
43
|
+
[{"name"=>"index hit rate", "ratio"=>"0.97796610169491525424"}, {"name"=>"table hit rate", "ratio"=>"0.96724294813466787989"}]
|
44
|
+
|
45
|
+
RubyPGExtras.cache_hit(in_format: :array) =>
|
46
|
+
|
47
|
+
[["index hit rate", "0.97796610169491525424"], ["table hit rate", "0.96724294813466787989"]]
|
48
|
+
|
49
|
+
RubyPGExtras.cache_hit(in_format: :raw) =>
|
50
|
+
|
51
|
+
#<PG::Result:0x00007f75777f7328 status=PGRES_TUPLES_OK ntuples=2 nfields=2 cmd_tuples=2>
|
52
|
+
```
|
53
|
+
|
54
|
+
### Available methods
|
55
|
+
|
56
|
+
#### `cache_hit`
|
57
|
+
|
58
|
+
```bash
|
59
|
+
$ rake pg_extras:cache_hit
|
60
|
+
name | ratio
|
61
|
+
----------------+------------------------
|
62
|
+
index hit rate | 0.99957765013541945832
|
63
|
+
table hit rate | 1.00
|
64
|
+
(2 rows)
|
65
|
+
```
|
66
|
+
|
67
|
+
This command provides information on the efficiency of the buffer cache, for both index reads (`index hit rate`) as well as table reads (`table hit rate`). A low buffer cache hit ratio can be a sign that the Postgres instance is too small for the workload.
|
68
|
+
|
69
|
+
#### `index_usage`
|
70
|
+
|
71
|
+
```
|
72
|
+
$ rake pg_extras:index_usage
|
73
|
+
relname | percent_of_times_index_used | rows_in_table
|
74
|
+
---------------------+-----------------------------+---------------
|
75
|
+
events | 65 | 1217347
|
76
|
+
app_infos | 74 | 314057
|
77
|
+
app_infos_user_info | 0 | 198848
|
78
|
+
user_info | 5 | 94545
|
79
|
+
delayed_jobs | 27 | 0
|
80
|
+
(5 rows)
|
81
|
+
```
|
82
|
+
|
83
|
+
This command provides information on the efficiency of indexes, represented as what percentage of total scans were index scans. A low percentage can indicate under indexing, or wrong data being indexed.
|
84
|
+
|
85
|
+
### `locks`
|
86
|
+
|
87
|
+
```
|
88
|
+
$ rake pg_extras:locks
|
89
|
+
procpid | relname | transactionid | granted | query_snippet | age
|
90
|
+
---------+---------+---------------+---------+-----------------------+-----------------
|
91
|
+
31776 | | | t | <IDLE> in transaction | 00:19:29.837898
|
92
|
+
31776 | | 1294 | t | <IDLE> in transaction | 00:19:29.837898
|
93
|
+
31912 | | | t | select * from hello; | 00:19:17.94259
|
94
|
+
3443 | | | t | +| 00:00:00
|
95
|
+
| | | | select +|
|
96
|
+
| | | | pg_stat_activi |
|
97
|
+
(4 rows)
|
98
|
+
```
|
99
|
+
|
100
|
+
This command displays queries that have taken out an exlusive lock on a relation. Exclusive locks typically prevent other operations on that relation from taking place, and can be a cause of "hung" queries that are waiting for a lock to be granted.
|
101
|
+
|
102
|
+
### `all_locks`
|
103
|
+
|
104
|
+
```
|
105
|
+
$ rake pg_extras:all_locks
|
106
|
+
```
|
107
|
+
|
108
|
+
This command displays all the current locks, regardless of their type.
|
109
|
+
|
110
|
+
### `outliers`
|
111
|
+
|
112
|
+
```
|
113
|
+
$ rake pg_extras:outliers
|
114
|
+
qry | exec_time | prop_exec_time | ncalls | sync_io_time
|
115
|
+
-----------------------------------------+------------------+----------------+-------------+--------------
|
116
|
+
SELECT * FROM archivable_usage_events.. | 154:39:26.431466 | 72.2% | 34,211,877 | 00:00:00
|
117
|
+
COPY public.archivable_usage_events (.. | 50:38:33.198418 | 23.6% | 13 | 13:34:21.00108
|
118
|
+
COPY public.usage_events (id, reporte.. | 02:32:16.335233 | 1.2% | 13 | 00:34:19.784318
|
119
|
+
INSERT INTO usage_events (id, retaine.. | 01:42:59.436532 | 0.8% | 12,328,187 | 00:00:00
|
120
|
+
SELECT * FROM usage_events WHERE (alp.. | 01:18:10.754354 | 0.6% | 102,114,301 | 00:00:00
|
121
|
+
UPDATE usage_events SET reporter_id =.. | 00:52:35.683254 | 0.4% | 23,786,348 | 00:00:00
|
122
|
+
INSERT INTO usage_events (id, retaine.. | 00:49:24.952561 | 0.4% | 21,988,201 | 00:00:00
|
123
|
+
COPY public.app_ownership_events (id,.. | 00:37:14.31082 | 0.3% | 13 | 00:12:32.584754
|
124
|
+
INSERT INTO app_ownership_events (id,.. | 00:26:59.808212 | 0.2% | 383,109 | 00:00:00
|
125
|
+
SELECT * FROM app_ownership_events .. | 00:19:06.021846 | 0.1% | 744,879 | 00:00:00
|
126
|
+
(10 rows)
|
127
|
+
```
|
128
|
+
|
129
|
+
This command displays statements, obtained from `pg_stat_statements`, ordered by the amount of time to execute in aggregate. This includes the statement itself, the total execution time for that statement, the proportion of total execution time for all statements that statement has taken up, the number of times that statement has been called, and the amount of time that statement spent on synchronous I/O (reading/writing from the filesystem).
|
130
|
+
|
131
|
+
Typically, an efficient query will have an appropriate ratio of calls to total execution time, with as little time spent on I/O as possible. Queries that have a high total execution time but low call count should be investigated to improve their performance. Queries that have a high proportion of execution time being spent on synchronous I/O should also be investigated.
|
132
|
+
|
133
|
+
### `calls`
|
134
|
+
|
135
|
+
```
|
136
|
+
$ rake pg_extras:calls
|
137
|
+
qry | exec_time | prop_exec_time | ncalls | sync_io_time
|
138
|
+
-----------------------------------------+------------------+----------------+-------------+--------------
|
139
|
+
SELECT * FROM usage_events WHERE (alp.. | 01:18:11.073333 | 0.6% | 102,120,780 | 00:00:00
|
140
|
+
BEGIN | 00:00:51.285988 | 0.0% | 47,288,662 | 00:00:00
|
141
|
+
COMMIT | 00:00:52.31724 | 0.0% | 47,288,615 | 00:00:00
|
142
|
+
SELECT * FROM archivable_usage_event.. | 154:39:26.431466 | 72.2% | 34,211,877 | 00:00:00
|
143
|
+
UPDATE usage_events SET reporter_id =.. | 00:52:35.986167 | 0.4% | 23,788,388 | 00:00:00
|
144
|
+
INSERT INTO usage_events (id, retaine.. | 00:49:25.260245 | 0.4% | 21,990,326 | 00:00:00
|
145
|
+
INSERT INTO usage_events (id, retaine.. | 01:42:59.436532 | 0.8% | 12,328,187 | 00:00:00
|
146
|
+
SELECT * FROM app_ownership_events .. | 00:19:06.289521 | 0.1% | 744,976 | 00:00:00
|
147
|
+
INSERT INTO app_ownership_events(id, .. | 00:26:59.885631 | 0.2% | 383,153 | 00:00:00
|
148
|
+
UPDATE app_ownership_events SET app_i.. | 00:01:22.282337 | 0.0% | 359,741 | 00:00:00
|
149
|
+
(10 rows)
|
150
|
+
```
|
151
|
+
|
152
|
+
This command is much like `pg:outliers`, but ordered by the number of times a statement has been called.
|
153
|
+
|
154
|
+
### `blocking`
|
155
|
+
|
156
|
+
```
|
157
|
+
$ rake pg_extras:blocking
|
158
|
+
blocked_pid | blocking_statement | blocking_duration | blocking_pid | blocked_statement | blocked_duration
|
159
|
+
-------------+--------------------------+-------------------+--------------+------------------------------------------------------------------------------------+------------------
|
160
|
+
461 | select count(*) from app | 00:00:03.838314 | 15682 | UPDATE "app" SET "updated_at" = '2013-03-04 15:07:04.746688' WHERE "id" = 12823149 | 00:00:03.821826
|
161
|
+
(1 row)
|
162
|
+
```
|
163
|
+
|
164
|
+
This command displays statements that are currently holding locks that other statements are waiting to be released. This can be used in conjunction with `pg:locks` to determine which statements need to be terminated in order to resolve lock contention.
|
165
|
+
|
166
|
+
#### `total_index_size`
|
167
|
+
|
168
|
+
```
|
169
|
+
$ rake pg_extras:total_index_size
|
170
|
+
size
|
171
|
+
-------
|
172
|
+
28194 MB
|
173
|
+
(1 row)
|
174
|
+
```
|
175
|
+
|
176
|
+
This command displays the total size of all indexes on the database, in MB. It is calculated by taking the number of pages (reported in `relpages`) and multiplying it by the page size (8192 bytes).
|
177
|
+
|
178
|
+
### `index_size`
|
179
|
+
|
180
|
+
```
|
181
|
+
$ rake pg_extras:index_size
|
182
|
+
name | size
|
183
|
+
---------------------------------------------------------------+---------
|
184
|
+
idx_activity_attemptable_and_type_lesson_enrollment | 5196 MB
|
185
|
+
index_enrollment_attemptables_by_attempt_and_last_in_group | 4045 MB
|
186
|
+
index_attempts_on_student_id | 2611 MB
|
187
|
+
enrollment_activity_attemptables_pkey | 2513 MB
|
188
|
+
index_attempts_on_student_id_final_attemptable_type | 2466 MB
|
189
|
+
attempts_pkey | 2466 MB
|
190
|
+
index_attempts_on_response_id | 2404 MB
|
191
|
+
index_attempts_on_enrollment_id | 1957 MB
|
192
|
+
index_enrollment_attemptables_by_enrollment_activity_id | 1789 MB
|
193
|
+
enrollment_activities_pkey | 458 MB
|
194
|
+
index_enrollment_activities_by_lesson_enrollment_and_activity | 402 MB
|
195
|
+
index_placement_attempts_on_response_id | 109 MB
|
196
|
+
index_placement_attempts_on_placement_test_id | 108 MB
|
197
|
+
index_placement_attempts_on_grade_level_id | 97 MB
|
198
|
+
index_lesson_enrollments_on_lesson_id | 93 MB
|
199
|
+
(truncated results for brevity)
|
200
|
+
```
|
201
|
+
|
202
|
+
This command displays the size of each each index in the database, in MB. It is calculated by taking the number of pages (reported in `relpages`) and multiplying it by the page size (8192 bytes).
|
203
|
+
|
204
|
+
### `table_size`
|
205
|
+
|
206
|
+
```
|
207
|
+
$ rake pg_extras:table_size
|
208
|
+
name | size
|
209
|
+
---------------------------------------------------------------+---------
|
210
|
+
learning_coaches | 196 MB
|
211
|
+
states | 145 MB
|
212
|
+
grade_levels | 111 MB
|
213
|
+
charities_customers | 73 MB
|
214
|
+
charities | 66 MB
|
215
|
+
(truncated results for brevity)
|
216
|
+
```
|
217
|
+
|
218
|
+
This command displays the size of each table in the database, in MB. It is calculated by using the system administration function `pg_table_size()`, which includes the size of the main data fork, free space map, visibility map and TOAST data.
|
219
|
+
|
220
|
+
### `table_indexes_size`
|
221
|
+
|
222
|
+
```
|
223
|
+
$ rake pg_extras:table-indexes-size
|
224
|
+
table | indexes_size
|
225
|
+
---------------------------------------------------------------+--------------
|
226
|
+
learning_coaches | 153 MB
|
227
|
+
states | 125 MB
|
228
|
+
charities_customers | 93 MB
|
229
|
+
charities | 16 MB
|
230
|
+
grade_levels | 11 MB
|
231
|
+
(truncated results for brevity)
|
232
|
+
```
|
233
|
+
|
234
|
+
This command displays the total size of indexes for each table, in MB. It is calcualtes by using the system administration function `pg_indexes_size()`.
|
235
|
+
|
236
|
+
### `total_table_size`
|
237
|
+
|
238
|
+
```
|
239
|
+
$ rake pg_extras:total_table_size
|
240
|
+
name | size
|
241
|
+
---------------------------------------------------------------+---------
|
242
|
+
learning_coaches | 349 MB
|
243
|
+
states | 270 MB
|
244
|
+
charities_customers | 166 MB
|
245
|
+
grade_levels | 122 MB
|
246
|
+
charities | 82 MB
|
247
|
+
(truncated results for brevity)
|
248
|
+
```
|
249
|
+
|
250
|
+
This command displays the total size of each table in the database, in MB. It is calculated by using the system administration function `pg_total_relation_size()`, which includes table size, total index size and TOAST data.
|
251
|
+
|
252
|
+
### `unused_indexes`
|
253
|
+
|
254
|
+
```
|
255
|
+
$ rake pg_extras:unused_indexes
|
256
|
+
table | index | index_size | index_scans
|
257
|
+
---------------------+--------------------------------------------+------------+-------------
|
258
|
+
public.grade_levels | index_placement_attempts_on_grade_level_id | 97 MB | 0
|
259
|
+
public.observations | observations_attrs_grade_resources | 33 MB | 0
|
260
|
+
public.messages | user_resource_id_idx | 12 MB | 0
|
261
|
+
(3 rows)
|
262
|
+
```
|
263
|
+
|
264
|
+
This command displays indexes that have < 50 scans recorded against them, and are greater than 5 pages in size, ordered by size relative to the number of index scans. This command is generally useful for eliminating indexes that are unused, which can impact write performance, as well as read performance should they occupy space in memory.
|
265
|
+
|
266
|
+
### `seq_scans`
|
267
|
+
|
268
|
+
```
|
269
|
+
$ rake pg_extras:seq_scans
|
270
|
+
|
271
|
+
name | count
|
272
|
+
-----------------------------------+----------
|
273
|
+
learning_coaches | 44820063
|
274
|
+
states | 36794975
|
275
|
+
grade_levels | 13972293
|
276
|
+
charities_customers | 8615277
|
277
|
+
charities | 4316276
|
278
|
+
messages | 3922247
|
279
|
+
contests_customers | 2915972
|
280
|
+
classroom_goals | 2142014
|
281
|
+
contests | 1370267
|
282
|
+
goals | 1112659
|
283
|
+
districts | 158995
|
284
|
+
rollup_reports | 115942
|
285
|
+
customers | 93847
|
286
|
+
schools | 92984
|
287
|
+
classrooms | 92982
|
288
|
+
customer_settings | 91226
|
289
|
+
(truncated results for brevity)
|
290
|
+
```
|
291
|
+
|
292
|
+
This command displays the number of sequential scans recorded against all tables, descending by count of sequential scans. Tables that have very high numbers of sequential scans may be underindexed, and it may be worth investigating queries that read from these tables.
|
293
|
+
|
294
|
+
### long_running_queries
|
295
|
+
|
296
|
+
```
|
297
|
+
$ rake pg_extras:long_running_queries
|
298
|
+
|
299
|
+
pid | duration | query
|
300
|
+
-------+-----------------+---------------------------------------------------------------------------------------
|
301
|
+
19578 | 02:29:11.200129 | EXPLAIN SELECT "students".* FROM "students" WHERE "students"."id" = 1450645 LIMIT 1
|
302
|
+
19465 | 02:26:05.542653 | EXPLAIN SELECT "students".* FROM "students" WHERE "students"."id" = 1889881 LIMIT 1
|
303
|
+
19632 | 02:24:46.962818 | EXPLAIN SELECT "students".* FROM "students" WHERE "students"."id" = 1581884 LIMIT 1
|
304
|
+
(truncated results for brevity)
|
305
|
+
```
|
306
|
+
|
307
|
+
This command displays currently running queries, that have been running for longer than 5 minutes, descending by duration. Very long running queries can be a source of multiple issues, such as preventing DDL statements completing or vacuum being unable to update `relfrozenxid`.
|
308
|
+
|
309
|
+
### records_rank
|
310
|
+
|
311
|
+
```
|
312
|
+
$ rake pg_extras:records_rank
|
313
|
+
name | estimated_count
|
314
|
+
-----------------------------------+-----------------
|
315
|
+
tastypie_apiaccess | 568891
|
316
|
+
notifications_event | 381227
|
317
|
+
core_todo | 178614
|
318
|
+
core_comment | 123969
|
319
|
+
notifications_notification | 102101
|
320
|
+
django_session | 68078
|
321
|
+
(truncated results for brevity)
|
322
|
+
```
|
323
|
+
|
324
|
+
This command displays an estimated count of rows per table, descending by estimated count. The estimated count is derived from `n_live_tup`, which is updated by vacuum operations. Due to the way `n_live_tup` is populated, sparse vs. dense pages can result in estimations that are significantly out from the real count of rows.
|
325
|
+
|
326
|
+
### bloat
|
327
|
+
|
328
|
+
```
|
329
|
+
$ rake pg_extras:bloat
|
330
|
+
|
331
|
+
type | schemaname | object_name | bloat | waste
|
332
|
+
-------+------------+-------------------------------+-------+----------
|
333
|
+
table | public | bloated_table | 1.1 | 98 MB
|
334
|
+
table | public | other_bloated_table | 1.1 | 58 MB
|
335
|
+
index | public | bloated_table::bloated_index | 3.7 | 34 MB
|
336
|
+
table | public | clean_table | 0.2 | 3808 kB
|
337
|
+
table | public | other_clean_table | 0.3 | 1576 kB
|
338
|
+
```
|
339
|
+
|
340
|
+
This command displays an estimation of table "bloat" – space allocated to a relation that is full of dead tuples, that has yet to be reclaimed. Tables that have a high bloat ratio, typically 10 or greater, should be investigated to see if vacuuming is aggressive enough, and can be a sign of high table churn.
|
341
|
+
|
342
|
+
### vacuum_stats
|
343
|
+
|
344
|
+
```
|
345
|
+
$ rake pg_extras:vacuum_stats
|
346
|
+
schema | table | last_vacuum | last_autovacuum | rowcount | dead_rowcount | autovacuum_threshold | expect_autovacuum
|
347
|
+
--------+-----------------------+-------------+------------------+----------------+----------------+----------------------+-------------------
|
348
|
+
public | log_table | | 2013-04-26 17:37 | 18,030 | 0 | 3,656 |
|
349
|
+
public | data_table | | 2013-04-26 13:09 | 79 | 28 | 66 |
|
350
|
+
public | other_table | | 2013-04-26 11:41 | 41 | 47 | 58 |
|
351
|
+
public | queue_table | | 2013-04-26 17:39 | 12 | 8,228 | 52 | yes
|
352
|
+
public | picnic_table | | | 13 | 0 | 53 |
|
353
|
+
```
|
354
|
+
|
355
|
+
This command displays statistics related to vacuum operations for each table, including an estiamtion of dead rows, last autovacuum and the current autovacuum threshold. This command can be useful when determining if current vacuum thresholds require adjustments, and to determine when the table was last vacuumed.
|
356
|
+
|
357
|
+
### mandelbrot
|
358
|
+
|
359
|
+
```
|
360
|
+
$ rake pg_extras:mandelbrot
|
361
|
+
```
|
362
|
+
|
363
|
+
This command outputs the Mandelbrot set, calculated through SQL.
|
364
|
+
|
365
|
+
## FAQ
|
366
|
+
|
367
|
+
* Does is not violate the Heroku PG Extras license?
|
368
|
+
|
369
|
+
The original plugin is MIT based so it means that copying and redistribution in any format is permitted.
|
370
|
+
|
371
|
+
## Disclaimer
|
372
|
+
|
373
|
+
This tool is in beta state.
|
data/Rakefile
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'terminal-table'
|
4
|
+
|
5
|
+
module RubyPGExtras
|
6
|
+
QUERIES = %i(
|
7
|
+
bloat blocking cache_hit
|
8
|
+
calls extensions
|
9
|
+
index_size index_usage locks all_locks
|
10
|
+
long_running_queries mandelbrot outliers
|
11
|
+
records_rank seq_scans table_indexes_size
|
12
|
+
table_size total_index_size total_table_size
|
13
|
+
unused_indexes vacuum_stats
|
14
|
+
)
|
15
|
+
|
16
|
+
QUERIES.each do |query_name|
|
17
|
+
define_singleton_method query_name do |options = { in_format: :display_table }|
|
18
|
+
run_query(
|
19
|
+
query_name: query_name,
|
20
|
+
in_format: options.fetch(:in_format)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.run_query(query_name:, in_format:)
|
26
|
+
result = connection.execute(
|
27
|
+
sql_for(query_name: query_name)
|
28
|
+
)
|
29
|
+
|
30
|
+
display_result(
|
31
|
+
result,
|
32
|
+
title: description_for(query_name: query_name),
|
33
|
+
in_format: in_format
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.display_result(result, title:, in_format:)
|
38
|
+
case in_format
|
39
|
+
when :array
|
40
|
+
result.values
|
41
|
+
when :hash
|
42
|
+
result.to_a
|
43
|
+
when :raw
|
44
|
+
result
|
45
|
+
when :display_table
|
46
|
+
headings = if result.count > 0
|
47
|
+
result[0].keys
|
48
|
+
else
|
49
|
+
["No results"]
|
50
|
+
end
|
51
|
+
|
52
|
+
puts Terminal::Table.new(
|
53
|
+
title: title,
|
54
|
+
headings: headings,
|
55
|
+
rows: result.values
|
56
|
+
)
|
57
|
+
else
|
58
|
+
raise "Invalid in_format option"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.description_for(query_name:)
|
63
|
+
first_line = File.open(
|
64
|
+
sql_path_for(query_name: query_name)
|
65
|
+
) { |f| f.readline }
|
66
|
+
|
67
|
+
first_line[/\/\*(.*?)\*\//m, 1].strip
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.sql_for(query_name:)
|
71
|
+
File.read(
|
72
|
+
sql_path_for(query_name: query_name)
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.sql_path_for(query_name:)
|
77
|
+
File.join(File.dirname(__FILE__), "/ruby-pg-extras/queries/#{query_name}.sql")
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.connection
|
81
|
+
ActiveRecord::Base.connection
|
82
|
+
end
|
83
|
+
|
84
|
+
%i(
|
85
|
+
connection
|
86
|
+
display_result
|
87
|
+
sql_for
|
88
|
+
sql_path_for
|
89
|
+
).each do |method_name|
|
90
|
+
private_class_method method_name
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
require 'ruby-pg-extras/railtie' if defined?(Rails)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
/* Queries with active locks */
|
2
|
+
|
3
|
+
SELECT
|
4
|
+
pg_stat_activity.pid,
|
5
|
+
pg_class.relname,
|
6
|
+
pg_locks.transactionid,
|
7
|
+
pg_locks.granted,
|
8
|
+
pg_locks.mode,
|
9
|
+
pg_stat_activity.query AS query_snippet,
|
10
|
+
age(now(),pg_stat_activity.query_start) AS "age"
|
11
|
+
FROM pg_stat_activity,pg_locks left
|
12
|
+
OUTER JOIN pg_class
|
13
|
+
ON (pg_locks.relation = pg_class.oid)
|
14
|
+
WHERE pg_stat_activity.query <> '<insufficient privilege>'
|
15
|
+
AND pg_locks.pid = pg_stat_activity.pid
|
16
|
+
AND pg_stat_activity.pid <> pg_backend_pid() order by query_start;
|
@@ -0,0 +1,63 @@
|
|
1
|
+
/* Table and index bloat in your database ordered by most wasteful */
|
2
|
+
|
3
|
+
WITH constants AS (
|
4
|
+
SELECT current_setting('block_size')::numeric AS bs, 23 AS hdr, 4 AS ma
|
5
|
+
), bloat_info AS (
|
6
|
+
SELECT
|
7
|
+
ma,bs,schemaname,tablename,
|
8
|
+
(datawidth+(hdr+ma-(case when hdr%ma=0 THEN ma ELSE hdr%ma END)))::numeric AS datahdr,
|
9
|
+
(maxfracsum*(nullhdr+ma-(case when nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2
|
10
|
+
FROM (
|
11
|
+
SELECT
|
12
|
+
schemaname, tablename, hdr, ma, bs,
|
13
|
+
SUM((1-null_frac)*avg_width) AS datawidth,
|
14
|
+
MAX(null_frac) AS maxfracsum,
|
15
|
+
hdr+(
|
16
|
+
SELECT 1+count(*)/8
|
17
|
+
FROM pg_stats s2
|
18
|
+
WHERE null_frac<>0 AND s2.schemaname = s.schemaname AND s2.tablename = s.tablename
|
19
|
+
) AS nullhdr
|
20
|
+
FROM pg_stats s, constants
|
21
|
+
GROUP BY 1,2,3,4,5
|
22
|
+
) AS foo
|
23
|
+
), table_bloat AS (
|
24
|
+
SELECT
|
25
|
+
schemaname, tablename, cc.relpages, bs,
|
26
|
+
CEIL((cc.reltuples*((datahdr+ma-
|
27
|
+
(CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::float)) AS otta
|
28
|
+
FROM bloat_info
|
29
|
+
JOIN pg_class cc ON cc.relname = bloat_info.tablename
|
30
|
+
JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = bloat_info.schemaname AND nn.nspname <> 'information_schema'
|
31
|
+
), index_bloat AS (
|
32
|
+
SELECT
|
33
|
+
schemaname, tablename, bs,
|
34
|
+
COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages,
|
35
|
+
COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::float)),0) AS iotta -- very rough approximation, assumes all cols
|
36
|
+
FROM bloat_info
|
37
|
+
JOIN pg_class cc ON cc.relname = bloat_info.tablename
|
38
|
+
JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = bloat_info.schemaname AND nn.nspname <> 'information_schema'
|
39
|
+
JOIN pg_index i ON indrelid = cc.oid
|
40
|
+
JOIN pg_class c2 ON c2.oid = i.indexrelid
|
41
|
+
)
|
42
|
+
SELECT
|
43
|
+
type, schemaname, object_name, bloat, pg_size_pretty(raw_waste) as waste
|
44
|
+
FROM
|
45
|
+
(SELECT
|
46
|
+
'table' as type,
|
47
|
+
schemaname,
|
48
|
+
tablename as object_name,
|
49
|
+
ROUND(CASE WHEN otta=0 THEN 0.0 ELSE table_bloat.relpages/otta::numeric END,1) AS bloat,
|
50
|
+
CASE WHEN relpages < otta THEN '0' ELSE (bs*(table_bloat.relpages-otta)::bigint)::bigint END AS raw_waste
|
51
|
+
FROM
|
52
|
+
table_bloat
|
53
|
+
UNION
|
54
|
+
SELECT
|
55
|
+
'index' as type,
|
56
|
+
schemaname,
|
57
|
+
tablename || '::' || iname as object_name,
|
58
|
+
ROUND(CASE WHEN iotta=0 OR ipages=0 THEN 0.0 ELSE ipages/iotta::numeric END,1) AS bloat,
|
59
|
+
CASE WHEN ipages < iotta THEN '0' ELSE (bs*(ipages-iotta))::bigint END AS raw_waste
|
60
|
+
FROM
|
61
|
+
index_bloat) bloat_summary
|
62
|
+
ORDER BY raw_waste DESC, bloat DESC;
|
63
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
/* Queries holding locks other queries are waiting to be released */
|
2
|
+
|
3
|
+
SELECT bl.pid AS blocked_pid,
|
4
|
+
ka.query AS blocking_statement,
|
5
|
+
now() - ka.query_start AS blocking_duration,
|
6
|
+
kl.pid AS blocking_pid,
|
7
|
+
a.query AS blocked_statement,
|
8
|
+
now() - a.query_start AS blocked_duration
|
9
|
+
FROM pg_catalog.pg_locks bl
|
10
|
+
JOIN pg_catalog.pg_stat_activity a
|
11
|
+
ON bl.pid = a.pid
|
12
|
+
JOIN pg_catalog.pg_locks kl
|
13
|
+
JOIN pg_catalog.pg_stat_activity ka
|
14
|
+
ON kl.pid = ka.pid
|
15
|
+
ON bl.transactionid = kl.transactionid AND bl.pid != kl.pid
|
16
|
+
WHERE NOT bl.granted;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
/* Index and table hit rate */
|
2
|
+
|
3
|
+
SELECT
|
4
|
+
'index hit rate' AS name,
|
5
|
+
(sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read),0) AS ratio
|
6
|
+
FROM pg_statio_user_indexes
|
7
|
+
UNION ALL
|
8
|
+
SELECT
|
9
|
+
'table hit rate' AS name,
|
10
|
+
sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read),0) AS ratio
|
11
|
+
FROM pg_statio_user_tables;
|
@@ -0,0 +1,9 @@
|
|
1
|
+
/* 10 queries that have longest execution time in aggregate */
|
2
|
+
|
3
|
+
SELECT query AS qry,
|
4
|
+
interval '1 millisecond' * total_time AS exec_time,
|
5
|
+
to_char((total_time/sum(total_time) OVER()) * 100, 'FM90D0') || '%' AS prop_exec_time,
|
6
|
+
to_char(calls, 'FM999G999G990') AS ncalls,
|
7
|
+
interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time
|
8
|
+
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
|
9
|
+
ORDER BY calls DESC LIMIT 10;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
/* The size of indexes, descending by size */
|
2
|
+
|
3
|
+
SELECT c.relname AS name,
|
4
|
+
pg_size_pretty(sum(c.relpages::bigint*8192)::bigint) AS size
|
5
|
+
FROM pg_class c
|
6
|
+
LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
|
7
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
8
|
+
AND n.nspname !~ '^pg_toast'
|
9
|
+
AND c.relkind='i'
|
10
|
+
GROUP BY c.relname
|
11
|
+
ORDER BY sum(c.relpages) DESC;
|
@@ -0,0 +1,12 @@
|
|
1
|
+
/* Index hit rate (effective databases are at 99% and up) */
|
2
|
+
|
3
|
+
SELECT relname,
|
4
|
+
CASE idx_scan
|
5
|
+
WHEN 0 THEN 'Insufficient data'
|
6
|
+
ELSE (100 * idx_scan / (seq_scan + idx_scan))::text
|
7
|
+
END percent_of_times_index_used,
|
8
|
+
n_live_tup rows_in_table
|
9
|
+
FROM
|
10
|
+
pg_stat_user_tables
|
11
|
+
ORDER BY
|
12
|
+
n_live_tup DESC;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
/* Queries with active exclusive locks */
|
2
|
+
|
3
|
+
SELECT
|
4
|
+
pg_stat_activity.pid,
|
5
|
+
pg_class.relname,
|
6
|
+
pg_locks.transactionid,
|
7
|
+
pg_locks.granted,
|
8
|
+
pg_stat_activity.query AS query_snippet,
|
9
|
+
age(now(),pg_stat_activity.query_start) AS "age"
|
10
|
+
FROM pg_stat_activity,pg_locks left
|
11
|
+
OUTER JOIN pg_class
|
12
|
+
ON (pg_locks.relation = pg_class.oid)
|
13
|
+
WHERE pg_stat_activity.query <> '<insufficient privilege>'
|
14
|
+
AND pg_locks.pid = pg_stat_activity.pid
|
15
|
+
AND pg_locks.mode = 'ExclusiveLock'
|
16
|
+
AND pg_stat_activity.pid <> pg_backend_pid() order by query_start;
|
@@ -0,0 +1,14 @@
|
|
1
|
+
/* All queries longer than five minutes by descending duration */
|
2
|
+
|
3
|
+
SELECT
|
4
|
+
pid,
|
5
|
+
now() - pg_stat_activity.query_start AS duration,
|
6
|
+
query AS query
|
7
|
+
FROM
|
8
|
+
pg_stat_activity
|
9
|
+
WHERE
|
10
|
+
pg_stat_activity.query <> ''::text
|
11
|
+
AND state <> 'idle'
|
12
|
+
AND now() - pg_stat_activity.query_start > interval '5 minutes'
|
13
|
+
ORDER BY
|
14
|
+
now() - pg_stat_activity.query_start DESC;
|
@@ -0,0 +1,21 @@
|
|
1
|
+
/* The mandelbrot set */
|
2
|
+
|
3
|
+
WITH RECURSIVE Z(IX, IY, CX, CY, X, Y, I) AS (
|
4
|
+
SELECT IX, IY, X::float, Y::float, X::float, Y::float, 0
|
5
|
+
FROM (select -2.2 + 0.031 * i, i from generate_series(0,101) as i) as xgen(x,ix),
|
6
|
+
(select -1.5 + 0.031 * i, i from generate_series(0,101) as i) as ygen(y,iy)
|
7
|
+
UNION ALL
|
8
|
+
SELECT IX, IY, CX, CY, X * X - Y * Y + CX AS X, Y * X * 2 + CY, I + 1
|
9
|
+
FROM Z
|
10
|
+
WHERE X * X + Y * Y < 16::float
|
11
|
+
AND I < 100
|
12
|
+
)
|
13
|
+
SELECT array_to_string(array_agg(SUBSTRING(' .,,,-----++++%%%%@@@@#### ', LEAST(GREATEST(I,1),27), 1)),'')
|
14
|
+
FROM (
|
15
|
+
SELECT IX, IY, MAX(I) AS I
|
16
|
+
FROM Z
|
17
|
+
GROUP BY IY, IX
|
18
|
+
ORDER BY IY, IX
|
19
|
+
) AS ZT
|
20
|
+
GROUP BY IY
|
21
|
+
ORDER BY IY;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
/* 10 queries that have longest execution time in aggregate */
|
2
|
+
|
3
|
+
SELECT interval '1 millisecond' * total_time AS total_exec_time,
|
4
|
+
to_char((total_time/sum(total_time) OVER()) * 100, 'FM90D0') || '%' AS prop_exec_time,
|
5
|
+
to_char(calls, 'FM999G999G999G990') AS ncalls,
|
6
|
+
interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time,
|
7
|
+
query AS query
|
8
|
+
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
|
9
|
+
ORDER BY total_time DESC
|
10
|
+
LIMIT 10;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
/* Total size of all the indexes on each table, descending by size */
|
2
|
+
|
3
|
+
SELECT c.relname AS table,
|
4
|
+
pg_size_pretty(pg_indexes_size(c.oid)) AS index_size
|
5
|
+
FROM pg_class c
|
6
|
+
LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
|
7
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
8
|
+
AND n.nspname !~ '^pg_toast'
|
9
|
+
AND c.relkind='r'
|
10
|
+
ORDER BY pg_indexes_size(c.oid) DESC;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
/* Size of the tables (excluding indexes), descending by size */
|
2
|
+
|
3
|
+
SELECT c.relname AS name,
|
4
|
+
pg_size_pretty(pg_table_size(c.oid)) AS size
|
5
|
+
FROM pg_class c
|
6
|
+
LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
|
7
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
8
|
+
AND n.nspname !~ '^pg_toast'
|
9
|
+
AND c.relkind='r'
|
10
|
+
ORDER BY pg_table_size(c.oid) DESC;
|
@@ -0,0 +1,8 @@
|
|
1
|
+
/* Total size of all indexes in MB */
|
2
|
+
|
3
|
+
SELECT pg_size_pretty(sum(c.relpages::bigint*8192)::bigint) AS size
|
4
|
+
FROM pg_class c
|
5
|
+
LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
|
6
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
7
|
+
AND n.nspname !~ '^pg_toast'
|
8
|
+
AND c.relkind='i';
|
@@ -0,0 +1,10 @@
|
|
1
|
+
/* Size of the tables (including indexes), descending by size */
|
2
|
+
|
3
|
+
SELECT c.relname AS name,
|
4
|
+
pg_size_pretty(pg_total_relation_size(c.oid)) AS size
|
5
|
+
FROM pg_class c
|
6
|
+
LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
|
7
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
8
|
+
AND n.nspname !~ '^pg_toast'
|
9
|
+
AND c.relkind='r'
|
10
|
+
ORDER BY pg_total_relation_size(c.oid) DESC;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
/* Unused and almost unused indexes */
|
2
|
+
/* Ordered by their size relative to the number of index scans.
|
3
|
+
Exclude indexes of very small tables (less than 5 pages),
|
4
|
+
where the planner will almost invariably select a sequential scan,
|
5
|
+
but may not in the future as the table grows */
|
6
|
+
|
7
|
+
SELECT
|
8
|
+
schemaname || '.' || relname AS table,
|
9
|
+
indexrelname AS index,
|
10
|
+
pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size,
|
11
|
+
idx_scan as index_scans
|
12
|
+
FROM pg_stat_user_indexes ui
|
13
|
+
JOIN pg_index i ON ui.indexrelid = i.indexrelid
|
14
|
+
WHERE NOT indisunique AND idx_scan < 50 AND pg_relation_size(relid) > 5 * 8192
|
15
|
+
ORDER BY pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,
|
16
|
+
pg_relation_size(i.indexrelid) DESC;
|
@@ -0,0 +1,40 @@
|
|
1
|
+
/* Dead rows and whether an automatic vacuum is expected to be triggered */
|
2
|
+
|
3
|
+
WITH table_opts AS (
|
4
|
+
SELECT
|
5
|
+
pg_class.oid, relname, nspname, array_to_string(reloptions, '') AS relopts
|
6
|
+
FROM
|
7
|
+
pg_class INNER JOIN pg_namespace ns ON relnamespace = ns.oid
|
8
|
+
), vacuum_settings AS (
|
9
|
+
SELECT
|
10
|
+
oid, relname, nspname,
|
11
|
+
CASE
|
12
|
+
WHEN relopts LIKE '%autovacuum_vacuum_threshold%'
|
13
|
+
THEN substring(relopts, '.*autovacuum_vacuum_threshold=([0-9.]+).*')::integer
|
14
|
+
ELSE current_setting('autovacuum_vacuum_threshold')::integer
|
15
|
+
END AS autovacuum_vacuum_threshold,
|
16
|
+
CASE
|
17
|
+
WHEN relopts LIKE '%autovacuum_vacuum_scale_factor%'
|
18
|
+
THEN substring(relopts, '.*autovacuum_vacuum_scale_factor=([0-9.]+).*')::real
|
19
|
+
ELSE current_setting('autovacuum_vacuum_scale_factor')::real
|
20
|
+
END AS autovacuum_vacuum_scale_factor
|
21
|
+
FROM
|
22
|
+
table_opts
|
23
|
+
)
|
24
|
+
SELECT
|
25
|
+
vacuum_settings.nspname AS schema,
|
26
|
+
vacuum_settings.relname AS table,
|
27
|
+
to_char(psut.last_vacuum, 'YYYY-MM-DD HH24:MI') AS last_vacuum,
|
28
|
+
to_char(psut.last_autovacuum, 'YYYY-MM-DD HH24:MI') AS last_autovacuum,
|
29
|
+
to_char(pg_class.reltuples, '9G999G999G999') AS rowcount,
|
30
|
+
to_char(psut.n_dead_tup, '9G999G999G999') AS dead_rowcount,
|
31
|
+
to_char(autovacuum_vacuum_threshold
|
32
|
+
+ (autovacuum_vacuum_scale_factor::numeric * pg_class.reltuples), '9G999G999G999') AS autovacuum_threshold,
|
33
|
+
CASE
|
34
|
+
WHEN autovacuum_vacuum_threshold + (autovacuum_vacuum_scale_factor::numeric * pg_class.reltuples) < psut.n_dead_tup
|
35
|
+
THEN 'yes'
|
36
|
+
END AS expect_autovacuum
|
37
|
+
FROM
|
38
|
+
pg_stat_user_tables psut INNER JOIN pg_class ON psut.relid = pg_class.oid
|
39
|
+
INNER JOIN vacuum_settings ON pg_class.oid = vacuum_settings.oid
|
40
|
+
ORDER BY 1;
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ruby-pg-extras'
|
4
|
+
|
5
|
+
namespace :pg_extras do
|
6
|
+
RubyPGExtras::QUERIES.each do |query_name|
|
7
|
+
desc RubyPGExtras.description_for(query_name: query_name)
|
8
|
+
task query_name.to_sym => :environment do
|
9
|
+
RubyPGExtras.public_send(query_name)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ruby-pg-extras/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "ruby-pg-extras"
|
8
|
+
gem.version = RubyPGExtras::VERSION
|
9
|
+
gem.authors = ["pawurb"]
|
10
|
+
gem.email = ["contact@pawelurbanek.com"]
|
11
|
+
gem.summary = %q{ Ruby PostgreSQL performance database insights }
|
12
|
+
gem.description = %q{ Ruby port of Heroku PG Extras. The goal of this project is to provide a powerful insights into PostgreSQL database for Ruby on Rails apps that are not using the default Heroku PostgreSQL plugin. }
|
13
|
+
gem.homepage = "http://github.com/pawurb/ruby-pg-extras"
|
14
|
+
gem.files = `git ls-files`.split("\n")
|
15
|
+
gem.test_files = gem.files.grep(%r{^(spec)/})
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.add_dependency "pg"
|
19
|
+
gem.add_dependency "terminal-table"
|
20
|
+
gem.add_development_dependency "rake"
|
21
|
+
gem.add_development_dependency "rspec"
|
22
|
+
end
|
data/spec/smoke_spec.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe RubyPGExtras do
|
6
|
+
RubyPGExtras::QUERIES.each do |query_name|
|
7
|
+
it "#{query_name} description can be read" do
|
8
|
+
expect do
|
9
|
+
RubyPGExtras.description_for(
|
10
|
+
query_name: query_name
|
11
|
+
)
|
12
|
+
end.not_to raise_error
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
PG_STATS_DEPENDENT_QUERIES = %i(calls outliers)
|
17
|
+
|
18
|
+
(RubyPGExtras::QUERIES - PG_STATS_DEPENDENT_QUERIES).each do |query_name|
|
19
|
+
it "#{query_name} query can be executed" do
|
20
|
+
expect do
|
21
|
+
RubyPGExtras.run_query(
|
22
|
+
query_name: query_name,
|
23
|
+
in_format: :hash
|
24
|
+
)
|
25
|
+
end.not_to raise_error
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/setup'
|
5
|
+
require_relative '../lib/ruby-pg-extras'
|
6
|
+
|
7
|
+
ENV["DATABASE_URL"] ||= "postgresql://postgres:secret@localhost:5432/ruby-pg-extras-test"
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.before :suite do
|
11
|
+
ActiveRecord::Base.establish_connection(
|
12
|
+
ENV.fetch("DATABASE_URL")
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
config.after :suite do
|
17
|
+
ActiveRecord::Base.remove_connection
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-pg-extras
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- pawurb
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-01-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pg
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: terminal-table
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: " Ruby port of Heroku PG Extras. The goal of this project is to provide
|
70
|
+
a powerful insights into PostgreSQL database for Ruby on Rails apps that are not
|
71
|
+
using the default Heroku PostgreSQL plugin. "
|
72
|
+
email:
|
73
|
+
- contact@pawelurbanek.com
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- ".gitignore"
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- lib/ruby-pg-extras.rb
|
84
|
+
- lib/ruby-pg-extras/queries/all_locks.sql
|
85
|
+
- lib/ruby-pg-extras/queries/bloat.sql
|
86
|
+
- lib/ruby-pg-extras/queries/blocking.sql
|
87
|
+
- lib/ruby-pg-extras/queries/cache_hit.sql
|
88
|
+
- lib/ruby-pg-extras/queries/calls.sql
|
89
|
+
- lib/ruby-pg-extras/queries/extensions.sql
|
90
|
+
- lib/ruby-pg-extras/queries/index_size.sql
|
91
|
+
- lib/ruby-pg-extras/queries/index_usage.sql
|
92
|
+
- lib/ruby-pg-extras/queries/locks.sql
|
93
|
+
- lib/ruby-pg-extras/queries/long_running_queries.sql
|
94
|
+
- lib/ruby-pg-extras/queries/mandelbrot.sql
|
95
|
+
- lib/ruby-pg-extras/queries/outliers.sql
|
96
|
+
- lib/ruby-pg-extras/queries/records_rank.sql
|
97
|
+
- lib/ruby-pg-extras/queries/seq_scans.sql
|
98
|
+
- lib/ruby-pg-extras/queries/table_indexes_size.sql
|
99
|
+
- lib/ruby-pg-extras/queries/table_size.sql
|
100
|
+
- lib/ruby-pg-extras/queries/total_index_size.sql
|
101
|
+
- lib/ruby-pg-extras/queries/total_table_size.sql
|
102
|
+
- lib/ruby-pg-extras/queries/unused_indexes.sql
|
103
|
+
- lib/ruby-pg-extras/queries/vacuum_stats.sql
|
104
|
+
- lib/ruby-pg-extras/tasks/all.rake
|
105
|
+
- lib/ruby-pg-extras/version.rb
|
106
|
+
- ruby-pg-extras.gemspec
|
107
|
+
- spec/smoke_spec.rb
|
108
|
+
- spec/spec_helper.rb
|
109
|
+
homepage: http://github.com/pawurb/ruby-pg-extras
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
metadata: {}
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubygems_version: 3.0.6
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: Ruby PostgreSQL performance database insights
|
132
|
+
test_files:
|
133
|
+
- spec/smoke_spec.rb
|
134
|
+
- spec/spec_helper.rb
|