pgtk 0.32.5 → 0.32.6
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 +15 -14
- data/lib/pgtk/impatient.rb +11 -3
- data/lib/pgtk/pgsql_task.rb +18 -1
- data/lib/pgtk/pool.rb +23 -15
- data/lib/pgtk/spy.rb +5 -3
- data/lib/pgtk/stash.rb +58 -37
- data/lib/pgtk/version.rb +1 -1
- data/lib/pgtk/wire/env.rb +1 -1
- data/lib/pgtk/wire/yaml.rb +7 -2
- data/pgtk.gemspec +1 -1
- data/resources/pom.xml +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bff4002fc7f88a973b99757aa0b8be3b82b6631b87f94c8ade93b30983b21761
|
|
4
|
+
data.tar.gz: 707d5ec1bf05047d60a0f25103b7210751c4731f79caa8b08c3021e7e3810e6c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 210230feda441fdee55cb3c3b6ccf1e2335efcdef77e95996afe89d3a4caf4f21bd8aa4fb9fd19f9b636d1336cc821345aa7454ec474d1693dff21990f2a1b40
|
|
7
|
+
data.tar.gz: ab98c8f9cf29405c495bd4808855e1107889ccdc2ee9011f4abe431b561f05d033564dc587f5a3084f64f0842e935d4bee6b9c8265cc729fe762a0f99bab4eb9
|
data/Gemfile.lock
CHANGED
|
@@ -2,7 +2,7 @@ PATH
|
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
4
|
pgtk (0.0.0)
|
|
5
|
-
|
|
5
|
+
cgi (~> 0.0)
|
|
6
6
|
concurrent-ruby (~> 1.3)
|
|
7
7
|
donce (~> 0.0)
|
|
8
8
|
ellipsized (~> 0.3)
|
|
@@ -22,7 +22,8 @@ GEM
|
|
|
22
22
|
ast (2.4.3)
|
|
23
23
|
backtrace (0.4.1)
|
|
24
24
|
builder (3.3.0)
|
|
25
|
-
|
|
25
|
+
cgi (0.5.1)
|
|
26
|
+
concurrent-ruby (1.3.7)
|
|
26
27
|
differ (0.1.2)
|
|
27
28
|
docile (1.4.1)
|
|
28
29
|
donce (0.7.0)
|
|
@@ -35,7 +36,7 @@ GEM
|
|
|
35
36
|
tago (~> 0.1)
|
|
36
37
|
ellipsized (0.3.0)
|
|
37
38
|
joined (0.4.0)
|
|
38
|
-
json (2.19.
|
|
39
|
+
json (2.19.9)
|
|
39
40
|
language_server-protocol (3.17.0.5)
|
|
40
41
|
lint_roller (1.1.0)
|
|
41
42
|
logger (1.7.0)
|
|
@@ -52,21 +53,21 @@ GEM
|
|
|
52
53
|
minitest (>= 5.0, < 7)
|
|
53
54
|
ruby-progressbar
|
|
54
55
|
minitest-stub-const (0.6)
|
|
55
|
-
nokogiri (1.19.
|
|
56
|
+
nokogiri (1.19.4-aarch64-linux-gnu)
|
|
56
57
|
racc (~> 1.4)
|
|
57
|
-
nokogiri (1.19.
|
|
58
|
+
nokogiri (1.19.4-aarch64-linux-musl)
|
|
58
59
|
racc (~> 1.4)
|
|
59
|
-
nokogiri (1.19.
|
|
60
|
+
nokogiri (1.19.4-arm-linux-gnu)
|
|
60
61
|
racc (~> 1.4)
|
|
61
|
-
nokogiri (1.19.
|
|
62
|
+
nokogiri (1.19.4-arm-linux-musl)
|
|
62
63
|
racc (~> 1.4)
|
|
63
|
-
nokogiri (1.19.
|
|
64
|
+
nokogiri (1.19.4-arm64-darwin)
|
|
64
65
|
racc (~> 1.4)
|
|
65
|
-
nokogiri (1.19.
|
|
66
|
+
nokogiri (1.19.4-x86_64-darwin)
|
|
66
67
|
racc (~> 1.4)
|
|
67
|
-
nokogiri (1.19.
|
|
68
|
+
nokogiri (1.19.4-x86_64-linux-gnu)
|
|
68
69
|
racc (~> 1.4)
|
|
69
|
-
nokogiri (1.19.
|
|
70
|
+
nokogiri (1.19.4-x86_64-linux-musl)
|
|
70
71
|
racc (~> 1.4)
|
|
71
72
|
os (1.1.4)
|
|
72
73
|
parallel (2.1.0)
|
|
@@ -94,7 +95,7 @@ GEM
|
|
|
94
95
|
tago (~> 0.0)
|
|
95
96
|
regexp_parser (2.12.0)
|
|
96
97
|
rexml (3.4.4)
|
|
97
|
-
rubocop (1.
|
|
98
|
+
rubocop (1.87.0)
|
|
98
99
|
json (~> 2.3)
|
|
99
100
|
language_server-protocol (~> 3.17.0.2)
|
|
100
101
|
lint_roller (~> 1.1.0)
|
|
@@ -108,7 +109,7 @@ GEM
|
|
|
108
109
|
rubocop-ast (1.49.1)
|
|
109
110
|
parser (>= 3.3.7.2)
|
|
110
111
|
prism (~> 1.7)
|
|
111
|
-
rubocop-elegant (0.
|
|
112
|
+
rubocop-elegant (0.6.0)
|
|
112
113
|
lint_roller (~> 1.1)
|
|
113
114
|
rubocop (~> 1.75)
|
|
114
115
|
rubocop-minitest (0.39.1)
|
|
@@ -147,7 +148,7 @@ GEM
|
|
|
147
148
|
nokogiri (~> 1.8)
|
|
148
149
|
rainbow (~> 3.0)
|
|
149
150
|
slop (~> 4.4)
|
|
150
|
-
yard (0.9.
|
|
151
|
+
yard (0.9.44)
|
|
151
152
|
|
|
152
153
|
PLATFORMS
|
|
153
154
|
aarch64-linux
|
data/lib/pgtk/impatient.rb
CHANGED
|
@@ -63,10 +63,12 @@ class Pgtk::Impatient
|
|
|
63
63
|
# @param [Pgtk::Pool] pool The pool to decorate
|
|
64
64
|
# @param [Integer] timeout Timeout in seconds for each SQL query
|
|
65
65
|
# @param [Array<Regex>] off List of regex to exclude queries from checking
|
|
66
|
-
|
|
66
|
+
# @param [Integer] default Fallback timeout in seconds for excluded queries (0 = no timeout)
|
|
67
|
+
def initialize(pool, timeout, *off, default: 300)
|
|
67
68
|
@pool = pool
|
|
68
69
|
@timeout = timeout
|
|
69
70
|
@off = off
|
|
71
|
+
@default = default
|
|
70
72
|
end
|
|
71
73
|
|
|
72
74
|
# Start a new connection pool with the given arguments.
|
|
@@ -86,7 +88,7 @@ class Pgtk::Impatient
|
|
|
86
88
|
[
|
|
87
89
|
@pool.dump,
|
|
88
90
|
'',
|
|
89
|
-
"Pgtk::Impatient (timeout=#{@timeout}s):",
|
|
91
|
+
"Pgtk::Impatient (timeout=#{@timeout}s, default=#{@default}s):",
|
|
90
92
|
@off.map { |re| " #{re}" }
|
|
91
93
|
].join("\n")
|
|
92
94
|
end
|
|
@@ -106,7 +108,13 @@ class Pgtk::Impatient
|
|
|
106
108
|
# @raise [TooSlow] If the query takes too long
|
|
107
109
|
def exec(query, *args)
|
|
108
110
|
sql = query.is_a?(Array) ? query.join(' ') : query
|
|
109
|
-
|
|
111
|
+
if @off.any? { |re| re.match?(sql) }
|
|
112
|
+
ms = Integer(@default * 1000)
|
|
113
|
+
return @pool.transaction do |t|
|
|
114
|
+
t.exec("SET LOCAL statement_timeout = #{ms}") unless ms.zero?
|
|
115
|
+
t.exec(sql, *args)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
110
118
|
start = Time.now
|
|
111
119
|
ms = [Integer(@timeout * 1000), 1].max
|
|
112
120
|
begin
|
data/lib/pgtk/pgsql_task.rb
CHANGED
|
@@ -129,6 +129,10 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
129
129
|
"-e POSTGRES_DB=#{Shellwords.escape(@dbname)}",
|
|
130
130
|
'--detach',
|
|
131
131
|
'--rm',
|
|
132
|
+
'--health-cmd', 'pg_isready',
|
|
133
|
+
'--health-interval', '1s',
|
|
134
|
+
'--health-timeout', '2s',
|
|
135
|
+
'--health-retries', '10',
|
|
132
136
|
'postgres:18.1',
|
|
133
137
|
@config.map { |k, v| "-c #{Shellwords.escape("#{k}=#{v}")}" },
|
|
134
138
|
stdout:
|
|
@@ -148,6 +152,15 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
148
152
|
rescue WaitUtil::TimeoutError => e
|
|
149
153
|
raise(IOError, "Failed to start PostgreSQL Docker container #{container.inspect}: #{e.message}")
|
|
150
154
|
end
|
|
155
|
+
(30 * 2).times do
|
|
156
|
+
status = qbash(
|
|
157
|
+
"docker inspect --format='{{.State.Health.Status}}' #{Shellwords.escape(container)}",
|
|
158
|
+
accept: nil, both: true
|
|
159
|
+
)[0]&.strip
|
|
160
|
+
break if status == 'healthy'
|
|
161
|
+
raise(IOError, "PostgreSQL container #{container.inspect} is #{status}") if status == 'unhealthy'
|
|
162
|
+
sleep(0.5)
|
|
163
|
+
end
|
|
151
164
|
container
|
|
152
165
|
end
|
|
153
166
|
|
|
@@ -181,13 +194,17 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
181
194
|
puts("PostgreSQL killed in PID #{pid}") unless @quiet
|
|
182
195
|
end
|
|
183
196
|
begin
|
|
184
|
-
WaitUtil.wait_for_service('PG in local', 'localhost', port, timeout_sec: 10, delay_sec: 0.
|
|
197
|
+
WaitUtil.wait_for_service('PG in local', 'localhost', port, timeout_sec: 10, delay_sec: 0.2)
|
|
185
198
|
rescue WaitUtil::TimeoutError => e
|
|
186
199
|
puts("+ #{cmd}")
|
|
187
200
|
puts("stdout:\n#{File.read(File.join(home, 'stdout.txt'))}")
|
|
188
201
|
puts("stderr:\n#{File.read(File.join(home, 'stderr.txt'))}")
|
|
189
202
|
raise(IOError, "Failed to start PostgreSQL database server on port #{port}: #{e.message}")
|
|
190
203
|
end
|
|
204
|
+
(10 * 2).times do
|
|
205
|
+
break if qbash("pg_isready -h localhost -p #{port}", accept: nil, both: true)[1].zero?
|
|
206
|
+
sleep(0.5)
|
|
207
|
+
end
|
|
191
208
|
qbash(
|
|
192
209
|
'createdb',
|
|
193
210
|
'--host', 'localhost',
|
data/lib/pgtk/pool.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'loog'
|
|
4
3
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
5
4
|
# SPDX-License-Identifier: MIT
|
|
6
5
|
|
|
7
6
|
require 'ellipsized'
|
|
7
|
+
require 'loog'
|
|
8
8
|
require 'pg'
|
|
9
9
|
require 'tago'
|
|
10
10
|
require_relative '../pgtk'
|
|
@@ -73,6 +73,7 @@ class Pgtk::Pool
|
|
|
73
73
|
@idle = idle
|
|
74
74
|
@log = log
|
|
75
75
|
@pool = IterableQueue.new(max, timeout)
|
|
76
|
+
@lock = Mutex.new
|
|
76
77
|
@started = false
|
|
77
78
|
end
|
|
78
79
|
|
|
@@ -80,7 +81,12 @@ class Pgtk::Pool
|
|
|
80
81
|
#
|
|
81
82
|
# @return [String] Version of PostgreSQL server
|
|
82
83
|
def version
|
|
83
|
-
@version ||=
|
|
84
|
+
@version ||=
|
|
85
|
+
begin
|
|
86
|
+
conn = @pool.pop
|
|
87
|
+
@pool.push(conn)
|
|
88
|
+
conn.parameter_status('server_version').split[0]
|
|
89
|
+
end
|
|
84
90
|
end
|
|
85
91
|
|
|
86
92
|
# Get as much details about it as possible.
|
|
@@ -103,20 +109,22 @@ class Pgtk::Pool
|
|
|
103
109
|
# open at the same time. For example, Heroku free PostgreSQL database
|
|
104
110
|
# allows only one connection open.
|
|
105
111
|
def start!
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
@
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
@lock.synchronize do
|
|
113
|
+
return if @started
|
|
114
|
+
@max.times do
|
|
115
|
+
@pool.push(@wire.connection)
|
|
116
|
+
end
|
|
117
|
+
(2 * @max).times do
|
|
118
|
+
connect { |c| c.exec('SELECT 1') }
|
|
119
|
+
rescue StandardError => e
|
|
120
|
+
@log.warn("Pool warm-up query failed, slot will be retried: #{e.message.strip}")
|
|
121
|
+
end
|
|
122
|
+
@max.times do
|
|
123
|
+
connect { |c| c.exec('SELECT 1') }
|
|
124
|
+
end
|
|
125
|
+
@started = true
|
|
126
|
+
@log.debug("PostgreSQL pool started with #{@max} connections")
|
|
117
127
|
end
|
|
118
|
-
@started = true
|
|
119
|
-
@log.debug("PostgreSQL pool started with #{@max} connections")
|
|
120
128
|
end
|
|
121
129
|
|
|
122
130
|
# Make a query and return the result as an array of hashes. For example,
|
data/lib/pgtk/spy.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'loog'
|
|
4
3
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
5
4
|
# SPDX-License-Identifier: MIT
|
|
6
5
|
|
|
6
|
+
require 'loog'
|
|
7
7
|
require 'pg'
|
|
8
8
|
require_relative '../pgtk'
|
|
9
9
|
require_relative 'wire'
|
|
@@ -82,8 +82,10 @@ class Pgtk::Spy
|
|
|
82
82
|
# @param [String] sql The SQL query with params inside (possibly)
|
|
83
83
|
# @return [Array] Result rows
|
|
84
84
|
def exec(sql, *)
|
|
85
|
-
|
|
86
|
-
@pool.exec(sql, *)
|
|
85
|
+
start = Time.now
|
|
86
|
+
@pool.exec(sql, *).tap do
|
|
87
|
+
@block&.call(sql.is_a?(Array) ? sql.join(' ') : sql, Time.now - start)
|
|
88
|
+
end
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
# Run a transaction with spying on each SQL query.
|
data/lib/pgtk/stash.rb
CHANGED
|
@@ -29,8 +29,11 @@ require_relative '../pgtk'
|
|
|
29
29
|
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
30
30
|
# License:: MIT
|
|
31
31
|
class Pgtk::Stash
|
|
32
|
-
MODS = %w[
|
|
33
|
-
|
|
32
|
+
MODS = %w[
|
|
33
|
+
INSERT DELETE UPDATE LOCK VACUUM TRANSACTION COMMIT ROLLBACK
|
|
34
|
+
REINDEX TRUNCATE CREATE ALTER DROP SET START
|
|
35
|
+
].freeze
|
|
36
|
+
MODS_RE = Regexp.new("\\A(#{MODS.join('|')})(\\s|$)")
|
|
34
37
|
|
|
35
38
|
IDENT = '[a-z_][a-z0-9_]*'
|
|
36
39
|
|
|
@@ -39,9 +42,13 @@ class Pgtk::Stash
|
|
|
39
42
|
|
|
40
43
|
READS_RE = Regexp.new("(?<=^|\\s)(?:FROM|JOIN)\\s(#{IDENT})(?=\\s|;|$)")
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
NONDETERMINISTIC = /
|
|
46
|
+
\b(?:NOW|CURRENT_TIMESTAMP|CURRENT_DATE|CURRENT_TIME|
|
|
47
|
+
LOCALTIMESTAMP|LOCALTIME|RANDOM|GEN_RANDOM_UUID|
|
|
48
|
+
CLOCK_TIMESTAMP)(?:\s*\(|(?=[^a-z0-9_]|$))
|
|
49
|
+
/ix
|
|
43
50
|
|
|
44
|
-
private_constant :MODS, :ALTS, :IDENT, :MODS_RE, :ALTS_RE, :READS_RE, :
|
|
51
|
+
private_constant :MODS, :ALTS, :IDENT, :MODS_RE, :ALTS_RE, :READS_RE, :NONDETERMINISTIC
|
|
45
52
|
|
|
46
53
|
# Initialize a new Stash with query caching.
|
|
47
54
|
#
|
|
@@ -76,7 +83,9 @@ class Pgtk::Stash
|
|
|
76
83
|
@pool = pool
|
|
77
84
|
@stash = stash
|
|
78
85
|
@stash[:table_mod] ||= {}
|
|
79
|
-
@stash[:table_inflight]
|
|
86
|
+
unless @stash[:table_inflight].default_proc
|
|
87
|
+
@stash[:table_inflight] = Hash.new { |h, k| h[k] = Concurrent::AtomicFixnum.new(0) }
|
|
88
|
+
end
|
|
80
89
|
@loog = loog
|
|
81
90
|
@entrance = entrance
|
|
82
91
|
@refill = refill
|
|
@@ -121,8 +130,10 @@ class Pgtk::Stash
|
|
|
121
130
|
# @return [PG::Result] Query result object
|
|
122
131
|
def exec(query, params = [], result = 0)
|
|
123
132
|
pure = (query.is_a?(Array) ? query.join(' ') : query).gsub(/\s+/, ' ').strip
|
|
124
|
-
if MODS_RE.match?(pure)
|
|
133
|
+
if MODS_RE.match?(pure)
|
|
125
134
|
modify(pure, params, result)
|
|
135
|
+
elsif /(^|\s)pg_[a-z_]+\(/.match?(pure)
|
|
136
|
+
@pool.exec(pure, params, result)
|
|
126
137
|
else
|
|
127
138
|
select(pure, params, result)
|
|
128
139
|
end
|
|
@@ -145,7 +156,7 @@ class Pgtk::Stash
|
|
|
145
156
|
{
|
|
146
157
|
q: q.dup,
|
|
147
158
|
c: kk.values.count,
|
|
148
|
-
p: kk.values.sum { |vv| vv[:popularity] },
|
|
159
|
+
p: kk.values.sum { |vv| vv[:popularity]&.value || 0 },
|
|
149
160
|
s: kk.values.count { |vv| vv[:stale] },
|
|
150
161
|
u: kk.values.map { |vv| vv[:used] }.max || Time.now
|
|
151
162
|
}
|
|
@@ -222,15 +233,13 @@ class Pgtk::Stash
|
|
|
222
233
|
tables = pure.scan(ALTS_RE).flatten
|
|
223
234
|
tables.uniq!
|
|
224
235
|
affected = (tables + tables.flat_map { |t| @cascades&.fetch(t, []) || [] }).uniq
|
|
225
|
-
@
|
|
226
|
-
affected.each { |t| @stash[:table_inflight][t] = (@stash[:table_inflight][t] || 0) + 1 }
|
|
227
|
-
end
|
|
236
|
+
affected.each { |t| @stash[:table_inflight][t].increment }
|
|
228
237
|
begin
|
|
229
238
|
@pool.exec(pure, params, result).tap do
|
|
230
239
|
now = Time.now
|
|
231
240
|
@entrance.with_write_lock do
|
|
232
241
|
affected.each do |t|
|
|
233
|
-
@stash[:table_inflight][t]
|
|
242
|
+
@stash[:table_inflight][t].decrement
|
|
234
243
|
@stash[:table_mod][t] = (@stash[:table_mod][t] || 0) + 1
|
|
235
244
|
@stash[:tables][t]&.each do |q|
|
|
236
245
|
@stash[:queries][q]&.each_key do |key|
|
|
@@ -241,28 +250,25 @@ class Pgtk::Stash
|
|
|
241
250
|
end
|
|
242
251
|
end
|
|
243
252
|
rescue StandardError
|
|
244
|
-
@
|
|
245
|
-
affected.each { |t| @stash[:table_inflight][t] -= 1 }
|
|
246
|
-
end
|
|
253
|
+
affected.each { |t| @stash[:table_inflight][t].decrement }
|
|
247
254
|
raise
|
|
248
255
|
end
|
|
249
256
|
end
|
|
250
257
|
|
|
251
258
|
def select(pure, params, result)
|
|
252
|
-
|
|
253
|
-
ret
|
|
254
|
-
if ret.nil? || @stash.dig(:queries, pure, key, :stale)
|
|
259
|
+
ret = @stash.dig(:queries, pure, params, :ret)
|
|
260
|
+
if ret.nil? || @stash.dig(:queries, pure, params, :stale)
|
|
255
261
|
tables = pure.scan(READS_RE).flatten
|
|
256
262
|
tables.uniq!
|
|
257
263
|
marks = tables.to_h { |t| [t, @stash[:table_mod][t]] }
|
|
258
264
|
ret = @pool.exec(pure, params, result)
|
|
259
|
-
cache(pure,
|
|
265
|
+
cache(pure, params, result, ret, tables, marks) unless pure.match?(NONDETERMINISTIC)
|
|
260
266
|
end
|
|
261
|
-
bump(pure,
|
|
267
|
+
bump(pure, params) if @stash.dig(:queries, pure, params)
|
|
262
268
|
ret
|
|
263
269
|
end
|
|
264
270
|
|
|
265
|
-
def cache(pure,
|
|
271
|
+
def cache(pure, params, result, ret, tables, marks)
|
|
266
272
|
raise(ArgumentError, "No tables at #{pure.inspect}") if tables.empty?
|
|
267
273
|
@entrance.with_write_lock do
|
|
268
274
|
tables.each do |t|
|
|
@@ -270,7 +276,7 @@ class Pgtk::Stash
|
|
|
270
276
|
@stash[:tables][t].append(pure).uniq!
|
|
271
277
|
end
|
|
272
278
|
@stash[:queries][pure] ||= {}
|
|
273
|
-
existing = @stash[:queries][pure][
|
|
279
|
+
existing = @stash[:queries][pure][params]
|
|
274
280
|
stillborn = tables.any? { |t| (cur = @stash[:table_mod][t]) && cur != marks[t] }
|
|
275
281
|
entry = { ret:, params:, result:, used: Time.now }
|
|
276
282
|
entry[:stale] =
|
|
@@ -280,23 +286,32 @@ class Pgtk::Stash
|
|
|
280
286
|
Time.now
|
|
281
287
|
end
|
|
282
288
|
entry.delete(:stale) if entry[:stale].nil?
|
|
283
|
-
|
|
289
|
+
entry[:popularity] = (existing && existing[:popularity]) || Concurrent::AtomicFixnum.new
|
|
290
|
+
@stash[:queries][pure][params] = entry
|
|
284
291
|
end
|
|
285
292
|
end
|
|
286
293
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
294
|
+
# Bump popularity and last-used time of a cached entry.
|
|
295
|
+
#
|
|
296
|
+
# This runs without the exclusive write lock: popularity is a
|
|
297
|
+
# +Concurrent::AtomicFixnum+ and +used+ is a plain assignment of an
|
|
298
|
+
# immutable value, neither of which mutates the structure of the
|
|
299
|
+
# +@stash[:queries]+ tree. Keeping it off the writer lock means a
|
|
300
|
+
# read-only workload never serializes through the writer (see #414).
|
|
301
|
+
#
|
|
302
|
+
# @return [void]
|
|
303
|
+
def bump(pure, params)
|
|
304
|
+
entry = @stash.dig(:queries, pure, params)
|
|
305
|
+
return unless entry
|
|
306
|
+
(entry[:popularity] ||= Concurrent::AtomicFixnum.new).increment
|
|
307
|
+
entry[:used] = Time.now
|
|
293
308
|
end
|
|
294
309
|
|
|
295
310
|
# Calculate total number of cached query results.
|
|
296
311
|
#
|
|
297
312
|
# @return [Integer] Total count of cached query results
|
|
298
313
|
def cached
|
|
299
|
-
@entrance.
|
|
314
|
+
@entrance.with_read_lock do
|
|
300
315
|
@stash[:queries].values.sum { |kk| kk.values.size }
|
|
301
316
|
end
|
|
302
317
|
end
|
|
@@ -350,6 +365,8 @@ class Pgtk::Stash
|
|
|
350
365
|
end
|
|
351
366
|
end
|
|
352
367
|
end
|
|
368
|
+
rescue StandardError => e
|
|
369
|
+
@loog.warn("Stash capper crashed: #{e.class}: #{e.message}")
|
|
353
370
|
end
|
|
354
371
|
end
|
|
355
372
|
|
|
@@ -361,26 +378,30 @@ class Pgtk::Stash
|
|
|
361
378
|
evict(q) if @stash[:queries][q].empty?
|
|
362
379
|
end
|
|
363
380
|
end
|
|
381
|
+
rescue StandardError => e
|
|
382
|
+
@loog.warn("Stash retiree crashed: #{e.class}: #{e.message}")
|
|
364
383
|
end
|
|
365
384
|
end
|
|
366
385
|
|
|
367
|
-
def evict(query)
|
|
368
|
-
@stash[:queries].delete(query)
|
|
369
|
-
@stash[:tables].each_value { |list| list.delete(query) }
|
|
370
|
-
@stash[:tables].delete_if { |_, list| list.empty? }
|
|
371
|
-
end
|
|
372
|
-
|
|
373
386
|
def refiller!
|
|
374
387
|
Concurrent::TimerTask.execute(execution_interval: @refill, executor: @tpool) do
|
|
375
388
|
ranked.each { |q| replenish(q) }
|
|
389
|
+
rescue StandardError => e
|
|
390
|
+
@loog.warn("Stash refiller crashed: #{e.class}: #{e.message}")
|
|
376
391
|
end
|
|
377
392
|
end
|
|
378
393
|
|
|
394
|
+
def evict(query)
|
|
395
|
+
@stash[:queries].delete(query)
|
|
396
|
+
@stash[:tables].each_value { |list| list.delete(query) }
|
|
397
|
+
@stash[:tables].delete_if { |_, list| list.empty? }
|
|
398
|
+
end
|
|
399
|
+
|
|
379
400
|
def ranked
|
|
380
401
|
qq =
|
|
381
402
|
@entrance.with_write_lock do
|
|
382
403
|
@stash[:queries]
|
|
383
|
-
.map { |k, v| [k, v.values.sum { |vv| vv[:popularity] }, v.values.any? { |vv| vv[:stale] }] }
|
|
404
|
+
.map { |k, v| [k, v.values.sum { |vv| vv[:popularity]&.value || 0 }, v.values.any? { |vv| vv[:stale] }] }
|
|
384
405
|
end
|
|
385
406
|
qq.select { _1[2] }.sort_by { -_1[1] }.map { _1[0] }
|
|
386
407
|
end
|
|
@@ -408,7 +429,7 @@ class Pgtk::Stash
|
|
|
408
429
|
next unless h
|
|
409
430
|
next unless h[:stale] == mark
|
|
410
431
|
next if pinned.any? { |t, m| @stash[:table_mod][t] != m }
|
|
411
|
-
next if tables.any? { |t|
|
|
432
|
+
next if tables.any? { |t| @stash[:table_inflight][t].value.positive? }
|
|
412
433
|
h[:ret] = ret
|
|
413
434
|
h.delete(:stale)
|
|
414
435
|
end
|
data/lib/pgtk/version.rb
CHANGED
data/lib/pgtk/wire/env.rb
CHANGED
|
@@ -29,7 +29,7 @@ class Pgtk::Wire::Env
|
|
|
29
29
|
def initialize(var = 'DATABASE_URL', **opts)
|
|
30
30
|
raise(ArgumentError, "The name of the environment variable can't be nil") if var.nil?
|
|
31
31
|
@value = ENV.fetch(var, nil)
|
|
32
|
-
raise(ArgumentError, "The environment variable #{
|
|
32
|
+
raise(ArgumentError, "The environment variable #{var.inspect} is not set") if @value.nil?
|
|
33
33
|
@opts = opts
|
|
34
34
|
end
|
|
35
35
|
|
data/lib/pgtk/wire/yaml.rb
CHANGED
|
@@ -19,11 +19,14 @@ class Pgtk::Wire::Yaml
|
|
|
19
19
|
#
|
|
20
20
|
# @param [String] file Path to the YAML configuration file
|
|
21
21
|
# @param [String] node The root node name in the YAML file containing PostgreSQL configuration
|
|
22
|
-
|
|
22
|
+
# @param [Hash] opts Extra options forwarded to +PG.connect+ (e.g. +sslmode+,
|
|
23
|
+
# +connect_timeout+, +keepalives+). These override values from the YAML file.
|
|
24
|
+
def initialize(file, node = 'pgsql', **opts)
|
|
23
25
|
raise(ArgumentError, "The name of the file can't be nil") if file.nil?
|
|
24
26
|
@file = file
|
|
25
27
|
raise(ArgumentError, "The name of the node in the YAML file can't be nil") if node.nil?
|
|
26
28
|
@node = node
|
|
29
|
+
@opts = opts
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
# Create a new connection to PostgreSQL server.
|
|
@@ -36,7 +39,9 @@ class Pgtk::Wire::Yaml
|
|
|
36
39
|
port: cfg[@node]['port'],
|
|
37
40
|
dbname: cfg[@node]['dbname'],
|
|
38
41
|
user: cfg[@node]['user'],
|
|
39
|
-
password: cfg[@node]['password']
|
|
42
|
+
password: cfg[@node]['password'],
|
|
43
|
+
**cfg[@node].except(*%w[host port dbname user password url]).transform_keys(&:to_sym),
|
|
44
|
+
**@opts
|
|
40
45
|
).connection
|
|
41
46
|
end
|
|
42
47
|
end
|
data/pgtk.gemspec
CHANGED
|
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
|
|
|
26
26
|
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
27
27
|
s.rdoc_options = ['--charset=UTF-8']
|
|
28
28
|
s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
|
|
29
|
-
s.add_dependency('
|
|
29
|
+
s.add_dependency('cgi', '~>0.0')
|
|
30
30
|
s.add_dependency('concurrent-ruby', '~>1.3')
|
|
31
31
|
s.add_dependency('donce', '~>0.0')
|
|
32
32
|
s.add_dependency('ellipsized', '~>0.3')
|
data/resources/pom.xml
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<version>0.0.0</version>
|
|
11
11
|
<packaging>pom</packaging>
|
|
12
12
|
<properties>
|
|
13
|
-
<postgresql.version>42.7.
|
|
13
|
+
<postgresql.version>42.7.12</postgresql.version>
|
|
14
14
|
<liquibase.version>5.0.3</liquibase.version>
|
|
15
15
|
</properties>
|
|
16
16
|
<dependencies>
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pgtk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.32.
|
|
4
|
+
version: 0.32.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yegor Bugayenko
|
|
@@ -10,19 +10,19 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: cgi
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0.
|
|
18
|
+
version: '0.0'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0.
|
|
25
|
+
version: '0.0'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: concurrent-ruby
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|