pgdexter 0.6.0 → 0.6.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +4 -4
- data/lib/dexter/client.rb +1 -1
- data/lib/dexter/connection.rb +4 -0
- data/lib/dexter/index_creator.rb +72 -0
- data/lib/dexter/indexer.rb +14 -73
- data/lib/dexter/logging.rb +1 -1
- data/lib/dexter/parsers/stderr_log_parser.rb +2 -2
- data/lib/dexter/version.rb +1 -1
- data/lib/dexter.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c6d5363b46c95785cf87964a0d1226161b43433e10d7700c71342ebcf3c10bc
|
4
|
+
data.tar.gz: 89edef30213d5ec288c9c7ab8bcb8b371649f569408a34abb4fe9038050729dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d65547dc4e985162671f3a5d32b66534ad9ceb7e17dbe1bbf516e16b0b820abadbf32aa045a0a431b36e92ea5ab1347539db220bc08d67c50095b501c6b56b62
|
7
|
+
data.tar.gz: c86be55126a037ddeba3170076ac94b3d10cbe0f29e530aa5a2e077b62719998427668420c3802f2ae612cea4127f4536db16683466c8f9184678ec2284481f5
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -12,8 +12,8 @@ First, install [HypoPG](https://github.com/HypoPG/hypopg) on your database serve
|
|
12
12
|
|
13
13
|
```sh
|
14
14
|
cd /tmp
|
15
|
-
curl -L https://github.com/HypoPG/hypopg/archive/1.4.
|
16
|
-
cd hypopg-1.4.
|
15
|
+
curl -L https://github.com/HypoPG/hypopg/archive/1.4.2.tar.gz | tar xz
|
16
|
+
cd hypopg-1.4.2
|
17
17
|
make
|
18
18
|
make install # may need sudo
|
19
19
|
```
|
@@ -232,10 +232,10 @@ If compilation fails with `fatal error: postgres.h: No such file or directory`,
|
|
232
232
|
For Ubuntu and Debian, use:
|
233
233
|
|
234
234
|
```sh
|
235
|
-
sudo apt-get install postgresql-server-dev-
|
235
|
+
sudo apt-get install postgresql-server-dev-17
|
236
236
|
```
|
237
237
|
|
238
|
-
Note: Replace `
|
238
|
+
Note: Replace `17` with your Postgres server version
|
239
239
|
|
240
240
|
## Additional Installation Methods
|
241
241
|
|
data/lib/dexter/client.rb
CHANGED
@@ -112,7 +112,7 @@ module Dexter
|
|
112
112
|
|
113
113
|
options[:dbname] = arguments.shift unless options[:dbname]
|
114
114
|
|
115
|
-
# TODO
|
115
|
+
# TODO remove global variable
|
116
116
|
$log_level = options[:log_level].to_s.downcase
|
117
117
|
unless ["error", "info", "debug", "debug2", "debug3"].include?($log_level)
|
118
118
|
raise Error, "Unknown log level"
|
data/lib/dexter/connection.rb
CHANGED
@@ -41,6 +41,10 @@ module Dexter
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
def quote_ident(value)
|
45
|
+
value.split(".").map { |v| conn.quote_ident(v) }.join(".")
|
46
|
+
end
|
47
|
+
|
44
48
|
def server_version_num
|
45
49
|
@server_version_num ||= execute("SHOW server_version_num").first["server_version_num"].to_i
|
46
50
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Dexter
|
2
|
+
class IndexCreator
|
3
|
+
include Logging
|
4
|
+
|
5
|
+
def initialize(connection, indexer, new_indexes, tablespace)
|
6
|
+
@connection = connection
|
7
|
+
@indexer = indexer
|
8
|
+
@new_indexes = new_indexes
|
9
|
+
@tablespace = tablespace
|
10
|
+
end
|
11
|
+
|
12
|
+
# 1. create lock
|
13
|
+
# 2. refresh existing index list
|
14
|
+
# 3. create indexes that still don't exist
|
15
|
+
# 4. release lock
|
16
|
+
def perform
|
17
|
+
with_advisory_lock do
|
18
|
+
@new_indexes.each do |index|
|
19
|
+
unless index_exists?(index)
|
20
|
+
statement = String.new("CREATE INDEX CONCURRENTLY ON #{@connection.quote_ident(index[:table])} (#{index[:columns].map { |c| @connection.quote_ident(c) }.join(", ")})")
|
21
|
+
statement << " TABLESPACE #{@connection.quote_ident(@tablespace)}" if @tablespace
|
22
|
+
log "Creating index: #{statement}"
|
23
|
+
started_at = monotonic_time
|
24
|
+
begin
|
25
|
+
@connection.execute(statement)
|
26
|
+
log "Index created: #{((monotonic_time - started_at) * 1000).to_i} ms"
|
27
|
+
rescue PG::LockNotAvailable
|
28
|
+
log "Could not acquire lock: #{index[:table]}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def monotonic_time
|
38
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
39
|
+
end
|
40
|
+
|
41
|
+
def with_advisory_lock
|
42
|
+
lock_id = 123456
|
43
|
+
first_time = true
|
44
|
+
while @connection.execute("SELECT pg_try_advisory_lock($1)", params: [lock_id]).first["pg_try_advisory_lock"] != "t"
|
45
|
+
if first_time
|
46
|
+
log "Waiting for lock..."
|
47
|
+
first_time = false
|
48
|
+
end
|
49
|
+
sleep(1)
|
50
|
+
end
|
51
|
+
yield
|
52
|
+
ensure
|
53
|
+
suppress_messages do
|
54
|
+
@connection.execute("SELECT pg_advisory_unlock($1)", params: [lock_id])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def suppress_messages
|
59
|
+
@connection.send(:conn).set_notice_processor do |message|
|
60
|
+
# do nothing
|
61
|
+
end
|
62
|
+
yield
|
63
|
+
ensure
|
64
|
+
# clear notice processor
|
65
|
+
@connection.send(:conn).set_notice_processor
|
66
|
+
end
|
67
|
+
|
68
|
+
def index_exists?(index)
|
69
|
+
@indexer.send(:indexes, [index[:table]]).find { |i| i["columns"] == index[:columns] }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/dexter/indexer.rb
CHANGED
@@ -30,6 +30,7 @@ module Dexter
|
|
30
30
|
|
31
31
|
if tables.any?
|
32
32
|
# analyze tables if needed
|
33
|
+
# TODO remove @log_level in 0.7.0
|
33
34
|
analyze_tables(tables) if @analyze || @log_level == "debug2"
|
34
35
|
|
35
36
|
# get initial costs for queries
|
@@ -41,7 +42,8 @@ module Dexter
|
|
41
42
|
ColumnResolver.new(@connection, candidate_queries, log_level: @log_level).perform
|
42
43
|
candidate_queries.each do |query|
|
43
44
|
# no reason to use btree index for json columns
|
44
|
-
|
45
|
+
# TODO check type supports btree
|
46
|
+
query.candidate_columns = query.columns.reject { |c| ["json", "jsonb", "point"].include?(c[:type]) }.sort_by { |c| [c[:table], c[:column]] }
|
45
47
|
end
|
46
48
|
candidate_queries.select! { |q| q.candidate_columns.any? }
|
47
49
|
|
@@ -59,7 +61,7 @@ module Dexter
|
|
59
61
|
show_debug_info(new_indexes, queries) if @log_level.start_with?("debug")
|
60
62
|
|
61
63
|
# create new indexes
|
62
|
-
|
64
|
+
IndexCreator.new(@connection, self, new_indexes, @tablespace).perform if @create && new_indexes.any?
|
63
65
|
end
|
64
66
|
|
65
67
|
private
|
@@ -120,7 +122,7 @@ module Dexter
|
|
120
122
|
end
|
121
123
|
|
122
124
|
if @analyze && (!la || la < Time.now - 3600)
|
123
|
-
statement = "ANALYZE #{quote_ident(table)}"
|
125
|
+
statement = "ANALYZE #{@connection.quote_ident(table)}"
|
124
126
|
log "Running analyze: #{statement}"
|
125
127
|
execute(statement)
|
126
128
|
end
|
@@ -134,7 +136,8 @@ module Dexter
|
|
134
136
|
puts
|
135
137
|
end
|
136
138
|
begin
|
137
|
-
|
139
|
+
plan = self.plan(query.statement)
|
140
|
+
query.plans << plan if plan && plan["Total Cost"]
|
138
141
|
rescue PG::Error, JSON::NestingError => e
|
139
142
|
if @log_explain
|
140
143
|
log e.message
|
@@ -183,8 +186,12 @@ module Dexter
|
|
183
186
|
|
184
187
|
def create_candidate_indexes(candidate_indexes, index_mapping)
|
185
188
|
candidate_indexes.each do |columns|
|
186
|
-
|
187
|
-
|
189
|
+
begin
|
190
|
+
index_name = create_hypothetical_index(columns[0][:table], columns.map { |c| c[:column] })
|
191
|
+
index_mapping[index_name] = columns
|
192
|
+
rescue PG::UndefinedObject
|
193
|
+
# data type x has no default operator class for access method "btree"
|
194
|
+
end
|
188
195
|
end
|
189
196
|
rescue PG::InternalError
|
190
197
|
# hypopg: not more oid available
|
@@ -446,37 +453,6 @@ module Dexter
|
|
446
453
|
end
|
447
454
|
end
|
448
455
|
|
449
|
-
# 1. create lock
|
450
|
-
# 2. refresh existing index list
|
451
|
-
# 3. create indexes that still don't exist
|
452
|
-
# 4. release lock
|
453
|
-
def create_indexes(new_indexes)
|
454
|
-
with_advisory_lock do
|
455
|
-
new_indexes.each do |index|
|
456
|
-
unless index_exists?(index)
|
457
|
-
statement = String.new("CREATE INDEX CONCURRENTLY ON #{quote_ident(index[:table])} (#{index[:columns].map { |c| quote_ident(c) }.join(", ")})")
|
458
|
-
statement << " TABLESPACE #{quote_ident(@tablespace)}" if @tablespace
|
459
|
-
log "Creating index: #{statement}"
|
460
|
-
started_at = monotonic_time
|
461
|
-
begin
|
462
|
-
execute(statement)
|
463
|
-
log "Index created: #{((monotonic_time - started_at) * 1000).to_i} ms"
|
464
|
-
rescue PG::LockNotAvailable
|
465
|
-
log "Could not acquire lock: #{index[:table]}"
|
466
|
-
end
|
467
|
-
end
|
468
|
-
end
|
469
|
-
end
|
470
|
-
end
|
471
|
-
|
472
|
-
def monotonic_time
|
473
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
474
|
-
end
|
475
|
-
|
476
|
-
def conn
|
477
|
-
@connection.send(:conn)
|
478
|
-
end
|
479
|
-
|
480
456
|
def execute(...)
|
481
457
|
@connection.execute(...)
|
482
458
|
end
|
@@ -522,38 +498,7 @@ module Dexter
|
|
522
498
|
end
|
523
499
|
|
524
500
|
def create_hypothetical_index(table, columns)
|
525
|
-
execute("SELECT * FROM hypopg_create_index('CREATE INDEX ON #{quote_ident(table)} (#{columns.map { |c| quote_ident(c) }.join(", ")})')").first["indexname"]
|
526
|
-
end
|
527
|
-
|
528
|
-
def with_advisory_lock
|
529
|
-
lock_id = 123456
|
530
|
-
first_time = true
|
531
|
-
while execute("SELECT pg_try_advisory_lock($1)", params: [lock_id]).first["pg_try_advisory_lock"] != "t"
|
532
|
-
if first_time
|
533
|
-
log "Waiting for lock..."
|
534
|
-
first_time = false
|
535
|
-
end
|
536
|
-
sleep(1)
|
537
|
-
end
|
538
|
-
yield
|
539
|
-
ensure
|
540
|
-
suppress_messages do
|
541
|
-
execute("SELECT pg_advisory_unlock($1)", params: [lock_id])
|
542
|
-
end
|
543
|
-
end
|
544
|
-
|
545
|
-
def suppress_messages
|
546
|
-
conn.set_notice_processor do |message|
|
547
|
-
# do nothing
|
548
|
-
end
|
549
|
-
yield
|
550
|
-
ensure
|
551
|
-
# clear notice processor
|
552
|
-
conn.set_notice_processor
|
553
|
-
end
|
554
|
-
|
555
|
-
def index_exists?(index)
|
556
|
-
indexes([index[:table]]).find { |i| i["columns"] == index[:columns] }
|
501
|
+
execute("SELECT * FROM hypopg_create_index('CREATE INDEX ON #{@connection.quote_ident(table)} (#{columns.map { |c| @connection.quote_ident(c) }.join(", ")})')").first["indexname"]
|
557
502
|
end
|
558
503
|
|
559
504
|
def indexes(tables)
|
@@ -590,10 +535,6 @@ module Dexter
|
|
590
535
|
end
|
591
536
|
end
|
592
537
|
|
593
|
-
def quote_ident(value)
|
594
|
-
value.split(".").map { |v| conn.quote_ident(v) }.join(".")
|
595
|
-
end
|
596
|
-
|
597
538
|
def safe_statement(statement)
|
598
539
|
statement.gsub(";", "")
|
599
540
|
end
|
data/lib/dexter/logging.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Dexter
|
2
2
|
class StderrLogParser < LogParser
|
3
|
-
|
3
|
+
LINE_SEPARATOR = ": ".freeze
|
4
4
|
DETAIL_LINE = "DETAIL: ".freeze
|
5
5
|
|
6
6
|
def perform(collector)
|
@@ -11,7 +11,7 @@ module Dexter
|
|
11
11
|
if active_line
|
12
12
|
if line.include?(DETAIL_LINE)
|
13
13
|
add_parameters(active_line, line.chomp.split(DETAIL_LINE)[1])
|
14
|
-
elsif line.include?(
|
14
|
+
elsif line.include?(LINE_SEPARATOR)
|
15
15
|
collector.add(active_line, duration)
|
16
16
|
active_line = nil
|
17
17
|
else
|
data/lib/dexter/version.rb
CHANGED
data/lib/dexter.rb
CHANGED
@@ -15,6 +15,7 @@ require_relative "dexter/client"
|
|
15
15
|
require_relative "dexter/collector"
|
16
16
|
require_relative "dexter/column_resolver"
|
17
17
|
require_relative "dexter/connection"
|
18
|
+
require_relative "dexter/index_creator"
|
18
19
|
require_relative "dexter/indexer"
|
19
20
|
require_relative "dexter/processor"
|
20
21
|
require_relative "dexter/query"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pgdexter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
@@ -80,6 +80,7 @@ files:
|
|
80
80
|
- lib/dexter/collector.rb
|
81
81
|
- lib/dexter/column_resolver.rb
|
82
82
|
- lib/dexter/connection.rb
|
83
|
+
- lib/dexter/index_creator.rb
|
83
84
|
- lib/dexter/indexer.rb
|
84
85
|
- lib/dexter/logging.rb
|
85
86
|
- lib/dexter/parsers/csv_log_parser.rb
|
@@ -114,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
115
|
- !ruby/object:Gem::Version
|
115
116
|
version: '0'
|
116
117
|
requirements: []
|
117
|
-
rubygems_version: 3.6.
|
118
|
+
rubygems_version: 3.6.9
|
118
119
|
specification_version: 4
|
119
120
|
summary: The automatic indexer for Postgres
|
120
121
|
test_files: []
|