pgdexter 0.5.1 → 0.5.3
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 +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
|
-
[](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
|