pgtk 0.30.6 → 0.30.7
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 +1 -0
- data/Gemfile.lock +17 -13
- data/Rakefile +2 -2
- data/lib/pgtk/impatient.rb +5 -5
- data/lib/pgtk/liquibase_task.rb +83 -93
- data/lib/pgtk/liquicheck_task.rb +59 -62
- data/lib/pgtk/pgsql_task.rb +63 -89
- data/lib/pgtk/pool.rb +12 -12
- data/lib/pgtk/retry.rb +3 -3
- data/lib/pgtk/spy.rb +2 -2
- data/lib/pgtk/stash.rb +217 -202
- data/lib/pgtk/version.rb +1 -2
- data/lib/pgtk/wire.rb +81 -85
- data/lib/pgtk.rb +0 -1
- data/pgtk.gemspec +13 -13
- metadata +1 -1
data/lib/pgtk/stash.rb
CHANGED
|
@@ -21,7 +21,7 @@ require_relative '../pgtk'
|
|
|
21
21
|
#
|
|
22
22
|
# @example Basic usage
|
|
23
23
|
# pool = Pgtk::Pool.new(...)
|
|
24
|
-
# stash = Pgtk::Stash.new(pool, cap: 1000,
|
|
24
|
+
# stash = Pgtk::Stash.new(pool, cap: 1000, refill: 30)
|
|
25
25
|
# stash.start!
|
|
26
26
|
# result = stash.exec('SELECT * FROM users WHERE id = $1', [42])
|
|
27
27
|
#
|
|
@@ -43,60 +43,48 @@ class Pgtk::Stash
|
|
|
43
43
|
#
|
|
44
44
|
# Set any of the intervals to nil to disable the cron.
|
|
45
45
|
#
|
|
46
|
-
# @param [Object] pool The underlying connection pool
|
|
47
|
-
# @param [Hash] stash Internal cache structure
|
|
48
|
-
#
|
|
49
|
-
# @param [Float]
|
|
50
|
-
#
|
|
51
|
-
# @param [
|
|
52
|
-
# @param [Integer]
|
|
53
|
-
#
|
|
54
|
-
# @param [Integer]
|
|
55
|
-
#
|
|
56
|
-
# @param [
|
|
57
|
-
#
|
|
58
|
-
# @param [Float] cap_interval Interval in seconds between background tasks that enforce the cache size
|
|
59
|
-
# cap by removing old queries
|
|
60
|
-
# @param [Integer] retire Maximum age in seconds to keep a query in cache after its latest usage
|
|
61
|
-
# @param [Float] retire_interval Interval in seconds between background tasks that remove
|
|
62
|
-
# retired queries
|
|
63
|
-
# @param [Loog] loog Logger instance for debugging and monitoring cache operations (default: null logger)
|
|
64
|
-
# @param [Concurrent::ReentrantReadWriteLock] entrance Read-write lock for thread-safe cache access
|
|
65
|
-
# shared across instances
|
|
46
|
+
# @param [Object] pool The underlying connection pool
|
|
47
|
+
# @param [Hash] stash Internal cache structure
|
|
48
|
+
# @param [Float] refill Interval in seconds between background refill tasks
|
|
49
|
+
# @param [Float] delay A pause in seconds before making a refill
|
|
50
|
+
# @param [Integer] maxqueue Maximum number of refilling tasks in the thread pool queue
|
|
51
|
+
# @param [Integer] threads Number of worker threads for cache refilling
|
|
52
|
+
# @param [Integer] cap Maximum number of cached query results to retain
|
|
53
|
+
# @param [Float] capping Interval in seconds between cache cap enforcement tasks
|
|
54
|
+
# @param [Integer] retire Maximum age in seconds to keep a query in cache
|
|
55
|
+
# @param [Float] retirement Interval in seconds between retirement tasks
|
|
56
|
+
# @param [Loog] loog Logger instance
|
|
57
|
+
# @param [Concurrent::ReentrantReadWriteLock] entrance Read-write lock for thread-safe access
|
|
66
58
|
def initialize(
|
|
67
59
|
pool,
|
|
68
60
|
stash: { queries: {}, tables: {} },
|
|
69
61
|
loog: Loog::NULL,
|
|
70
62
|
entrance: Concurrent::ReentrantReadWriteLock.new,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
refill: 16,
|
|
64
|
+
delay: 0,
|
|
65
|
+
maxqueue: 128,
|
|
74
66
|
threads: 4,
|
|
75
67
|
cap: 10_000,
|
|
76
|
-
|
|
68
|
+
capping: 60,
|
|
77
69
|
retire: 15 * 60,
|
|
78
|
-
|
|
70
|
+
retirement: 60
|
|
79
71
|
)
|
|
80
72
|
@pool = pool
|
|
81
73
|
@stash = stash
|
|
82
74
|
@loog = loog
|
|
83
75
|
@entrance = entrance
|
|
84
|
-
@
|
|
85
|
-
@
|
|
86
|
-
@
|
|
76
|
+
@refill = refill
|
|
77
|
+
@delay = delay
|
|
78
|
+
@maxqueue = maxqueue
|
|
87
79
|
@threads = threads
|
|
88
80
|
@cap = cap
|
|
89
|
-
@
|
|
81
|
+
@capping = capping
|
|
90
82
|
@retire = retire
|
|
91
|
-
@
|
|
83
|
+
@retirement = retirement
|
|
92
84
|
end
|
|
93
85
|
|
|
94
86
|
# Start the connection pool and launch background cache management tasks.
|
|
95
87
|
#
|
|
96
|
-
# Initializes background timer tasks for cache refilling and size capping.
|
|
97
|
-
# The refill task periodically updates stale cached queries based on popularity.
|
|
98
|
-
# The cap task removes oldest queries when cache size exceeds the configured limit.
|
|
99
|
-
#
|
|
100
88
|
# @return [void]
|
|
101
89
|
def start!
|
|
102
90
|
@pool.start!
|
|
@@ -111,155 +99,177 @@ class Pgtk::Stash
|
|
|
111
99
|
|
|
112
100
|
# Convert internal state into text.
|
|
113
101
|
#
|
|
114
|
-
# Generates a detailed report of the cache state including query counts,
|
|
115
|
-
# popularity scores, stale queries, and thread pool status.
|
|
116
|
-
#
|
|
117
102
|
# @return [String] Multi-line text representation of the current cache state
|
|
118
103
|
def dump
|
|
119
104
|
@entrance.with_read_lock do
|
|
120
|
-
qq =
|
|
121
|
-
|
|
122
|
-
{
|
|
123
|
-
q: q.dup, # the query
|
|
124
|
-
c: kk.values.count, # how many keys?
|
|
125
|
-
p: kk.values.sum { |vv| vv[:popularity] }, # total popularity of all keys
|
|
126
|
-
s: kk.values.count { |vv| vv[:stale] }, # how many stale keys?
|
|
127
|
-
u: kk.values.map { |vv| vv[:used] }.max || Time.now # when was it used
|
|
128
|
-
}
|
|
129
|
-
end
|
|
130
|
-
[
|
|
131
|
-
@pool.dump,
|
|
132
|
-
'',
|
|
133
|
-
[
|
|
134
|
-
'Pgtk::Stash (',
|
|
135
|
-
[
|
|
136
|
-
"threads=#{@threads}",
|
|
137
|
-
"max_queue_length=#{@max_queue_length}",
|
|
138
|
-
if @refill_interval
|
|
139
|
-
[
|
|
140
|
-
"refill_interval=#{@refill_interval}s",
|
|
141
|
-
"refill_delay=#{@refill_delay}s"
|
|
142
|
-
]
|
|
143
|
-
else
|
|
144
|
-
'no refilling'
|
|
145
|
-
end,
|
|
146
|
-
if @cap_interval
|
|
147
|
-
[
|
|
148
|
-
"cap_interval=#{@cap_interval}s",
|
|
149
|
-
"cap=#{@cap}"
|
|
150
|
-
]
|
|
151
|
-
else
|
|
152
|
-
'no capping'
|
|
153
|
-
end,
|
|
154
|
-
if @retire_interval
|
|
155
|
-
[
|
|
156
|
-
"retire_interval=#{@retire_interval}s",
|
|
157
|
-
"retire=#{@retire}"
|
|
158
|
-
]
|
|
159
|
-
else
|
|
160
|
-
'no retirement'
|
|
161
|
-
end
|
|
162
|
-
].flatten.join(', '),
|
|
163
|
-
'):'
|
|
164
|
-
].join,
|
|
165
|
-
if @tpool
|
|
166
|
-
" #{@tpool.queue_length} tasks in the thread pool"
|
|
167
|
-
else
|
|
168
|
-
' Not launched yet'
|
|
169
|
-
end,
|
|
170
|
-
" #{stash_size} queries cached (#{stash_size > @cap ? 'above' : 'below'} the cap)",
|
|
171
|
-
" #{@stash[:tables].count} tables in cache",
|
|
172
|
-
" #{qq.sum { |a| a[:s] }} stale queries in cache:",
|
|
173
|
-
qq.select { |a| a[:s].positive? }.sort_by { -_1[:p] }.take(8).map do |a|
|
|
174
|
-
" #{a[:c]}/#{a[:p]}p/#{a[:s]}s/#{a[:u].ago}: #{a[:q]}"
|
|
175
|
-
end,
|
|
176
|
-
" #{qq.count { |a| a[:s].zero? }} other queries in cache:",
|
|
177
|
-
qq.select { |a| a[:s].zero? }.sort_by { -_1[:p] }.take(16).map do |a|
|
|
178
|
-
" #{a[:c]}/#{a[:p]}p/#{a[:s]}s/#{a[:u].ago}: #{a[:q]}"
|
|
179
|
-
end
|
|
180
|
-
].join("\n")
|
|
105
|
+
qq = queries
|
|
106
|
+
body(qq)
|
|
181
107
|
end
|
|
182
108
|
end
|
|
183
109
|
|
|
184
110
|
# Execute a SQL query with optional caching.
|
|
185
111
|
#
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
# @param [String, Array<String>] query The SQL query to execute as a string or array of strings to be joined
|
|
192
|
-
# @param [Array] params Query parameters for placeholder substitution in prepared statements (default: empty array)
|
|
193
|
-
# @param [Integer] result Result format code where 0 requests text format and 1 requests binary format (default: 0)
|
|
194
|
-
# @return [PG::Result] Query result object containing rows and metadata from the database
|
|
112
|
+
# @param [String, Array<String>] query The SQL query
|
|
113
|
+
# @param [Array] params Query parameters
|
|
114
|
+
# @param [Integer] result Result format code
|
|
115
|
+
# @return [PG::Result] Query result object
|
|
195
116
|
def exec(query, params = [], result = 0)
|
|
196
117
|
pure = (query.is_a?(Array) ? query.join(' ') : query).gsub(/\s+/, ' ').strip
|
|
197
118
|
if MODS_RE.match?(pure) || /(^|\s)pg_[a-z_]+\(/.match?(pure)
|
|
198
|
-
|
|
199
|
-
ret = @pool.exec(pure, params, result)
|
|
200
|
-
@entrance.with_write_lock do
|
|
201
|
-
tables.each do |t|
|
|
202
|
-
@stash[:tables][t]&.each do |q|
|
|
203
|
-
@stash[:queries][q]&.each_key do |key|
|
|
204
|
-
@stash[:queries][q][key][:stale] = Time.now
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
end
|
|
119
|
+
modify(pure, params, result)
|
|
209
120
|
else
|
|
210
|
-
|
|
211
|
-
ret = @stash.dig(:queries, pure, key, :ret)
|
|
212
|
-
if ret.nil? || @stash.dig(:queries, pure, key, :stale)
|
|
213
|
-
ret = @pool.exec(pure, params, result)
|
|
214
|
-
unless pure.include?(' NOW() ')
|
|
215
|
-
tables = pure.scan(/(?<=^|\s)(?:FROM|JOIN) ([a-z_]+)(?=\s|;|$)/).map(&:first).uniq
|
|
216
|
-
raise "No tables at #{pure.inspect}" if tables.empty?
|
|
217
|
-
@entrance.with_write_lock do
|
|
218
|
-
tables.each do |t|
|
|
219
|
-
@stash[:tables][t] = [] if @stash[:tables][t].nil?
|
|
220
|
-
@stash[:tables][t].append(pure).uniq!
|
|
221
|
-
end
|
|
222
|
-
@stash[:queries][pure] ||= {}
|
|
223
|
-
@stash[:queries][pure][key] = { ret:, params:, result:, used: Time.now }
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
if @stash.dig(:queries, pure, key)
|
|
228
|
-
@entrance.with_write_lock do
|
|
229
|
-
@stash[:queries][pure][key][:popularity] ||= 0
|
|
230
|
-
@stash[:queries][pure][key][:popularity] += 1
|
|
231
|
-
@stash[:queries][pure][key][:used] = Time.now
|
|
232
|
-
end
|
|
233
|
-
end
|
|
121
|
+
select(pure, params, result)
|
|
234
122
|
end
|
|
235
|
-
ret
|
|
236
123
|
end
|
|
237
124
|
|
|
238
125
|
# Execute a database transaction.
|
|
239
126
|
#
|
|
240
|
-
# Yields a new Stash that shares the same cache but uses the transaction connection.
|
|
241
|
-
#
|
|
242
127
|
# @yield [Pgtk::Stash] A stash connected to the transaction
|
|
243
128
|
# @return [Object] The result of the block
|
|
244
129
|
def transaction
|
|
245
130
|
@pool.transaction do |t|
|
|
246
|
-
yield
|
|
247
|
-
t,
|
|
248
|
-
stash: @stash,
|
|
249
|
-
loog: @loog,
|
|
250
|
-
entrance: @entrance
|
|
251
|
-
)
|
|
131
|
+
yield(Pgtk::Stash.new(t, stash: @stash, loog: @loog, entrance: @entrance))
|
|
252
132
|
end
|
|
253
133
|
end
|
|
254
134
|
|
|
255
135
|
private
|
|
256
136
|
|
|
137
|
+
def queries
|
|
138
|
+
@stash[:queries].map do |q, kk|
|
|
139
|
+
{
|
|
140
|
+
q: q.dup,
|
|
141
|
+
c: kk.values.count,
|
|
142
|
+
p: kk.values.sum { |vv| vv[:popularity] },
|
|
143
|
+
s: kk.values.count { |vv| vv[:stale] },
|
|
144
|
+
u: kk.values.map { |vv| vv[:used] }.max || Time.now
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def body(list)
|
|
150
|
+
[
|
|
151
|
+
@pool.dump,
|
|
152
|
+
'',
|
|
153
|
+
header,
|
|
154
|
+
if @tpool
|
|
155
|
+
" #{@tpool.queue_length} tasks in the thread pool"
|
|
156
|
+
else
|
|
157
|
+
' Not launched yet'
|
|
158
|
+
end,
|
|
159
|
+
" #{cached} queries cached (#{cached > @cap ? 'above' : 'below'} the cap)",
|
|
160
|
+
" #{@stash[:tables].count} tables in cache",
|
|
161
|
+
" #{list.sum { |a| a[:s] }} stale queries in cache:",
|
|
162
|
+
stale(list),
|
|
163
|
+
" #{list.count { |a| a[:s].zero? }} other queries in cache:",
|
|
164
|
+
fresh(list)
|
|
165
|
+
].join("\n")
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def header
|
|
169
|
+
[
|
|
170
|
+
'Pgtk::Stash (',
|
|
171
|
+
[
|
|
172
|
+
"threads=#{@threads}",
|
|
173
|
+
"maxqueue=#{@maxqueue}",
|
|
174
|
+
if @refill
|
|
175
|
+
[
|
|
176
|
+
"refill=#{@refill}s",
|
|
177
|
+
"delay=#{@delay}s"
|
|
178
|
+
]
|
|
179
|
+
else
|
|
180
|
+
'no refilling'
|
|
181
|
+
end,
|
|
182
|
+
if @capping
|
|
183
|
+
[
|
|
184
|
+
"capping=#{@capping}s",
|
|
185
|
+
"cap=#{@cap}"
|
|
186
|
+
]
|
|
187
|
+
else
|
|
188
|
+
'no capping'
|
|
189
|
+
end,
|
|
190
|
+
if @retirement
|
|
191
|
+
[
|
|
192
|
+
"retirement=#{@retirement}s",
|
|
193
|
+
"retire=#{@retire}"
|
|
194
|
+
]
|
|
195
|
+
else
|
|
196
|
+
'no retirement'
|
|
197
|
+
end
|
|
198
|
+
].flatten.join(', '),
|
|
199
|
+
'):'
|
|
200
|
+
].join
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def stale(list)
|
|
204
|
+
items = list.select { |a| a[:s].positive? }.sort_by { -_1[:p] }.take(8)
|
|
205
|
+
items.map! { |a| " #{a[:c]}/#{a[:p]}p/#{a[:s]}s/#{a[:u].ago}: #{a[:q]}" }
|
|
206
|
+
items
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def fresh(list)
|
|
210
|
+
items = list.select { |a| a[:s].zero? }.sort_by { -_1[:p] }.take(16)
|
|
211
|
+
items.map! { |a| " #{a[:c]}/#{a[:p]}p/#{a[:s]}s/#{a[:u].ago}: #{a[:q]}" }
|
|
212
|
+
items
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def modify(pure, params, result)
|
|
216
|
+
tables = pure.scan(ALTS_RE).flatten
|
|
217
|
+
tables.uniq!
|
|
218
|
+
ret = @pool.exec(pure, params, result)
|
|
219
|
+
@entrance.with_write_lock do
|
|
220
|
+
tables.each do |t|
|
|
221
|
+
@stash[:tables][t]&.each do |q|
|
|
222
|
+
@stash[:queries][q]&.each_key do |key|
|
|
223
|
+
@stash[:queries][q][key][:stale] = Time.now
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
ret
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def select(pure, params, result)
|
|
232
|
+
key = params.join(SEPARATOR)
|
|
233
|
+
ret = @stash.dig(:queries, pure, key, :ret)
|
|
234
|
+
if ret.nil? || @stash.dig(:queries, pure, key, :stale)
|
|
235
|
+
mark = @stash.dig(:queries, pure, key, :stale)
|
|
236
|
+
ret = @pool.exec(pure, params, result)
|
|
237
|
+
cache(pure, key, params, result, ret, mark) unless pure.include?(' NOW() ')
|
|
238
|
+
end
|
|
239
|
+
bump(pure, key) if @stash.dig(:queries, pure, key)
|
|
240
|
+
ret
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def cache(pure, key, params, result, ret, mark)
|
|
244
|
+
tables = pure.scan(/(?<=^|\s)(?:FROM|JOIN) ([a-z_]+)(?=\s|;|$)/).flatten
|
|
245
|
+
tables.uniq!
|
|
246
|
+
raise(ArgumentError, "No tables at #{pure.inspect}") if tables.empty?
|
|
247
|
+
@entrance.with_write_lock do
|
|
248
|
+
tables.each do |t|
|
|
249
|
+
@stash[:tables][t] = [] if @stash[:tables][t].nil?
|
|
250
|
+
@stash[:tables][t].append(pure).uniq!
|
|
251
|
+
end
|
|
252
|
+
@stash[:queries][pure] ||= {}
|
|
253
|
+
existing = @stash[:queries][pure][key]
|
|
254
|
+
stale = existing && existing[:stale]
|
|
255
|
+
entry = { ret:, params:, result:, used: Time.now }
|
|
256
|
+
entry[:stale] = stale if stale && stale != mark
|
|
257
|
+
@stash[:queries][pure][key] = entry
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def bump(pure, key)
|
|
262
|
+
@entrance.with_write_lock do
|
|
263
|
+
@stash[:queries][pure][key][:popularity] ||= 0
|
|
264
|
+
@stash[:queries][pure][key][:popularity] += 1
|
|
265
|
+
@stash[:queries][pure][key][:used] = Time.now
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
257
269
|
# Calculate total number of cached query results.
|
|
258
270
|
#
|
|
259
|
-
# Counts all cached query-parameter combinations across all queries.
|
|
260
|
-
#
|
|
261
271
|
# @return [Integer] Total count of cached query results
|
|
262
|
-
def
|
|
272
|
+
def cached
|
|
263
273
|
@entrance.with_write_lock do
|
|
264
274
|
@stash[:queries].values.sum { |kk| kk.values.size }
|
|
265
275
|
end
|
|
@@ -267,64 +277,69 @@ class Pgtk::Stash
|
|
|
267
277
|
|
|
268
278
|
# Launch background tasks for cache management.
|
|
269
279
|
#
|
|
270
|
-
# Starts two concurrent timer tasks: one for enforcing cache size cap by removing
|
|
271
|
-
# oldest queries, and another for refilling stale cached queries based on popularity.
|
|
272
|
-
# This method can only be called once per cache instance.
|
|
273
|
-
#
|
|
274
280
|
# @return [nil]
|
|
275
|
-
# @raise [RuntimeError] if background tasks have already been launched on this cache instance
|
|
276
281
|
def launch!
|
|
277
282
|
@tpool = Concurrent::FixedThreadPool.new(@threads)
|
|
278
|
-
if @
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
@stash[:queries].delete_if { |_, kk| kk.empty? }
|
|
288
|
-
end
|
|
289
|
-
end
|
|
290
|
-
end
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
if @retire_interval
|
|
294
|
-
Concurrent::TimerTask.execute(execution_interval: @retire_interval, executor: @tpool) do
|
|
283
|
+
capper! if @capping
|
|
284
|
+
retiree! if @retirement
|
|
285
|
+
refiller! if @refill
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def capper!
|
|
289
|
+
Concurrent::TimerTask.execute(execution_interval: @capping, executor: @tpool) do
|
|
290
|
+
loop do
|
|
291
|
+
break if cached <= @cap
|
|
295
292
|
@entrance.with_write_lock do
|
|
296
293
|
@stash[:queries].each_key do |q|
|
|
297
|
-
@stash[:queries][q].
|
|
294
|
+
m = @stash[:queries][q].values.map { |h| h[:used] }.min
|
|
295
|
+
next unless m
|
|
296
|
+
@stash[:queries][q].delete_if { |_, h| h[:used] == m }
|
|
298
297
|
@stash[:queries].delete_if { |_, kk| kk.empty? }
|
|
299
298
|
end
|
|
300
299
|
end
|
|
301
300
|
end
|
|
302
301
|
end
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def retiree!
|
|
305
|
+
Concurrent::TimerTask.execute(execution_interval: @retirement, executor: @tpool) do
|
|
306
|
+
@entrance.with_write_lock do
|
|
307
|
+
@stash[:queries].each_key do |q|
|
|
308
|
+
@stash[:queries][q].delete_if { |_, h| h[:used] < Time.now - @retire }
|
|
309
|
+
@stash[:queries].delete_if { |_, kk| kk.empty? }
|
|
309
310
|
end
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def refiller!
|
|
316
|
+
Concurrent::TimerTask.execute(execution_interval: @refill, executor: @tpool) do
|
|
317
|
+
ranked.each { |q| replenish(q) }
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def ranked
|
|
322
|
+
qq =
|
|
323
|
+
@entrance.with_write_lock do
|
|
324
|
+
@stash[:queries]
|
|
325
|
+
.map { |k, v| [k, v.values.sum { |vv| vv[:popularity] }, v.values.any? { |vv| vv[:stale] }] }
|
|
326
|
+
end
|
|
327
|
+
qq.select { _1[2] }.sort_by { -_1[1] }.map { _1[0] }
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def replenish(query)
|
|
331
|
+
@entrance.with_write_lock { @stash[:queries][query].keys }.each do |k|
|
|
332
|
+
next unless @stash[:queries][query][k][:stale]
|
|
333
|
+
next if @stash[:queries][query][k][:stale] > Time.now - @delay
|
|
334
|
+
next if @tpool.queue_length >= @maxqueue
|
|
335
|
+
@tpool.post do
|
|
336
|
+
h = @stash[:queries][query][k]
|
|
337
|
+
mark = h[:stale]
|
|
338
|
+
ret = @pool.exec(query, h[:params], h[:result])
|
|
339
|
+
@entrance.with_write_lock do
|
|
340
|
+
h = @stash[:queries][query][k]
|
|
341
|
+
h[:ret] = ret
|
|
342
|
+
h.delete(:stale) if h[:stale] == mark
|
|
328
343
|
end
|
|
329
344
|
end
|
|
330
345
|
end
|
data/lib/pgtk/version.rb
CHANGED