pgtk 0.33.0 → 0.34.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 +8 -8
- data/README.md +28 -15
- data/lib/pgtk/impatient/too_slow.rb +3 -3
- data/lib/pgtk/impatient.rb +30 -35
- data/lib/pgtk/version.rb +1 -1
- data/resources/pom.xml +3 -3
- data/test-resources/2019/01-test.xml +2 -2
- data/test-resources/master.xml +2 -2
- 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: f4e1d913f72c6d33b8ecdb1cc17487c2ad7dfceae06f897b81cf0b4c12d32278
|
|
4
|
+
data.tar.gz: 456a7e3ae4a04891f3c51c0bc32108a30625782db57cca1725dbc4aec20b9363
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 810a9b990e6e040683f02f58932ce480aa250331d1a73c6750e68f9ede1b069a0a63446ce3ecc3971bf07cb5695f2c25ce64d697fe33a4d1d5be18ed5d7b7ae5
|
|
7
|
+
data.tar.gz: c2ac270f074f32bab3ce1dc03b7a68b9c5ae17eeccc764d07e379813a665d57b968f6f2de672b9640db31d56e716a7212813e95d6968d8988ff4fd25e375d898
|
data/Gemfile.lock
CHANGED
|
@@ -36,8 +36,8 @@ GEM
|
|
|
36
36
|
tago (~> 0.1)
|
|
37
37
|
ellipsized (0.3.0)
|
|
38
38
|
joined (0.4.0)
|
|
39
|
-
json (2.
|
|
40
|
-
language_server-protocol (3.17.0.
|
|
39
|
+
json (2.21.1)
|
|
40
|
+
language_server-protocol (3.17.0.6)
|
|
41
41
|
lint_roller (1.1.0)
|
|
42
42
|
logger (1.7.0)
|
|
43
43
|
loog (0.8.0)
|
|
@@ -71,7 +71,7 @@ GEM
|
|
|
71
71
|
racc (~> 1.4)
|
|
72
72
|
os (1.1.4)
|
|
73
73
|
parallel (2.1.0)
|
|
74
|
-
parser (3.3.
|
|
74
|
+
parser (3.3.12.0)
|
|
75
75
|
ast (~> 2.4.1)
|
|
76
76
|
racc
|
|
77
77
|
pg (1.6.3)
|
|
@@ -95,7 +95,7 @@ GEM
|
|
|
95
95
|
tago (~> 0.0)
|
|
96
96
|
regexp_parser (2.12.0)
|
|
97
97
|
rexml (3.4.4)
|
|
98
|
-
rubocop (1.88.
|
|
98
|
+
rubocop (1.88.2)
|
|
99
99
|
json (~> 2.3)
|
|
100
100
|
language_server-protocol (~> 3.17.0.2)
|
|
101
101
|
lint_roller (~> 1.1.0)
|
|
@@ -106,13 +106,13 @@ GEM
|
|
|
106
106
|
rubocop-ast (>= 1.49.0, < 2.0)
|
|
107
107
|
ruby-progressbar (~> 1.7)
|
|
108
108
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
109
|
-
rubocop-ast (1.
|
|
109
|
+
rubocop-ast (1.50.0)
|
|
110
110
|
parser (>= 3.3.7.2)
|
|
111
111
|
prism (~> 1.7)
|
|
112
|
-
rubocop-elegant (0.7.
|
|
112
|
+
rubocop-elegant (0.7.1)
|
|
113
113
|
lint_roller (~> 1.1)
|
|
114
114
|
rubocop (~> 1.75)
|
|
115
|
-
rubocop-minitest (0.
|
|
115
|
+
rubocop-minitest (0.40.0)
|
|
116
116
|
lint_roller (~> 1.1)
|
|
117
117
|
rubocop (>= 1.75.0, < 2.0)
|
|
118
118
|
rubocop-ast (>= 1.38.0, < 2.0)
|
|
@@ -148,7 +148,7 @@ GEM
|
|
|
148
148
|
nokogiri (~> 1.8)
|
|
149
149
|
rainbow (~> 3.0)
|
|
150
150
|
slop (~> 4.4)
|
|
151
|
-
yard (0.9.
|
|
151
|
+
yard (0.9.45)
|
|
152
152
|
|
|
153
153
|
PLATFORMS
|
|
154
154
|
aarch64-linux
|
data/README.md
CHANGED
|
@@ -217,14 +217,26 @@ end
|
|
|
217
217
|
## Query Timeouts with `Pgtk::Impatient`
|
|
218
218
|
|
|
219
219
|
To prevent queries from running indefinitely, use `Pgtk::Impatient` to enforce
|
|
220
|
-
timeouts on database operations
|
|
220
|
+
timeouts on database operations.
|
|
221
|
+
The limit itself belongs to the connection: the wire puts `statement_timeout`
|
|
222
|
+
into the startup packet, and the decorator explains what happened when
|
|
223
|
+
PostgreSQL applies it:
|
|
221
224
|
|
|
222
225
|
```ruby
|
|
223
226
|
require 'pgtk/impatient'
|
|
224
|
-
#
|
|
227
|
+
# Every connection of the pool carries a 5-second limit
|
|
228
|
+
pool = Pgtk::Pool.new(
|
|
229
|
+
Pgtk::Wire::Env.new('DATABASE_URL', options: '-c statement_timeout=5000'),
|
|
230
|
+
max: 4
|
|
231
|
+
)
|
|
232
|
+
pool.start!
|
|
233
|
+
# Wrap the pool, telling it about the very same 5 seconds
|
|
225
234
|
impatient = Pgtk::Impatient.new(pool, 5)
|
|
226
235
|
```
|
|
227
236
|
|
|
237
|
+
Both numbers must agree, since the connection stops the query, while the
|
|
238
|
+
decorator only reports it.
|
|
239
|
+
|
|
228
240
|
The impatient decorator ensures queries don't hang your application:
|
|
229
241
|
|
|
230
242
|
```ruby
|
|
@@ -243,31 +255,32 @@ You can exclude specific queries from timeout enforcement using regex patterns:
|
|
|
243
255
|
impatient = Pgtk::Impatient.new(pool, 2, /^SELECT/, /^VACUUM/)
|
|
244
256
|
```
|
|
245
257
|
|
|
246
|
-
Excluded queries
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
`
|
|
251
|
-
|
|
258
|
+
Excluded queries get a longer fallback timeout (300 seconds by default,
|
|
259
|
+
configurable via the `default:` argument), applied at the session level with
|
|
260
|
+
`SET statement_timeout` before the query runs and reset afterwards. This is how
|
|
261
|
+
statements that legitimately need much longer than a query, such as `VACUUM`,
|
|
262
|
+
`REINDEX`, or `CREATE INDEX CONCURRENTLY`, stay possible. Pass `default: 0` to
|
|
263
|
+
run excluded queries with no timeout at all:
|
|
252
264
|
|
|
253
265
|
```ruby
|
|
254
266
|
# Give excluded queries a 60-second fallback timeout
|
|
255
267
|
impatient = Pgtk::Impatient.new(pool, 2, /^VACUUM/, default: 60)
|
|
256
268
|
```
|
|
257
269
|
|
|
258
|
-
The
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
270
|
+
The limit travels in the startup packet and nowhere else. An in-band
|
|
271
|
+
`SET LOCAL statement_timeout` arrives behind a `START TRANSACTION`, and both of
|
|
272
|
+
them run with no limit in force, which is exactly the hang that `Impatient`
|
|
273
|
+
exists to prevent. A setting from the startup packet is in force from the first
|
|
274
|
+
byte of the first statement, survives a transaction-pooling PgBouncer, and
|
|
275
|
+
frees the server-side connection slot even when the client cannot deliver a
|
|
276
|
+
cancellation request.
|
|
264
277
|
|
|
265
278
|
Key features:
|
|
266
279
|
|
|
267
280
|
1. Configurable timeout in seconds for each query
|
|
268
281
|
1. Raises `Pgtk::Impatient::TooSlow` exception when timeout is exceeded
|
|
269
282
|
1. Can exclude queries matching specific patterns from timeout checks
|
|
270
|
-
1.
|
|
283
|
+
1. Sends each query in a single round trip, with the limit already in force,
|
|
271
284
|
so timeouts are enforced server-side and orphan backends do not pile up
|
|
272
285
|
|
|
273
286
|
## Query Caching with `Pgtk::Stash`
|
|
@@ -8,9 +8,9 @@ require_relative '../../pgtk'
|
|
|
8
8
|
class Pgtk::Impatient; end
|
|
9
9
|
|
|
10
10
|
# Raised by Pgtk::Impatient#exec when the query takes longer than the
|
|
11
|
-
# configured timeout. The deadline is enforced server-side
|
|
12
|
-
# +
|
|
13
|
-
# is translated into this error for the caller.
|
|
11
|
+
# configured timeout. The deadline is enforced server-side, by the
|
|
12
|
+
# +statement_timeout+ of the connection, so the underlying
|
|
13
|
+
# +PG::QueryCanceled+ is translated into this error for the caller.
|
|
14
14
|
#
|
|
15
15
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
16
16
|
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
data/lib/pgtk/impatient.rb
CHANGED
|
@@ -12,26 +12,30 @@ require_relative '../pgtk'
|
|
|
12
12
|
# It ensures that SQL queries don't run indefinitely, which helps prevent application
|
|
13
13
|
# hangs and resource exhaustion when database operations are slow or stalled.
|
|
14
14
|
#
|
|
15
|
-
# This class implements the same interface as Pool
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
15
|
+
# This class implements the same interface as Pool. The timeout itself is
|
|
16
|
+
# enforced by PostgreSQL, through the +statement_timeout+ that the wire puts
|
|
17
|
+
# into the startup packet of every connection. The limit is in force from the
|
|
18
|
+
# first byte of the first statement, which no in-band +SET+ can achieve: the
|
|
19
|
+
# +SET+ itself, and the +START TRANSACTION+ ahead of it, would travel with no
|
|
20
|
+
# limit at all. Because the setting arrives in the startup packet, it also
|
|
21
|
+
# survives a transaction-pooling PgBouncer, and it frees the server-side
|
|
22
|
+
# connection slot even when the client cannot deliver a cancellation request.
|
|
23
|
+
# When PostgreSQL terminates a query at the deadline, +TooSlow+ is raised here.
|
|
22
24
|
#
|
|
23
25
|
# Queries that match one of the +off+ regular expressions are excluded from
|
|
24
|
-
# this checking. They
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# +default: 0+ to run excluded queries with no timeout at all.
|
|
26
|
+
# this checking. They run on a single connection, guarded by a session-level
|
|
27
|
+
# +SET statement_timeout+ with the +default+ fallback timeout, which is reset
|
|
28
|
+
# afterwards. Pass +default: 0+ to run excluded queries with no timeout at all.
|
|
29
|
+
# This is how statements that take much longer than a query, such as +VACUUM+
|
|
30
|
+
# or +REINDEX+, stay possible.
|
|
30
31
|
#
|
|
31
32
|
# Basic usage:
|
|
32
33
|
#
|
|
33
|
-
# # Create
|
|
34
|
-
# pool = Pgtk::Pool.new(
|
|
34
|
+
# # Create a pool of connections that carry a two-second limit
|
|
35
|
+
# pool = Pgtk::Pool.new(
|
|
36
|
+
# Pgtk::Wire::Env.new('DATABASE_URL', options: '-c statement_timeout=2000'),
|
|
37
|
+
# max: 4
|
|
38
|
+
# )
|
|
35
39
|
# pool.start!
|
|
36
40
|
#
|
|
37
41
|
# # Wrap the pool in an impatient decorator with a 2-second timeout
|
|
@@ -69,7 +73,8 @@ class Pgtk::Impatient
|
|
|
69
73
|
# Constructor.
|
|
70
74
|
#
|
|
71
75
|
# @param [Pgtk::Pool] pool The pool to decorate
|
|
72
|
-
# @param [Integer] timeout Timeout in seconds for each SQL query
|
|
76
|
+
# @param [Integer] timeout Timeout in seconds for each SQL query, the same
|
|
77
|
+
# one that the connections of the pool carry as +statement_timeout+
|
|
73
78
|
# @param [Array<Regex>] off List of regex to exclude queries from checking
|
|
74
79
|
# @param [Integer] default Fallback timeout in seconds for excluded queries (0 = no timeout)
|
|
75
80
|
def initialize(pool, timeout, *off, default: 300)
|
|
@@ -103,18 +108,14 @@ class Pgtk::Impatient
|
|
|
103
108
|
|
|
104
109
|
# Execute a SQL query with a server-side timeout.
|
|
105
110
|
#
|
|
106
|
-
# The query
|
|
107
|
-
# +
|
|
108
|
-
#
|
|
109
|
-
# even when the client cannot deliver a cancellation request (for example,
|
|
110
|
-
# behind a transaction-pool PgBouncer). When the deadline fires, the
|
|
111
|
-
# underlying +PG::QueryCanceled+ is translated to +TooSlow+.
|
|
111
|
+
# The query travels alone, in a single round trip, and the limit is the
|
|
112
|
+
# +statement_timeout+ that the connection already carries. When the deadline
|
|
113
|
+
# fires, the underlying +PG::QueryCanceled+ is translated to +TooSlow+.
|
|
112
114
|
#
|
|
113
|
-
# Queries matching one of the +off+ regular expressions
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
# statements that cannot run inside a transaction, such as +VACUUM+ or
|
|
115
|
+
# Queries matching one of the +off+ regular expressions are guarded instead
|
|
116
|
+
# by a session-level +SET statement_timeout+ (the +default+ fallback, reset
|
|
117
|
+
# afterwards), or by no timeout at all when +default+ is zero. This keeps
|
|
118
|
+
# statements that need much longer than a query, such as +VACUUM+ or
|
|
118
119
|
# +REINDEX+, working as expected.
|
|
119
120
|
#
|
|
120
121
|
# @param [String, Array] query The SQL query with params inside (possibly)
|
|
@@ -124,20 +125,14 @@ class Pgtk::Impatient
|
|
|
124
125
|
def exec(query, *args)
|
|
125
126
|
sql = query.is_a?(Array) ? query.join(' ') : query
|
|
126
127
|
if @off.any? { |re| re.match?(sql) }
|
|
127
|
-
ms = Integer(@default * 1000)
|
|
128
|
-
return @pool.exec(sql, *args) if ms.zero?
|
|
129
128
|
return @pool.session do |t|
|
|
130
|
-
t.exec("SET statement_timeout = #{
|
|
129
|
+
t.exec("SET statement_timeout = #{Integer(@default * 1000)}")
|
|
131
130
|
t.exec(sql, *args).tap { t.exec('RESET statement_timeout') }
|
|
132
131
|
end
|
|
133
132
|
end
|
|
134
133
|
start = Time.now
|
|
135
|
-
ms = [Integer(@timeout * 1000), 1].max
|
|
136
134
|
begin
|
|
137
|
-
@pool.
|
|
138
|
-
t.exec("SET LOCAL statement_timeout = #{ms}")
|
|
139
|
-
t.exec(sql, *args)
|
|
140
|
-
end
|
|
135
|
+
@pool.exec(sql, *args)
|
|
141
136
|
rescue PG::QueryCanceled
|
|
142
137
|
raise(
|
|
143
138
|
TooSlow, [
|
data/lib/pgtk/version.rb
CHANGED
data/resources/pom.xml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
2
|
<!--
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
* SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
5
|
-->
|
|
6
6
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
7
7
|
<modelVersion>4.0.0</modelVersion>
|
|
@@ -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.13</postgresql.version>
|
|
14
14
|
<liquibase.version>5.0.3</liquibase.version>
|
|
15
15
|
</properties>
|
|
16
16
|
<dependencies>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
2
|
<!--
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
* SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
5
|
-->
|
|
6
6
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd" logicalFilePath="001-initial-schema.xml">
|
|
7
7
|
<changeSet id="01" author="yegor256">
|
data/test-resources/master.xml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
2
|
<!--
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
* SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
5
|
-->
|
|
6
6
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd">
|
|
7
7
|
<includeAll path="2019"/>
|