pgtk 0.18.2 → 0.19.0
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/Gemfile.lock +5 -5
- data/lib/pgtk/stash.rb +111 -15
- data/lib/pgtk/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1cab54d9805f427ff6187a6cdec4fa2cd5424a0c4959191169e674eb2400a09a
|
|
4
|
+
data.tar.gz: 2d584fffabf7b943fc087d9bad1edb68296826b5b987e34725d5a252503f9f46
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6f3b1bd6f15acd85f50194b1aa9653f1b892b0dea6501dcacaa1c2cc38ccd3f609331fa3cccc7a3c79f86285861b28e77e87d6c84d4f7f9b81f272e938fed216
|
|
7
|
+
data.tar.gz: d583bfab4f9647f08235891899ab938dd4d778d49115c6a4bee4a72298810364fd419cdb271e145610001e8742f432ade4a6ed212b7bc74c62ce89caf0cc8f27
|
data/Gemfile.lock
CHANGED
|
@@ -26,7 +26,7 @@ GEM
|
|
|
26
26
|
loog (~> 0.6)
|
|
27
27
|
tago (~> 0.1)
|
|
28
28
|
joined (0.4.0)
|
|
29
|
-
json (2.15.
|
|
29
|
+
json (2.15.2)
|
|
30
30
|
language_server-protocol (3.17.0.5)
|
|
31
31
|
lint_roller (1.1.0)
|
|
32
32
|
logger (1.7.0)
|
|
@@ -45,7 +45,7 @@ GEM
|
|
|
45
45
|
nokogiri (1.18.10-x86_64-linux-gnu)
|
|
46
46
|
racc (~> 1.4)
|
|
47
47
|
parallel (1.27.0)
|
|
48
|
-
parser (3.3.
|
|
48
|
+
parser (3.3.10.0)
|
|
49
49
|
ast (~> 2.4.1)
|
|
50
50
|
racc
|
|
51
51
|
pg (1.6.2-arm64-darwin)
|
|
@@ -60,12 +60,12 @@ GEM
|
|
|
60
60
|
racc (1.8.1)
|
|
61
61
|
rack (3.2.3)
|
|
62
62
|
rainbow (3.1.1)
|
|
63
|
-
rake (13.3.
|
|
63
|
+
rake (13.3.1)
|
|
64
64
|
random-port (0.7.6)
|
|
65
65
|
tago (~> 0.0)
|
|
66
66
|
regexp_parser (2.11.3)
|
|
67
67
|
rexml (3.4.4)
|
|
68
|
-
rubocop (1.81.
|
|
68
|
+
rubocop (1.81.7)
|
|
69
69
|
json (~> 2.3)
|
|
70
70
|
language_server-protocol (~> 3.17.0.2)
|
|
71
71
|
lint_roller (~> 1.1.0)
|
|
@@ -102,7 +102,7 @@ GEM
|
|
|
102
102
|
simplecov_json_formatter (0.1.4)
|
|
103
103
|
slop (4.10.1)
|
|
104
104
|
tago (0.3.0)
|
|
105
|
-
timeout (0.4.
|
|
105
|
+
timeout (0.4.4)
|
|
106
106
|
unicode-display_width (3.2.0)
|
|
107
107
|
unicode-emoji (~> 4.1)
|
|
108
108
|
unicode-emoji (4.1.0)
|
data/lib/pgtk/stash.rb
CHANGED
|
@@ -22,17 +22,46 @@ require_relative '../pgtk'
|
|
|
22
22
|
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
23
23
|
# License:: MIT
|
|
24
24
|
class Pgtk::Stash
|
|
25
|
+
MODS = %w[INSERT DELETE UPDATE LOCK VACUUM TRANSACTION COMMIT ROLLBACK REINDEX TRUNCATE CREATE ALTER DROP SET].freeze
|
|
26
|
+
MODS_RE = Regexp.new("(^|\\s)(#{MODS.join('|')})(\\s|$)")
|
|
27
|
+
|
|
28
|
+
ALTS = ['UPDATE', 'INSERT INTO', 'DELETE FROM', 'TRUNCATE', 'ALTER TABLE', 'DROP TABLE'].freeze
|
|
29
|
+
ALTS_RE = Regexp.new("(?<=^|\\s)(?:#{ALTS.join('|')})\\s([a-z]+)(?=[^a-z]|$)")
|
|
30
|
+
|
|
31
|
+
private_constant :MODS, :ALTS, :MODS_RE, :ALTS_RE
|
|
32
|
+
|
|
25
33
|
# Initialize a new Stash with query caching.
|
|
26
34
|
#
|
|
27
35
|
# @param [Object] pgsql PostgreSQL connection object
|
|
28
36
|
# @param [Hash] stash Optional existing stash to use (default: new empty stash)
|
|
37
|
+
# @option [Hash] queries Internal cache data (default: {})
|
|
38
|
+
# @option [Hash] tables Internal cache data (default: {})
|
|
39
|
+
# @option [Concurrent::ReentrantReadWriteLock] entrance Lock for write internal state
|
|
40
|
+
# @option [Concurrent::AtomicBoolean] start_refresher Latch for start timers once
|
|
41
|
+
# @param [Integer] refresh Interval in seconds for recalculate stale queries
|
|
42
|
+
# @param [Integer] top Number of queries to recalculate
|
|
43
|
+
# @param [Integer] threads Number of threads in threadpool
|
|
29
44
|
# @param [Loog] loog Logger for debugging (default: null logger)
|
|
30
|
-
def initialize(
|
|
45
|
+
def initialize(
|
|
46
|
+
pgsql,
|
|
47
|
+
stash = {
|
|
48
|
+
queries: {},
|
|
49
|
+
tables: {},
|
|
50
|
+
entrance: Concurrent::ReentrantReadWriteLock.new,
|
|
51
|
+
start_refresher: Concurrent::AtomicBoolean.new(false)
|
|
52
|
+
},
|
|
53
|
+
refresh: 5,
|
|
54
|
+
top: 100,
|
|
55
|
+
threads: 5,
|
|
56
|
+
loog: Loog::NULL
|
|
57
|
+
)
|
|
31
58
|
@pgsql = pgsql
|
|
32
59
|
@stash = stash
|
|
33
|
-
@stash[:
|
|
34
|
-
@
|
|
35
|
-
@
|
|
60
|
+
@entrance = stash[:entrance]
|
|
61
|
+
@refresh = refresh
|
|
62
|
+
@top = top
|
|
63
|
+
@threads = threads
|
|
64
|
+
@loog = loog
|
|
36
65
|
end
|
|
37
66
|
|
|
38
67
|
# Execute a SQL query with optional caching.
|
|
@@ -44,30 +73,29 @@ class Pgtk::Stash
|
|
|
44
73
|
# @param [Integer] result Should be 0 for text results, 1 for binary
|
|
45
74
|
# @return [PG::Result] Query result
|
|
46
75
|
def exec(query, params = [], result = 0)
|
|
47
|
-
mods = %w[INSERT DELETE UPDATE LOCK VACUUM TRANSACTION COMMIT ROLLBACK REINDEX TRUNCATE CREATE ALTER DROP SET]
|
|
48
|
-
alts = ['UPDATE', 'INSERT INTO', 'DELETE FROM', 'TRUNCATE', 'ALTER TABLE', 'DROP TABLE']
|
|
49
76
|
pure = (query.is_a?(Array) ? query.join(' ') : query).gsub(/\s+/, ' ').strip
|
|
50
|
-
if
|
|
51
|
-
tables = pure.scan(
|
|
77
|
+
if MODS_RE.match?(pure) || /(^|\s)pg_[a-z_]+\(/.match?(pure)
|
|
78
|
+
tables = pure.scan(ALTS_RE).map(&:first).uniq
|
|
52
79
|
ret = @pgsql.exec(pure, params, result)
|
|
53
80
|
@entrance.with_write_lock do
|
|
54
81
|
tables.each do |t|
|
|
55
82
|
@stash[:tables][t]&.each do |q|
|
|
56
|
-
@stash[:queries].
|
|
83
|
+
@stash[:queries][q].each_key do |key|
|
|
84
|
+
@stash[:queries][q][key]['stale'] = true
|
|
85
|
+
end
|
|
57
86
|
end
|
|
58
|
-
@stash[:tables].delete(t)
|
|
59
87
|
end
|
|
60
88
|
end
|
|
61
89
|
else
|
|
62
90
|
key = params.map(&:to_s).join(' -*&%^- ')
|
|
63
91
|
@entrance.with_write_lock { @stash[:queries][pure] ||= {} }
|
|
64
|
-
ret = @stash
|
|
65
|
-
if ret.nil?
|
|
92
|
+
ret = @stash.dig(:queries, pure, key, 'ret')
|
|
93
|
+
if ret.nil? || @stash.dig(:queries, pure, key, 'stale')
|
|
66
94
|
ret = @pgsql.exec(pure, params, result)
|
|
67
95
|
unless pure.include?(' NOW() ')
|
|
68
96
|
@entrance.with_write_lock do
|
|
69
97
|
@stash[:queries][pure] ||= {}
|
|
70
|
-
@stash[:queries][pure][key] = ret
|
|
98
|
+
@stash[:queries][pure][key] = { 'ret' => ret, 'params' => params, 'result' => result }
|
|
71
99
|
tables = pure.scan(/(?<=^|\s)(?:FROM|JOIN) ([a-z_]+)(?=\s|$)/).map(&:first).uniq
|
|
72
100
|
tables.each do |t|
|
|
73
101
|
@stash[:tables][t] = [] if @stash[:tables][t].nil?
|
|
@@ -77,6 +105,7 @@ class Pgtk::Stash
|
|
|
77
105
|
end
|
|
78
106
|
end
|
|
79
107
|
end
|
|
108
|
+
count(pure, key)
|
|
80
109
|
end
|
|
81
110
|
ret
|
|
82
111
|
end
|
|
@@ -89,7 +118,13 @@ class Pgtk::Stash
|
|
|
89
118
|
# @return [Object] The result of the block
|
|
90
119
|
def transaction
|
|
91
120
|
@pgsql.transaction do |t|
|
|
92
|
-
yield Pgtk::Stash.new(
|
|
121
|
+
yield Pgtk::Stash.new(
|
|
122
|
+
t, @stash,
|
|
123
|
+
refresh: @refresh,
|
|
124
|
+
top: @top,
|
|
125
|
+
threads: @threads,
|
|
126
|
+
loog: @loog
|
|
127
|
+
)
|
|
93
128
|
end
|
|
94
129
|
end
|
|
95
130
|
|
|
@@ -98,7 +133,14 @@ class Pgtk::Stash
|
|
|
98
133
|
# @param args Arguments to pass to the underlying pool's start method
|
|
99
134
|
# @return [Pgtk::Stash] A new stash that shares the same cache
|
|
100
135
|
def start(*args)
|
|
101
|
-
|
|
136
|
+
start_refresher
|
|
137
|
+
Pgtk::Stash.new(
|
|
138
|
+
@pgsql.start(*args), @stash,
|
|
139
|
+
refresh: @refresh,
|
|
140
|
+
top: @top,
|
|
141
|
+
threads: @threads,
|
|
142
|
+
loog: @loog
|
|
143
|
+
)
|
|
102
144
|
end
|
|
103
145
|
|
|
104
146
|
# Get the PostgreSQL server version.
|
|
@@ -107,4 +149,58 @@ class Pgtk::Stash
|
|
|
107
149
|
def version
|
|
108
150
|
@pgsql.version
|
|
109
151
|
end
|
|
152
|
+
|
|
153
|
+
# Get statistics on the most used queries
|
|
154
|
+
#
|
|
155
|
+
# @return [Array<Array<String, Integer>>] Array of query and hits in desc hits order
|
|
156
|
+
def stats
|
|
157
|
+
@stash[:queries].map { |k, v| [k.dup, v.values.sum { |vv| vv['count'] }] }.sort_by { -_1[1] }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
def count(query, key)
|
|
163
|
+
@entrance.with_write_lock do
|
|
164
|
+
@stash[:queries][query][key]['count'] ||= 0
|
|
165
|
+
@stash[:queries][query][key]['count'] += 1
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def start_refresher
|
|
170
|
+
raise 'Cannot start cache refresh multiple times on same cache data' unless @stash[:start_refresher].make_true
|
|
171
|
+
Concurrent::FixedThreadPool.new(@threads).then do |threadpool|
|
|
172
|
+
Concurrent::TimerTask.execute(execution_interval: 24 * 60 * 60, executor: threadpool) do
|
|
173
|
+
@entrance.with_write_lock do
|
|
174
|
+
@stash[:queries].each_key do |q|
|
|
175
|
+
@stash[:queries][q].each_key do |k|
|
|
176
|
+
@stash[:queries][q][k]['count'] = 0
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
Concurrent::TimerTask.execute(execution_interval: @refresh, executor: threadpool) do
|
|
182
|
+
@stash[:queries]
|
|
183
|
+
.map { |k, v| [k, v.values.sum { |vv| vv['count'] }, v.values.any? { |vv| vv['stale'] }] }
|
|
184
|
+
.select { _1[2] }
|
|
185
|
+
.sort_by { -_1[1] }
|
|
186
|
+
.first(@top)
|
|
187
|
+
.each do |a|
|
|
188
|
+
q = a[0]
|
|
189
|
+
@stash[:queries][q].each_key do |k|
|
|
190
|
+
next unless @stash[:queries][q][k]['stale']
|
|
191
|
+
threadpool.post do
|
|
192
|
+
params = @stash[:queries][q][k]['params']
|
|
193
|
+
result = @stash[:queries][q][k]['result']
|
|
194
|
+
ret = @pgsql.exec(q, params, result)
|
|
195
|
+
@entrance.with_write_lock do
|
|
196
|
+
@stash[:queries][q] ||= {}
|
|
197
|
+
@stash[:queries][q][k] = { 'ret' => ret, 'params' => params, 'result' => result, 'count' => 1 }
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
nil
|
|
205
|
+
end
|
|
110
206
|
end
|
data/lib/pgtk/version.rb
CHANGED