pgdexter 0.5.1 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/LICENSE.txt +1 -1
- data/README.md +1 -1
- data/lib/dexter/indexer.rb +34 -17
- data/lib/dexter/query.rb +2 -2
- data/lib/dexter/version.rb +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d40fb941685400a6a158502895da0d8b93d94db03da0ca4551714aea36e3dae3
|
4
|
+
data.tar.gz: 8b99de845f8d6f2e455e98c3505e689a0a55dac69f7c08ba6ebebabeeb1ddd63
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2b25419796e276074f5a49b2d109be72789ddf8a5c7c5cf29ba55900ac79c323e55b06bf5ab20dde4d34e4cf1e752e7362d23481d21d19a8b068a360562d13f
|
7
|
+
data.tar.gz: 382792e95461d718489d36c71e1ba01940f4b1d76de1b80527f0a4a19fcc90c215366645640970271011d5ad2c264051be57e62684e3278248d52bfaae983cd2
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## 0.5.3 (2024-03-05)
|
2
|
+
|
3
|
+
- Fixed error with hypothetical index limit
|
4
|
+
- Fixed error with foreign tables
|
5
|
+
|
6
|
+
## 0.5.2 (2024-01-10)
|
7
|
+
|
8
|
+
- Added Docker image for `linux/arm64`
|
9
|
+
- Switched to `GENERIC_PLAN` for Postgres 16
|
10
|
+
- Fixed error with `auto_explain`
|
11
|
+
- Fixed warning with Ruby 3.3
|
12
|
+
|
1
13
|
## 0.5.1 (2023-05-27)
|
2
14
|
|
3
15
|
- Fixed `JSON::NestingError`
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -4,7 +4,7 @@ The automatic indexer for Postgres
|
|
4
4
|
|
5
5
|
[Read about how it works](https://ankane.org/introducing-dexter) or [watch the talk](https://www.youtube.com/watch?v=Mni_1yTaNbE)
|
6
6
|
|
7
|
-
[![Build Status](https://github.com/ankane/dexter/workflows/build/badge.svg
|
7
|
+
[![Build Status](https://github.com/ankane/dexter/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/dexter/actions)
|
8
8
|
|
9
9
|
## Installation
|
10
10
|
|
data/lib/dexter/indexer.rb
CHANGED
@@ -96,10 +96,16 @@ module Dexter
|
|
96
96
|
analyze_tables(tables) if tables.any? && (@analyze || @log_level == "debug2")
|
97
97
|
|
98
98
|
# create hypothetical indexes and explain queries
|
99
|
-
|
99
|
+
if tables.any?
|
100
|
+
# process in batches to prevent "hypopg: not more oid available" error
|
101
|
+
# https://hypopg.readthedocs.io/en/rel1_stable/usage.html#configuration
|
102
|
+
queries.select(&:candidate_tables).each_slice(500) do |batch|
|
103
|
+
create_hypothetical_indexes(batch)
|
104
|
+
end
|
105
|
+
end
|
100
106
|
|
101
107
|
# see if new indexes were used and meet bar
|
102
|
-
new_indexes = determine_indexes(queries,
|
108
|
+
new_indexes = determine_indexes(queries, tables)
|
103
109
|
|
104
110
|
# display and create new indexes
|
105
111
|
show_and_create_indexes(new_indexes, queries)
|
@@ -185,7 +191,7 @@ module Dexter
|
|
185
191
|
|
186
192
|
# get initial costs for queries
|
187
193
|
calculate_plan(queries)
|
188
|
-
explainable_queries = queries.select { |q| q.
|
194
|
+
explainable_queries = queries.select { |q| q.plans.any? && q.high_cost? }
|
189
195
|
|
190
196
|
# filter tables for performance
|
191
197
|
tables = Set.new(explainable_queries.flat_map(&:tables))
|
@@ -228,7 +234,9 @@ module Dexter
|
|
228
234
|
calculate_plan(explainable_queries)
|
229
235
|
end
|
230
236
|
|
231
|
-
|
237
|
+
queries.each do |query|
|
238
|
+
query.candidates = candidates
|
239
|
+
end
|
232
240
|
end
|
233
241
|
|
234
242
|
def find_columns(plan)
|
@@ -282,9 +290,8 @@ module Dexter
|
|
282
290
|
query_indexes
|
283
291
|
end
|
284
292
|
|
285
|
-
def determine_indexes(queries,
|
293
|
+
def determine_indexes(queries, tables)
|
286
294
|
new_indexes = {}
|
287
|
-
index_name_to_columns = candidates.invert
|
288
295
|
|
289
296
|
# filter out existing indexes
|
290
297
|
# this must happen at end of process
|
@@ -313,11 +320,11 @@ module Dexter
|
|
313
320
|
cost_savings2 = new_cost > 100 && new_cost2 < new_cost * savings_ratio
|
314
321
|
|
315
322
|
key = cost_savings2 ? 2 : 1
|
316
|
-
query_indexes = hypo_indexes_from_plan(
|
323
|
+
query_indexes = hypo_indexes_from_plan(query.candidates, query.plans[key], index_set)
|
317
324
|
|
318
325
|
# likely a bad suggestion, so try single column
|
319
326
|
if cost_savings2 && query_indexes.size > 1
|
320
|
-
query_indexes = hypo_indexes_from_plan(
|
327
|
+
query_indexes = hypo_indexes_from_plan(query.candidates, query.plans[1], index_set)
|
321
328
|
cost_savings2 = false
|
322
329
|
end
|
323
330
|
|
@@ -390,8 +397,8 @@ module Dexter
|
|
390
397
|
|
391
398
|
# TODO optimize
|
392
399
|
if @log_level.start_with?("debug")
|
393
|
-
query.pass1_indexes = hypo_indexes_from_plan(
|
394
|
-
query.pass2_indexes = hypo_indexes_from_plan(
|
400
|
+
query.pass1_indexes = hypo_indexes_from_plan(query.candidates, query.plans[1], index_set)
|
401
|
+
query.pass2_indexes = hypo_indexes_from_plan(query.candidates, query.plans[2], index_set)
|
395
402
|
end
|
396
403
|
end
|
397
404
|
end
|
@@ -521,8 +528,8 @@ module Dexter
|
|
521
528
|
raise Dexter::Abort, e.message
|
522
529
|
end
|
523
530
|
|
524
|
-
def execute(query, pretty: true, params: [])
|
525
|
-
# use exec_params instead of exec for security
|
531
|
+
def execute(query, pretty: true, params: [], use_exec: false)
|
532
|
+
# use exec_params instead of exec when possible for security
|
526
533
|
#
|
527
534
|
# Unlike PQexec, PQexecParams allows at most one SQL command in the given string.
|
528
535
|
# (There can be semicolons in it, but not more than one nonempty command.)
|
@@ -533,7 +540,11 @@ module Dexter
|
|
533
540
|
log colorize("[sql] #{query}#{params.any? ? " /*#{params.to_json}*/" : ""}", :cyan) if @log_sql
|
534
541
|
|
535
542
|
@mutex.synchronize do
|
536
|
-
|
543
|
+
if use_exec
|
544
|
+
conn.exec("#{query} /*dexter*/").to_a
|
545
|
+
else
|
546
|
+
conn.exec_params("#{query} /*dexter*/", params).to_a
|
547
|
+
end
|
537
548
|
end
|
538
549
|
end
|
539
550
|
|
@@ -543,7 +554,9 @@ module Dexter
|
|
543
554
|
|
544
555
|
# try to EXPLAIN normalized queries
|
545
556
|
# https://dev.to/yugabyte/explain-from-pgstatstatements-normalized-queries-how-to-always-get-the-generic-plan-in--5cfi
|
546
|
-
|
557
|
+
normalized = query.include?("$1")
|
558
|
+
generic_plan = normalized && server_version_num >= 160000
|
559
|
+
explain_normalized = normalized && !generic_plan
|
547
560
|
if explain_normalized
|
548
561
|
prepared_name = "dexter_prepared"
|
549
562
|
execute("PREPARE #{prepared_name} AS #{safe_statement(query)}", pretty: false)
|
@@ -566,12 +579,14 @@ module Dexter
|
|
566
579
|
end
|
567
580
|
end
|
568
581
|
|
582
|
+
explain_prefix = generic_plan ? "GENERIC_PLAN, " : ""
|
583
|
+
|
569
584
|
# strip semi-colons as another measure of defense
|
570
|
-
plan = JSON.parse(execute("EXPLAIN (FORMAT JSON) #{safe_statement(query)}", pretty: false).first["QUERY PLAN"], max_nesting: 1000).first["Plan"]
|
585
|
+
plan = JSON.parse(execute("EXPLAIN (#{explain_prefix}FORMAT JSON) #{safe_statement(query)}", pretty: false, use_exec: generic_plan).first["QUERY PLAN"], max_nesting: 1000).first["Plan"]
|
571
586
|
|
572
587
|
if @log_explain
|
573
588
|
# Pass format to prevent ANALYZE
|
574
|
-
puts execute("EXPLAIN (FORMAT TEXT) #{safe_statement(query)}", pretty: false).map { |r| r["QUERY PLAN"] }.join("\n")
|
589
|
+
puts execute("EXPLAIN (#{explain_prefix}FORMAT TEXT) #{safe_statement(query)}", pretty: false, use_exec: generic_plan).map { |r| r["QUERY PLAN"] }.join("\n")
|
575
590
|
end
|
576
591
|
|
577
592
|
plan
|
@@ -587,7 +602,8 @@ module Dexter
|
|
587
602
|
columns_by_table.each do |table, cols|
|
588
603
|
# no reason to use btree index for json columns
|
589
604
|
cols.reject { |c| ["json", "jsonb"].include?(c[:type]) }.permutation(n) do |col_set|
|
590
|
-
|
605
|
+
index_name = create_hypothetical_index(table, col_set)
|
606
|
+
candidates[index_name] = col_set
|
591
607
|
end
|
592
608
|
end
|
593
609
|
end
|
@@ -604,6 +620,7 @@ module Dexter
|
|
604
620
|
information_schema.tables
|
605
621
|
WHERE
|
606
622
|
table_catalog = current_database()
|
623
|
+
AND table_type IN ('BASE TABLE', 'VIEW')
|
607
624
|
SQL
|
608
625
|
result.map { |r| r["table_name"] }
|
609
626
|
end
|
data/lib/dexter/query.rb
CHANGED
@@ -2,7 +2,7 @@ module Dexter
|
|
2
2
|
class Query
|
3
3
|
attr_reader :statement, :fingerprint, :plans
|
4
4
|
attr_writer :tables
|
5
|
-
attr_accessor :missing_tables, :new_cost, :total_time, :calls, :indexes, :suggest_index, :pass1_indexes, :pass2_indexes, :pass3_indexes, :candidate_tables, :tables_from_views
|
5
|
+
attr_accessor :missing_tables, :new_cost, :total_time, :calls, :indexes, :suggest_index, :pass1_indexes, :pass2_indexes, :pass3_indexes, :candidate_tables, :tables_from_views, :candidates
|
6
6
|
|
7
7
|
def initialize(statement, fingerprint = nil)
|
8
8
|
@statement = statement
|
@@ -31,7 +31,7 @@ module Dexter
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def explainable?
|
34
|
-
plans.
|
34
|
+
plans.size >= 3
|
35
35
|
end
|
36
36
|
|
37
37
|
def costs
|
data/lib/dexter/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pgdexter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-03-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: csv
|
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'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: pg
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -96,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
110
|
- !ruby/object:Gem::Version
|
97
111
|
version: '0'
|
98
112
|
requirements: []
|
99
|
-
rubygems_version: 3.
|
113
|
+
rubygems_version: 3.5.3
|
100
114
|
signing_key:
|
101
115
|
specification_version: 4
|
102
116
|
summary: The automatic indexer for Postgres
|