ruby-pg-extras 1.2.3 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 390b7ac85682a6c91029781a9cf2e639993048c0c2b9b80a93e0b83ef52ba457
4
- data.tar.gz: d84e02a04eaba3ef8d899cfd137139d5bb9d00310480fe0d7a2b70786538a14d
3
+ metadata.gz: 741b8ec49ba2e7b4933ddbe085bd3c4f90ab1640e951fdec1dfa1178c4bf8ab0
4
+ data.tar.gz: d1b7d405ef8da4bcd450e461a6ff4411b760128bd22e660c1dc102aaa1cf9453
5
5
  SHA512:
6
- metadata.gz: e07b9007849b685743727d590ed095c68868c364a5090f2a030a50bafcfa25b15132f3954d3daa301506ec82210b4a2e2e78ddf21b92bfbcb8643bf8825f38e9
7
- data.tar.gz: eda59a08e3ac273743897ab2460a1653ae4c33d1d4ec1b38e38f8a6f7cc44275f984f5231703435191b660181e23c5d75469d9fec498c73f67b3a9a70921ddad
6
+ metadata.gz: ce5011d3e926a7b1a246b6268d7d58a8f05f26bbab22bd665209daec81f4d03867b78c8306f6d3ebd4661acc1ae96640427ea33486d3ab6f91b1716c20b11cc1
7
+ data.tar.gz: 1cee23d5f7e171bdd13eeea76102a59ff1ad6774a9b1c80b614e76e9d4a60d4b76b098403fd69e7e5913c001d98e0b4792fc1f58762234d0b10af7cbd6a1ef6e
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 install postgresql-client
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 seperately.
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 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.
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 filesystem).
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 calcualtes by using the system administration function `pg_indexes_size()`.
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 | 1418 MB | t | reset_token | 96.15% | 1363 MB
363
+ 88732 | plan_cancelled_at | 1651 MB | f | cancelled_at | 6.11% | 101 MB
364
+ 9827345 | users_email | 22 MB | t | email | 11.21% | 2494 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 underindexed, and it may be worth investigating queries that read from these tables.
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 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.
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
 
@@ -10,26 +10,37 @@ 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
+ })
26
+
20
27
  QUERIES.each do |query_name|
21
- define_singleton_method query_name do |options = { in_format: :display_table }|
28
+ define_singleton_method query_name do |options = {}|
22
29
  run_query(
23
30
  query_name: query_name,
24
- in_format: options.fetch(:in_format)
31
+ in_format: options.fetch(:in_format, :display_table),
32
+ args: options.fetch(:args, {})
25
33
  )
26
34
  end
27
35
  end
28
36
 
29
- def self.run_query(query_name:, in_format:)
30
- result = connection.exec(
37
+ def self.run_query(query_name:, in_format:, args: {})
38
+ sql = if (custom_args = DEFAULT_ARGS[query_name].merge(args)) != {}
39
+ sql_for(query_name: query_name) % custom_args
40
+ else
31
41
  sql_for(query_name: query_name)
32
- )
42
+ end
43
+ result = connection.exec(sql)
33
44
 
34
45
  display_result(
35
46
  result,
@@ -1,9 +1,9 @@
1
- /* 10 queries that have highest frequency of execution */
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') || '%' AS prop_exec_time,
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 10;
9
+ ORDER BY calls DESC LIMIT %{limit};
@@ -1,4 +1,4 @@
1
- /* All queries longer than five minutes by descending duration */
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 '5 minutes'
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 indexed columns with high null_frac */
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 10MB
30
+ AND pg_relation_size(c.oid) > 10 * 1024 ^ 2
31
+ ORDER BY
32
+ pg_relation_size(c.oid) * s.null_frac DESC;
@@ -1,10 +1,10 @@
1
- /* 10 queries that have longest execution time in aggregate */
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') || '%' AS prop_exec_time,
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;
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 < 50 AND pg_relation_size(relid) > 5 * 8192
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;
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyPGExtras
4
- VERSION = "1.2.3"
4
+ VERSION = "1.5.0"
5
5
  end
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.2.3
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - pawurb
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-08 00:00:00.000000000 Z
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.2
133
+ rubygems_version: 3.1.4
133
134
  signing_key:
134
135
  specification_version: 4
135
136
  summary: Ruby PostgreSQL performance database insights