ruby-pg-extras 1.2.4 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +2 -1
- data/README.md +34 -9
- data/lib/ruby-pg-extras.rb +18 -6
- data/lib/ruby-pg-extras/queries/calls.sql +3 -3
- data/lib/ruby-pg-extras/queries/long_running_queries.sql +2 -2
- data/lib/ruby-pg-extras/queries/null_indexes.sql +32 -0
- data/lib/ruby-pg-extras/queries/outliers.sql +3 -3
- data/lib/ruby-pg-extras/queries/unused_indexes.sql +1 -1
- data/lib/ruby-pg-extras/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 869687fea5c3e1c4c00ddd705bd3446413529164b8d628368daff0fe7161ee94
|
4
|
+
data.tar.gz: 074a7847ca0e36fad4059a5650d2a02806d01da92deeabb19bf1a93ae490366f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d51fd8809cda4c5d167a352ab6a0c1dbd09e043474a5a763e4ca1853a35d0f4fda8788d7f603bd89238823ddf1cb1a9014eec97674ea039b367d3b449f6ab96
|
7
|
+
data.tar.gz: d1fcf3c26a1ff72ae8b2cff877c650d38e5ebc03b4ef139c62a9f7be04582882e1f18f9b24ac1bfcb2fc746492ac5e28fe63009afa77413a1cf3a0d4f626a65c
|
data/.circleci/config.yml
CHANGED
@@ -16,7 +16,8 @@ jobs:
|
|
16
16
|
- run: gem update --system
|
17
17
|
- run: gem install bundler
|
18
18
|
- run: bundle install --path vendor/bundle
|
19
|
-
- run: sudo apt
|
19
|
+
- run: sudo apt-get update
|
20
|
+
- run: sudo apt install postgresql-client-11
|
20
21
|
- run: dockerize -wait tcp://localhost:5432 -timeout 1m
|
21
22
|
- run:
|
22
23
|
name: Run specs
|
data/README.md
CHANGED
@@ -16,6 +16,8 @@ Alternative versions:
|
|
16
16
|
|
17
17
|
- [Python](https://github.com/pawurb/python-pg-extras)
|
18
18
|
|
19
|
+
- [Haskell](https://github.com/pawurb/haskell-pg-extras)
|
20
|
+
|
19
21
|
## Installation
|
20
22
|
|
21
23
|
In your Gemfile
|
@@ -83,6 +85,13 @@ RubyPGExtras.cache_hit(in_format: :raw) =>
|
|
83
85
|
#<PG::Result:0x00007f75777f7328 status=PGRES_TUPLES_OK ntuples=2 nfields=2 cmd_tuples=2>
|
84
86
|
```
|
85
87
|
|
88
|
+
Some methods accept an optional `args` param allowing you to customize queries:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
RubyPGExtras.long_running_queries(args: { threshold: "200 milliseconds" })
|
92
|
+
|
93
|
+
```
|
94
|
+
|
86
95
|
## Available methods
|
87
96
|
|
88
97
|
### `cache_hit`
|
@@ -114,7 +123,7 @@ RubyPGExtras.index_cache_hit
|
|
114
123
|
(truncated results for brevity)
|
115
124
|
```
|
116
125
|
|
117
|
-
The same as `cache_hit` with each table's indexes cache hit info displayed
|
126
|
+
The same as `cache_hit` with each table's indexes cache hit info displayed separately.
|
118
127
|
|
119
128
|
### `table_cache_hit`
|
120
129
|
|
@@ -167,7 +176,7 @@ RubyPGExtras.locks
|
|
167
176
|
(4 rows)
|
168
177
|
```
|
169
178
|
|
170
|
-
This command displays queries that have taken out an
|
179
|
+
This command displays queries that have taken out an exclusive 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.
|
171
180
|
|
172
181
|
### `all_locks`
|
173
182
|
|
@@ -183,7 +192,7 @@ This command displays all the current locks, regardless of their type.
|
|
183
192
|
|
184
193
|
```ruby
|
185
194
|
|
186
|
-
RubyPGExtras.outliers
|
195
|
+
RubyPGExtras.outliers(args: { limit: 20 })
|
187
196
|
|
188
197
|
qry | exec_time | prop_exec_time | ncalls | sync_io_time
|
189
198
|
-----------------------------------------+------------------+----------------+-------------+--------------
|
@@ -197,7 +206,7 @@ RubyPGExtras.outliers
|
|
197
206
|
(truncated results for brevity)
|
198
207
|
```
|
199
208
|
|
200
|
-
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
|
209
|
+
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 file system).
|
201
210
|
|
202
211
|
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.
|
203
212
|
|
@@ -205,7 +214,7 @@ Typically, an efficient query will have an appropriate ratio of calls to total e
|
|
205
214
|
|
206
215
|
```ruby
|
207
216
|
|
208
|
-
RubyPGExtras.calls
|
217
|
+
RubyPGExtras.calls(args: { limit: 10 })
|
209
218
|
|
210
219
|
qry | exec_time | prop_exec_time | ncalls | sync_io_time
|
211
220
|
-----------------------------------------+------------------+----------------+-------------+--------------
|
@@ -306,7 +315,7 @@ RubyPGExtras.table_indexes_size
|
|
306
315
|
(truncated results for brevity)
|
307
316
|
```
|
308
317
|
|
309
|
-
This command displays the total size of indexes for each table and materialized view, in MB. It is
|
318
|
+
This command displays the total size of indexes for each table and materialized view, in MB. It is calculated by using the system administration function `pg_indexes_size()`.
|
310
319
|
|
311
320
|
### `total_table_size`
|
312
321
|
|
@@ -330,7 +339,7 @@ This command displays the total size of each table and materialized view in the
|
|
330
339
|
|
331
340
|
```ruby
|
332
341
|
|
333
|
-
RubyPGExtras.unused_indexes
|
342
|
+
RubyPGExtras.unused_indexes(args: { min_scans: 20 })
|
334
343
|
|
335
344
|
table | index | index_size | index_scans
|
336
345
|
---------------------+--------------------------------------------+------------+-------------
|
@@ -342,6 +351,22 @@ RubyPGExtras.unused_indexes
|
|
342
351
|
|
343
352
|
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.
|
344
353
|
|
354
|
+
### `null_indexes`
|
355
|
+
|
356
|
+
```ruby
|
357
|
+
|
358
|
+
RubyPGExtras.null_indexes
|
359
|
+
|
360
|
+
oid | index | index_size | unique | indexed_column | null_frac | expected_saving
|
361
|
+
---------+--------------------+------------+--------+----------------+-----------+-----------------
|
362
|
+
183764 | users_reset_token | 1445 MB | t | reset_token | 97.00% | 1401 MB
|
363
|
+
88732 | plan_cancelled_at | 539 MB | f | cancelled_at | 8.30% | 44 MB
|
364
|
+
9827345 | users_email | 18 MB | t | email | 28.67% | 5160 kB
|
365
|
+
|
366
|
+
```
|
367
|
+
|
368
|
+
This commands displays indexes that contain `NULL` values. A high ratio of `NULL` values means that using a partial index excluding them will be beneficial in case they are not used for searching. [Source and more info](https://hakibenita.com/postgresql-unused-index-size).
|
369
|
+
|
345
370
|
### `seq_scans`
|
346
371
|
|
347
372
|
```ruby
|
@@ -362,7 +387,7 @@ RubyPGExtras.seq_scans
|
|
362
387
|
(truncated results for brevity)
|
363
388
|
```
|
364
389
|
|
365
|
-
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
|
390
|
+
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 under-indexed, and it may be worth investigating queries that read from these tables.
|
366
391
|
|
367
392
|
### `long_running_queries`
|
368
393
|
|
@@ -435,7 +460,7 @@ RubyPGExtras.vacuum_stats
|
|
435
460
|
(truncated results for brevity)
|
436
461
|
```
|
437
462
|
|
438
|
-
This command displays statistics related to vacuum operations for each table, including an
|
463
|
+
This command displays statistics related to vacuum operations for each table, including an estimation 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.
|
439
464
|
|
440
465
|
### `kill_all`
|
441
466
|
|
data/lib/ruby-pg-extras.rb
CHANGED
@@ -10,26 +10,38 @@ module RubyPGExtras
|
|
10
10
|
QUERIES = %i(
|
11
11
|
bloat blocking cache_hit
|
12
12
|
calls extensions table_cache_hit index_cache_hit
|
13
|
-
index_size index_usage locks all_locks
|
13
|
+
index_size index_usage null_indexes locks all_locks
|
14
14
|
long_running_queries mandelbrot outliers
|
15
15
|
records_rank seq_scans table_indexes_size
|
16
16
|
table_size total_index_size total_table_size
|
17
17
|
unused_indexes vacuum_stats kill_all
|
18
18
|
)
|
19
19
|
|
20
|
+
DEFAULT_ARGS = Hash.new({}).merge({
|
21
|
+
calls: { limit: 10 },
|
22
|
+
long_running_queries: { threshold: "500 milliseconds" },
|
23
|
+
outliers: { limit: 10 },
|
24
|
+
unused_indexes: { min_scans: 50 },
|
25
|
+
null_indexes: { min_relation_size_mb: 10 }
|
26
|
+
})
|
27
|
+
|
20
28
|
QUERIES.each do |query_name|
|
21
|
-
define_singleton_method query_name do |options = {
|
29
|
+
define_singleton_method query_name do |options = {}|
|
22
30
|
run_query(
|
23
31
|
query_name: query_name,
|
24
|
-
in_format: options.fetch(:in_format)
|
32
|
+
in_format: options.fetch(:in_format, :display_table),
|
33
|
+
args: options.fetch(:args, {})
|
25
34
|
)
|
26
35
|
end
|
27
36
|
end
|
28
37
|
|
29
|
-
def self.run_query(query_name:, in_format:)
|
30
|
-
|
38
|
+
def self.run_query(query_name:, in_format:, args: {})
|
39
|
+
sql = if (custom_args = DEFAULT_ARGS[query_name].merge(args)) != {}
|
40
|
+
sql_for(query_name: query_name) % custom_args
|
41
|
+
else
|
31
42
|
sql_for(query_name: query_name)
|
32
|
-
|
43
|
+
end
|
44
|
+
result = connection.exec(sql)
|
33
45
|
|
34
46
|
display_result(
|
35
47
|
result,
|
@@ -1,9 +1,9 @@
|
|
1
|
-
/*
|
1
|
+
/* Queries that have highest frequency of execution */
|
2
2
|
|
3
3
|
SELECT query AS qry,
|
4
4
|
interval '1 millisecond' * total_time AS exec_time,
|
5
|
-
to_char((total_time/sum(total_time) OVER()) * 100, 'FM90D0') || '
|
5
|
+
to_char((total_time/sum(total_time) OVER()) * 100, 'FM90D0') || '%%' AS prop_exec_time,
|
6
6
|
to_char(calls, 'FM999G999G990') AS ncalls,
|
7
7
|
interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time
|
8
8
|
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
|
9
|
-
ORDER BY calls DESC LIMIT
|
9
|
+
ORDER BY calls DESC LIMIT %{limit};
|
@@ -1,4 +1,4 @@
|
|
1
|
-
/* All queries longer than
|
1
|
+
/* All queries longer than the threshold by descending duration */
|
2
2
|
|
3
3
|
SELECT
|
4
4
|
pid,
|
@@ -9,6 +9,6 @@ FROM
|
|
9
9
|
WHERE
|
10
10
|
pg_stat_activity.query <> ''::text
|
11
11
|
AND state <> 'idle'
|
12
|
-
AND now() - pg_stat_activity.query_start > interval '
|
12
|
+
AND now() - pg_stat_activity.query_start > interval '%{threshold}'
|
13
13
|
ORDER BY
|
14
14
|
now() - pg_stat_activity.query_start DESC;
|
@@ -0,0 +1,32 @@
|
|
1
|
+
/* Find indexes with a high ratio of NULL values */
|
2
|
+
SELECT
|
3
|
+
c.oid,
|
4
|
+
c.relname AS index,
|
5
|
+
pg_size_pretty(pg_relation_size(c.oid)) AS index_size,
|
6
|
+
i.indisunique AS unique,
|
7
|
+
a.attname AS indexed_column,
|
8
|
+
CASE s.null_frac
|
9
|
+
WHEN 0 THEN ''
|
10
|
+
ELSE to_char(s.null_frac * 100, '999.00%')
|
11
|
+
END AS null_frac,
|
12
|
+
pg_size_pretty((pg_relation_size(c.oid) * s.null_frac)::bigint) AS expected_saving
|
13
|
+
FROM
|
14
|
+
pg_class c
|
15
|
+
JOIN pg_index i ON i.indexrelid = c.oid
|
16
|
+
JOIN pg_attribute a ON a.attrelid = c.oid
|
17
|
+
JOIN pg_class c_table ON c_table.oid = i.indrelid
|
18
|
+
JOIN pg_indexes ixs ON c.relname = ixs.indexname
|
19
|
+
LEFT JOIN pg_stats s ON s.tablename = c_table.relname AND a.attname = s.attname
|
20
|
+
WHERE
|
21
|
+
-- Primary key cannot be partial
|
22
|
+
NOT i.indisprimary
|
23
|
+
-- Exclude already partial indexes
|
24
|
+
AND i.indpred IS NULL
|
25
|
+
-- Exclude composite indexes
|
26
|
+
AND array_length(i.indkey, 1) = 1
|
27
|
+
-- Exclude indexes without null_frac ratio
|
28
|
+
AND coalesce(s.null_frac, 0) != 0
|
29
|
+
-- Larger than threshold
|
30
|
+
AND pg_relation_size(c.oid) > %{min_relation_size_mb} * 1024 ^ 2
|
31
|
+
ORDER BY
|
32
|
+
pg_relation_size(c.oid) * s.null_frac DESC;
|
@@ -1,10 +1,10 @@
|
|
1
|
-
/*
|
1
|
+
/* Queries that have longest execution time in aggregate */
|
2
2
|
|
3
3
|
SELECT interval '1 millisecond' * total_time AS total_exec_time,
|
4
|
-
to_char((total_time/sum(total_time) OVER()) * 100, 'FM90D0') || '
|
4
|
+
to_char((total_time/sum(total_time) OVER()) * 100, 'FM90D0') || '%%' AS prop_exec_time,
|
5
5
|
to_char(calls, 'FM999G999G999G990') AS ncalls,
|
6
6
|
interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time,
|
7
7
|
query AS query
|
8
8
|
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
|
9
9
|
ORDER BY total_time DESC
|
10
|
-
LIMIT
|
10
|
+
LIMIT %{limit};
|
@@ -11,6 +11,6 @@ SELECT
|
|
11
11
|
idx_scan as index_scans
|
12
12
|
FROM pg_stat_user_indexes ui
|
13
13
|
JOIN pg_index i ON ui.indexrelid = i.indexrelid
|
14
|
-
WHERE NOT indisunique AND idx_scan <
|
14
|
+
WHERE NOT indisunique AND idx_scan < %{min_scans} AND pg_relation_size(relid) > 5 * 8192
|
15
15
|
ORDER BY pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,
|
16
16
|
pg_relation_size(i.indexrelid) DESC;
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-pg-extras
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- pawurb
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -96,6 +96,7 @@ files:
|
|
96
96
|
- lib/ruby-pg-extras/queries/locks.sql
|
97
97
|
- lib/ruby-pg-extras/queries/long_running_queries.sql
|
98
98
|
- lib/ruby-pg-extras/queries/mandelbrot.sql
|
99
|
+
- lib/ruby-pg-extras/queries/null_indexes.sql
|
99
100
|
- lib/ruby-pg-extras/queries/outliers.sql
|
100
101
|
- lib/ruby-pg-extras/queries/records_rank.sql
|
101
102
|
- lib/ruby-pg-extras/queries/seq_scans.sql
|
@@ -129,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
130
|
- !ruby/object:Gem::Version
|
130
131
|
version: '0'
|
131
132
|
requirements: []
|
132
|
-
rubygems_version: 3.1.
|
133
|
+
rubygems_version: 3.1.4
|
133
134
|
signing_key:
|
134
135
|
specification_version: 4
|
135
136
|
summary: Ruby PostgreSQL performance database insights
|