pgdexter 0.1.4 → 0.1.5
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 +7 -0
- data/README.md +33 -5
- data/guides/Linux.md +13 -13
- data/lib/dexter/client.rb +4 -2
- data/lib/dexter/indexer.rb +74 -10
- data/lib/dexter/log_parser.rb +1 -1
- data/lib/dexter/query.rb +4 -1
- data/lib/dexter/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: fbc105f249fc27a10fcd47fa16ba9d732f0cd933
|
4
|
+
data.tar.gz: 20929a8fd1d268194b9bc2c7bab71130a2eabc43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b01d8e32db41bdf7fa0dd2412b4b490318d8574fbfba5ed0357f1aa9f3e8dcc2e9a0fbef9ee80376678c184f8840a5bf5e52fef5b325cdbffb2b31106d5c2838
|
7
|
+
data.tar.gz: 2e9808dba0e161eb184ddc180ee915f0c49097225b2539a11916eaf5dd32e9bedce8ef07763391f758d6f4627258c90e6af4f7c158b473323d398050e64a9f75
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -37,7 +37,7 @@ The command line tool is also available as a [Linux package](guides/Linux.md).
|
|
37
37
|
Dexter needs a connection to your database and a log file to process.
|
38
38
|
|
39
39
|
```sh
|
40
|
-
tail -F -n +1 <log-file> | dexter <
|
40
|
+
tail -F -n +1 <log-file> | dexter <connection-string>
|
41
41
|
```
|
42
42
|
|
43
43
|
This finds slow queries and generates output like:
|
@@ -62,12 +62,18 @@ To be safe, Dexter will not create indexes unless you pass the `--create` flag.
|
|
62
62
|
2017-06-25T17:52:37+00:00 Index created: 15243 ms
|
63
63
|
```
|
64
64
|
|
65
|
-
##
|
65
|
+
## Connection String
|
66
66
|
|
67
|
-
|
67
|
+
The connection string is a URI with the format:
|
68
68
|
|
69
|
-
```
|
70
|
-
|
69
|
+
```
|
70
|
+
postgresql://user:pass@host:5432/dbname
|
71
|
+
```
|
72
|
+
|
73
|
+
To connect through a socket, just pass the database name.
|
74
|
+
|
75
|
+
```
|
76
|
+
dbname
|
71
77
|
```
|
72
78
|
|
73
79
|
## Options
|
@@ -80,6 +86,28 @@ log-level | `debug` gives additional info for suggested indexes<br />`debug2` gi
|
|
80
86
|
log-sql | log SQL statements executed | false
|
81
87
|
min-time | only process queries consuming a min amount of DB time, in minutes | 0
|
82
88
|
|
89
|
+
## Single Statement Mode
|
90
|
+
|
91
|
+
You can pass a single statement with:
|
92
|
+
|
93
|
+
```sh
|
94
|
+
dexter <connection-string> -s "SELECT * FROM ..."
|
95
|
+
```
|
96
|
+
|
97
|
+
## Examples
|
98
|
+
|
99
|
+
Ubuntu with PostgreSQL 9.6
|
100
|
+
|
101
|
+
```sh
|
102
|
+
tail -F -n +1 /var/log/postgresql/postgresql-9.6-main.log | sudo -u postgres dexter dbname
|
103
|
+
```
|
104
|
+
|
105
|
+
Homebrew on Mac
|
106
|
+
|
107
|
+
```sh
|
108
|
+
tail -F -n +1 /usr/local/var/postgres/server.log | dexter dbname
|
109
|
+
```
|
110
|
+
|
83
111
|
## Future Work
|
84
112
|
|
85
113
|
[Here are some ideas](https://github.com/ankane/dexter/issues/1)
|
data/guides/Linux.md
CHANGED
@@ -11,8 +11,9 @@ Distributions
|
|
11
11
|
### Ubuntu 16.04 (Xenial)
|
12
12
|
|
13
13
|
```sh
|
14
|
-
wget -qO
|
15
|
-
|
14
|
+
wget -qO- https://dl.packager.io/srv/pghero/dexter/key | sudo apt-key add -
|
15
|
+
sudo wget -O /etc/apt/sources.list.d/dexter.list \
|
16
|
+
https://dl.packager.io/srv/pghero/dexter/master/installer/ubuntu/16.04.repo
|
16
17
|
sudo apt-get update
|
17
18
|
sudo apt-get -y install dexter
|
18
19
|
```
|
@@ -20,8 +21,9 @@ sudo apt-get -y install dexter
|
|
20
21
|
### Ubuntu 14.04 (Trusty)
|
21
22
|
|
22
23
|
```sh
|
23
|
-
wget -qO
|
24
|
-
|
24
|
+
wget -qO- https://dl.packager.io/srv/pghero/dexter/key | sudo apt-key add -
|
25
|
+
sudo wget -O /etc/apt/sources.list.d/dexter.list \
|
26
|
+
https://dl.packager.io/srv/pghero/dexter/master/installer/ubuntu/14.04.repo
|
25
27
|
sudo apt-get update
|
26
28
|
sudo apt-get install dexter
|
27
29
|
```
|
@@ -29,8 +31,9 @@ sudo apt-get install dexter
|
|
29
31
|
### Debian 8 (Jesse)
|
30
32
|
|
31
33
|
```sh
|
32
|
-
wget -qO
|
33
|
-
|
34
|
+
wget -qO- https://dl.packager.io/srv/pghero/dexter/key | sudo apt-key add -
|
35
|
+
sudo wget -O /etc/apt/sources.list.d/dexter.list \
|
36
|
+
https://dl.packager.io/srv/pghero/dexter/master/installer/debian/8.repo
|
34
37
|
sudo apt-get update
|
35
38
|
sudo apt-get install dexter
|
36
39
|
```
|
@@ -38,19 +41,16 @@ sudo apt-get install dexter
|
|
38
41
|
### CentOS / RHEL 7
|
39
42
|
|
40
43
|
```sh
|
41
|
-
sudo
|
42
|
-
|
43
|
-
name=Repository for pghero/dexter application.
|
44
|
-
baseurl=https://rpm.packager.io/gh/pghero/dexter/centos7/master
|
45
|
-
enabled=1" | sudo tee /etc/yum.repos.d/dexter.repo
|
44
|
+
sudo wget -O /etc/yum.repos.d/dexter.repo \
|
45
|
+
https://dl.packager.io/srv/pghero/dexter/master/installer/el/7.repo
|
46
46
|
sudo yum install dexter
|
47
47
|
```
|
48
48
|
|
49
49
|
### SUSE Linux Enterprise Server 12
|
50
50
|
|
51
51
|
```sh
|
52
|
-
sudo
|
53
|
-
|
52
|
+
sudo wget -O /etc/zypp/repos.d/dexter.repo \
|
53
|
+
https://dl.packager.io/srv/pghero/dexter/master/installer/sles/12.repo
|
54
54
|
sudo zypper install dexter
|
55
55
|
```
|
56
56
|
|
data/lib/dexter/client.rb
CHANGED
@@ -11,9 +11,10 @@ module Dexter
|
|
11
11
|
STDERR.sync = true
|
12
12
|
|
13
13
|
if options[:statement]
|
14
|
-
|
15
|
-
query = Query.new(options[:statement], fingerprint)
|
14
|
+
query = Query.new(options[:statement])
|
16
15
|
Indexer.new(arguments[0], options).process_queries([query])
|
16
|
+
elsif options[:pg_stat_statements]
|
17
|
+
Indexer.new(arguments[0], options).process_stat_statements
|
17
18
|
elsif arguments[1]
|
18
19
|
Processor.new(arguments[0], arguments[1], options).perform
|
19
20
|
else
|
@@ -31,6 +32,7 @@ Options:)
|
|
31
32
|
o.array "--exclude", "prevent specific tables from being indexed"
|
32
33
|
o.integer "--interval", "time to wait between processing queries, in seconds", default: 60
|
33
34
|
o.float "--min-time", "only process queries that have consumed a certain amount of DB time, in minutes", default: 0
|
35
|
+
o.boolean "--pg-stat-statements", "use pg_stat_statements", default: false, help: false
|
34
36
|
o.boolean "--log-explain", "log explain", default: false, help: false
|
35
37
|
o.string "--log-level", "log level", default: "info"
|
36
38
|
o.boolean "--log-sql", "log sql", default: false
|
data/lib/dexter/indexer.rb
CHANGED
@@ -9,8 +9,15 @@ module Dexter
|
|
9
9
|
@exclude_tables = options[:exclude]
|
10
10
|
@log_sql = options[:log_sql]
|
11
11
|
@log_explain = options[:log_explain]
|
12
|
+
@min_time = options[:min_time] || 0
|
12
13
|
|
13
|
-
create_extension
|
14
|
+
create_extension unless extension_exists?
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_stat_statements
|
18
|
+
queries = stat_statements.map { |q| Query.new(q) }.sort_by(&:fingerprint).group_by(&:fingerprint).map { |_, v| v.first }
|
19
|
+
log "Processing #{queries.size} new query fingerprints"
|
20
|
+
process_queries(queries)
|
14
21
|
end
|
15
22
|
|
16
23
|
def process_queries(queries)
|
@@ -46,7 +53,15 @@ module Dexter
|
|
46
53
|
|
47
54
|
def create_extension
|
48
55
|
execute("SET client_min_messages = warning")
|
49
|
-
|
56
|
+
begin
|
57
|
+
execute("CREATE EXTENSION IF NOT EXISTS hypopg")
|
58
|
+
rescue PG::InsufficientPrivilege
|
59
|
+
abort "Use a superuser to run: CREATE EXTENSION hypopg"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def extension_exists?
|
64
|
+
execute("SELECT * FROM pg_available_extensions WHERE name = 'hypopg' AND installed_version IS NOT NULL").any?
|
50
65
|
end
|
51
66
|
|
52
67
|
def reset_hypothetical_indexes
|
@@ -208,17 +223,20 @@ module Dexter
|
|
208
223
|
end
|
209
224
|
|
210
225
|
if @create
|
211
|
-
# TODO use advisory locks
|
212
226
|
# 1. create lock
|
213
227
|
# 2. refresh existing index list
|
214
228
|
# 3. create indexes that still don't exist
|
215
229
|
# 4. release lock
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
230
|
+
with_advisory_lock do
|
231
|
+
new_indexes.each do |index|
|
232
|
+
unless index_exists?(index)
|
233
|
+
statement = "CREATE INDEX CONCURRENTLY ON #{quote_ident(index[:table])} (#{index[:columns].map { |c| quote_ident(c) }.join(", ")})"
|
234
|
+
log "Creating index: #{statement}"
|
235
|
+
started_at = Time.now
|
236
|
+
execute(statement)
|
237
|
+
log "Index created: #{((Time.now - started_at) * 1000).to_i} ms"
|
238
|
+
end
|
239
|
+
end
|
222
240
|
end
|
223
241
|
end
|
224
242
|
else
|
@@ -242,7 +260,7 @@ module Dexter
|
|
242
260
|
PG::Connection.new(config)
|
243
261
|
end
|
244
262
|
rescue PG::ConnectionBad
|
245
|
-
abort "
|
263
|
+
abort "Can't connect to database"
|
246
264
|
end
|
247
265
|
|
248
266
|
def execute(query)
|
@@ -284,14 +302,60 @@ module Dexter
|
|
284
302
|
WHERE
|
285
303
|
table_catalog = current_database() AND
|
286
304
|
table_schema NOT IN ('pg_catalog', 'information_schema')
|
305
|
+
AND table_type = 'BASE TABLE'
|
287
306
|
SQL
|
288
307
|
result.map { |r| r["table_name"] }
|
289
308
|
end
|
290
309
|
|
310
|
+
def stat_statements
|
311
|
+
result = execute <<-SQL
|
312
|
+
SELECT
|
313
|
+
DISTINCT query
|
314
|
+
FROM
|
315
|
+
pg_stat_statements
|
316
|
+
INNER JOIN
|
317
|
+
pg_database ON pg_database.oid = pg_stat_statements.dbid
|
318
|
+
WHERE
|
319
|
+
datname = current_database()
|
320
|
+
AND total_time >= #{@min_time * 60000}
|
321
|
+
ORDER BY
|
322
|
+
1
|
323
|
+
SQL
|
324
|
+
result.map { |q| q["query"] }
|
325
|
+
end
|
326
|
+
|
291
327
|
def possible_tables(queries)
|
292
328
|
Set.new(queries.flat_map(&:tables).uniq & database_tables)
|
293
329
|
end
|
294
330
|
|
331
|
+
def with_advisory_lock
|
332
|
+
lock_id = 123456
|
333
|
+
first_time = true
|
334
|
+
while execute("SELECT pg_try_advisory_lock(#{lock_id})").first["pg_try_advisory_lock"] != "t"
|
335
|
+
if first_time
|
336
|
+
log "Waiting for lock..."
|
337
|
+
first_time = false
|
338
|
+
end
|
339
|
+
sleep(1)
|
340
|
+
end
|
341
|
+
yield
|
342
|
+
ensure
|
343
|
+
with_min_messages("error") do
|
344
|
+
execute("SELECT pg_advisory_unlock(#{lock_id})")
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def with_min_messages(value)
|
349
|
+
execute("SET client_min_messages = #{quote(value)}")
|
350
|
+
yield
|
351
|
+
ensure
|
352
|
+
execute("SET client_min_messages = warning")
|
353
|
+
end
|
354
|
+
|
355
|
+
def index_exists?(index)
|
356
|
+
indexes([index[:table]]).find { |i| i["columns"] == index[:columns] }
|
357
|
+
end
|
358
|
+
|
295
359
|
def columns(tables)
|
296
360
|
columns = execute <<-SQL
|
297
361
|
SELECT
|
data/lib/dexter/log_parser.rb
CHANGED
data/lib/dexter/query.rb
CHANGED
@@ -3,8 +3,11 @@ module Dexter
|
|
3
3
|
attr_reader :statement, :fingerprint, :plans
|
4
4
|
attr_accessor :missing_tables, :new_cost
|
5
5
|
|
6
|
-
def initialize(statement, fingerprint)
|
6
|
+
def initialize(statement, fingerprint = nil)
|
7
7
|
@statement = statement
|
8
|
+
unless fingerprint
|
9
|
+
fingerprint = PgQuery.fingerprint(statement) rescue "unknown"
|
10
|
+
end
|
8
11
|
@fingerprint = fingerprint
|
9
12
|
@plans = []
|
10
13
|
end
|
data/lib/dexter/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pgdexter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: slop
|