trellis-pg 0.1.0.pre.alpha → 0.1.0.pre.beta
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/README.md +27 -4
- data/ext/trellis_ruby/Cargo.lock +6 -5
- data/ext/trellis_ruby/Cargo.toml +3 -3
- data/ext/trellis_ruby/src/engine.rs +1 -1
- data/ext/trellis_ruby/src/lib.rs +20 -9
- data/lib/trellis/error.rb +1 -1
- data/lib/trellis/instance.rb +27 -6
- data/lib/trellis/pg.rb +1 -1
- data/lib/trellis/railtie.rb +1 -1
- data/lib/trellis/schema_dump.rb +2 -2
- data/lib/trellis/values.rb +3 -3
- data/lib/trellis/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: 040d3074f1223a81e28989c5d0a2b7825e84806ea0b088894fc1ea0daaf68312
|
|
4
|
+
data.tar.gz: e5bbfe7ce9446bd67d7b01666e555255a6ab39687c2c7415080789ddb8a934ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0f56d84b4f68c1619f480040e659513872e4c6d9f93358eff375b400b7e5b22eb9c2c29080c5433cc24ae9ab33cb8467f735c151e36c6634fea094fa2e04a5de
|
|
7
|
+
data.tar.gz: cc1c7b5b6ed393b91c6ed3a866a0172ddb085b297e4c7914ec4a3de56f32163734449d68a2d28e40192bc147c3b797cc16222ff4e7d2b943fbb97a84ac498847
|
data/README.md
CHANGED
|
@@ -144,10 +144,33 @@ Trellis.shutdown
|
|
|
144
144
|
| `staging:` | `false` | run the staging worker (installs change capture, starts backfills) here |
|
|
145
145
|
| `drain_threads:` | `0` | threads applying staged changes to the targets |
|
|
146
146
|
| `worker_threads:` | `2` | the threads of each Rust runtime a handle owns (its calls', and its background client's), invisible to Ruby's own sizing |
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
| `max_call_pool_size:` | `20` | the most connections the handle's call pool opens; the background pool is sized by `staging:` and `drain_threads:` |
|
|
148
|
+
| `call_pool_wait_timeout_ms:` | `30_000` | how long a call waits for a free call-pool connection before a `ConnectivityError` |
|
|
149
|
+
|
|
150
|
+
The defaults run nothing in the background. Some process in a fleet must set
|
|
151
|
+
`staging: true` and some process must run drain threads, or no definition ever
|
|
152
|
+
reaches `:live`. Any number of processes may set `staging: true`: one takes the
|
|
153
|
+
producer lock and leads, the rest stand by and take over when it goes away, so a
|
|
154
|
+
rolling deploy needs no operator step. Under a preforking Puma with
|
|
155
|
+
`TRELLIS_WORKER=1`, every Puma worker connects with `staging: true`, so all but
|
|
156
|
+
one stand by; that is harmless, and each polls the lock every 5 seconds (see
|
|
157
|
+
[Rolling deploys](../../docs/embedding.md#rolling-deploys)).
|
|
158
|
+
|
|
159
|
+
### Resources
|
|
160
|
+
|
|
161
|
+
At the defaults, a handle holds up to 20 Postgres connections (its call pool,
|
|
162
|
+
opened as calls need them and closed after 60 seconds idle) and 3 threads. A
|
|
163
|
+
worker handle (`staging: true, drain_threads: N`) also owns a background pool
|
|
164
|
+
of up to `2N + 5` connections (`2N + 4` without staging, `1` with staging
|
|
165
|
+
alone), closed after 60 seconds idle, and two connections that stay open: the
|
|
166
|
+
staging session and one `LISTEN` shared by its drain threads. It holds 6
|
|
167
|
+
threads: drain threads are tasks on its runtime, not OS threads. Its memory is
|
|
168
|
+
bounded by row counts, not bytes: about `N` × 100,000 changed rows' images at
|
|
169
|
+
its peak. `max_call_pool_size:` lowers the 20 (the background pool is sized by
|
|
170
|
+
the handle's roles, not by an option), and `worker_threads:` sets the threads.
|
|
171
|
+
Every handle (each `Trellis::Instance` too) costs this again. The full
|
|
172
|
+
accounting is in
|
|
173
|
+
[`docs/embedding.md`](../../docs/embedding.md#what-a-handle-uses).
|
|
151
174
|
|
|
152
175
|
### Several instances
|
|
153
176
|
|
data/ext/trellis_ruby/Cargo.lock
CHANGED
|
@@ -1729,9 +1729,10 @@ dependencies = [
|
|
|
1729
1729
|
|
|
1730
1730
|
[[package]]
|
|
1731
1731
|
name = "trellis"
|
|
1732
|
-
version = "0.1.0-
|
|
1733
|
-
source = "git+https://github.com/salesforce-misc/trellis?rev=
|
|
1732
|
+
version = "0.1.0-beta"
|
|
1733
|
+
source = "git+https://github.com/salesforce-misc/trellis?rev=1820fd7db091e540990d1efe190a687fb4dfeb59#1820fd7db091e540990d1efe190a687fb4dfeb59"
|
|
1734
1734
|
dependencies = [
|
|
1735
|
+
"deadpool",
|
|
1735
1736
|
"deadpool-postgres",
|
|
1736
1737
|
"getrandom 0.4.3",
|
|
1737
1738
|
"metrics",
|
|
@@ -1751,8 +1752,8 @@ dependencies = [
|
|
|
1751
1752
|
|
|
1752
1753
|
[[package]]
|
|
1753
1754
|
name = "trellis-embed"
|
|
1754
|
-
version = "0.1.0-
|
|
1755
|
-
source = "git+https://github.com/salesforce-misc/trellis?rev=
|
|
1755
|
+
version = "0.1.0-beta"
|
|
1756
|
+
source = "git+https://github.com/salesforce-misc/trellis?rev=1820fd7db091e540990d1efe190a687fb4dfeb59#1820fd7db091e540990d1efe190a687fb4dfeb59"
|
|
1756
1757
|
dependencies = [
|
|
1757
1758
|
"tracing",
|
|
1758
1759
|
"trellis",
|
|
@@ -1760,7 +1761,7 @@ dependencies = [
|
|
|
1760
1761
|
|
|
1761
1762
|
[[package]]
|
|
1762
1763
|
name = "trellis_ruby"
|
|
1763
|
-
version = "0.1.0-
|
|
1764
|
+
version = "0.1.0-beta"
|
|
1764
1765
|
dependencies = [
|
|
1765
1766
|
"magnus",
|
|
1766
1767
|
"rb-sys",
|
data/ext/trellis_ruby/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "trellis_ruby"
|
|
3
|
-
version = "0.1.0-
|
|
3
|
+
version = "0.1.0-beta"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
publish = false
|
|
6
6
|
|
|
@@ -28,8 +28,8 @@ doctest = false
|
|
|
28
28
|
ruby = ["dep:magnus", "dep:rb-sys"]
|
|
29
29
|
|
|
30
30
|
[dependencies]
|
|
31
|
-
trellis = { git = "https://github.com/salesforce-misc/trellis", rev = "
|
|
32
|
-
trellis-embed = { git = "https://github.com/salesforce-misc/trellis", rev = "
|
|
31
|
+
trellis = { git = "https://github.com/salesforce-misc/trellis", rev = "1820fd7db091e540990d1efe190a687fb4dfeb59" }
|
|
32
|
+
trellis-embed = { git = "https://github.com/salesforce-misc/trellis", rev = "1820fd7db091e540990d1efe190a687fb4dfeb59" }
|
|
33
33
|
magnus = { version = "0.9", optional = true, default-features = false, features = ["rb-sys"] }
|
|
34
34
|
rb-sys = { version = "0.9", optional = true }
|
|
35
35
|
|
data/ext/trellis_ruby/src/lib.rs
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
//! and raises `Trellis::ForkedHandleError`, and a forked copy is never
|
|
25
25
|
//! dropped either (see [`Handle`]'s `Drop`).
|
|
26
26
|
//! - **A process forked while its parent had an engine running can't
|
|
27
|
-
//! connect one of its own
|
|
27
|
+
//! connect one of its own**: it may have inherited a lock one
|
|
28
28
|
//! of the parent's engine threads held, which nothing in it can release.
|
|
29
29
|
//! [`connect`] raises `Trellis::ForkedHandleError` instead of risking the
|
|
30
30
|
//! hang. See the [`engine`] module.
|
|
@@ -331,7 +331,7 @@ impl Handle {
|
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
/// Pauses now the readers of every table whose capture was changed
|
|
334
|
-
/// outside Trellis
|
|
334
|
+
/// outside Trellis, and returns the tables.
|
|
335
335
|
fn detect_capture_disruptions(ruby: &Ruby, rb_self: &Self) -> Result<Vec<String>, Error> {
|
|
336
336
|
rb_self.call(ruby, BlockingTrellis::start_detect_capture_disruptions)
|
|
337
337
|
}
|
|
@@ -388,18 +388,21 @@ impl Handle {
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
/// The configuration the handle connected with, all but the connection
|
|
391
|
-
/// string (see [`PlainConfig`]).
|
|
391
|
+
/// string, and its call pool's limits (see [`PlainConfig`]).
|
|
392
392
|
fn config(ruby: &Ruby, rb_self: &Self) -> Result<RHash, Error> {
|
|
393
|
-
let config = rb_self.with_engine(ruby, |trellis| PlainConfig::from(trellis
|
|
393
|
+
let config = rb_self.with_engine(ruby, |trellis| PlainConfig::from(trellis))?;
|
|
394
394
|
record(
|
|
395
395
|
ruby,
|
|
396
396
|
[
|
|
397
397
|
("schema", ruby.into_value(config.schema)),
|
|
398
398
|
("target_schema", ruby.into_value(config.target_schema)),
|
|
399
|
-
("pool_max_size", ruby.into_value(config.pool_max_size)),
|
|
400
399
|
(
|
|
401
|
-
"
|
|
402
|
-
ruby.into_value(config.
|
|
400
|
+
"max_call_pool_size",
|
|
401
|
+
ruby.into_value(config.max_call_pool_size),
|
|
402
|
+
),
|
|
403
|
+
(
|
|
404
|
+
"call_pool_wait_timeout_ms",
|
|
405
|
+
ruby.into_value(config.call_pool_wait_timeout_ms),
|
|
403
406
|
),
|
|
404
407
|
],
|
|
405
408
|
)
|
|
@@ -432,7 +435,11 @@ impl Handle {
|
|
|
432
435
|
/// `lib/trellis/pg.rb`'s to document, and nothing falls back to the environment.
|
|
433
436
|
///
|
|
434
437
|
/// Raises `Trellis::ForkedHandleError`, before starting anything, in a
|
|
435
|
-
/// process forked while its parent had an engine running
|
|
438
|
+
/// process forked while its parent had an engine running.
|
|
439
|
+
#[allow(
|
|
440
|
+
clippy::too_many_arguments,
|
|
441
|
+
reason = "`function!` binds each Ruby argument to its own parameter"
|
|
442
|
+
)]
|
|
436
443
|
fn connect(
|
|
437
444
|
ruby: &Ruby,
|
|
438
445
|
url: String,
|
|
@@ -441,6 +448,8 @@ fn connect(
|
|
|
441
448
|
staging: bool,
|
|
442
449
|
drain_threads: usize,
|
|
443
450
|
worker_threads: usize,
|
|
451
|
+
max_call_pool_size: usize,
|
|
452
|
+
call_pool_wait_timeout_ms: u64,
|
|
444
453
|
) -> Result<Handle, Error> {
|
|
445
454
|
engine::check().map_err(|err| forked_handle_error(ruby, forked_while_running(&err)))?;
|
|
446
455
|
// Connecting can wait on the database (a staging worker installs its
|
|
@@ -460,6 +469,8 @@ fn connect(
|
|
|
460
469
|
staging,
|
|
461
470
|
drain_threads,
|
|
462
471
|
worker_threads: Some(worker_threads),
|
|
472
|
+
max_call_pool_size,
|
|
473
|
+
call_pool_wait_timeout: Duration::from_millis(call_pool_wait_timeout_ms),
|
|
463
474
|
..Default::default()
|
|
464
475
|
};
|
|
465
476
|
// Can't fail after `check` passed (a process's answer never
|
|
@@ -1229,7 +1240,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
1229
1240
|
}
|
|
1230
1241
|
|
|
1231
1242
|
let native = ruby.define_module("Trellis")?.define_module("Native")?;
|
|
1232
|
-
native.define_module_function("connect", function!(connect,
|
|
1243
|
+
native.define_module_function("connect", function!(connect, 8))?;
|
|
1233
1244
|
native.define_module_function("error_codes", function!(error_codes, 0))?;
|
|
1234
1245
|
native.define_module_function("status_names", function!(status_names, 0))?;
|
|
1235
1246
|
native.define_module_function("quarantine_states", function!(quarantine_states, 0))?;
|
data/lib/trellis/error.rb
CHANGED
|
@@ -82,7 +82,7 @@ module Trellis
|
|
|
82
82
|
#
|
|
83
83
|
# Also Trellis.connect's error in a process forked while its parent had a
|
|
84
84
|
# handle running, which may have inherited a lock that handle's threads
|
|
85
|
-
# held
|
|
85
|
+
# held: shut down before forking (Puma's before_fork).
|
|
86
86
|
class ForkedHandleError < ValidationError
|
|
87
87
|
end
|
|
88
88
|
|
data/lib/trellis/instance.rb
CHANGED
|
@@ -98,7 +98,7 @@ module Trellis
|
|
|
98
98
|
# forked child's calls on an instance it inherited raise
|
|
99
99
|
# Trellis::ForkedHandleError, and its shutdown leaves the parent's handle
|
|
100
100
|
# alone and does nothing. So does Instance.connect in a child forked while
|
|
101
|
-
# any instance of its parent was running
|
|
101
|
+
# any instance of its parent was running.
|
|
102
102
|
class Instance
|
|
103
103
|
extend Checks
|
|
104
104
|
include Checks
|
|
@@ -111,7 +111,7 @@ module Trellis
|
|
|
111
111
|
# Connects a new instance and returns it. Raises ForkedHandleError in
|
|
112
112
|
# a process forked while its parent had an instance running
|
|
113
113
|
# (connected, connecting, or not yet fully shut down): it may have
|
|
114
|
-
# inherited a lock one of that instance's threads held
|
|
114
|
+
# inherited a lock one of that instance's threads held.
|
|
115
115
|
#
|
|
116
116
|
# - url: where the database is, as a libpq connection string
|
|
117
117
|
# ("host=... dbname=...") or URL. Nothing is read from the environment.
|
|
@@ -121,8 +121,10 @@ module Trellis
|
|
|
121
121
|
# - target_schema: the schema a bare target table name is created in.
|
|
122
122
|
# - staging: whether this process runs the instance's staging worker,
|
|
123
123
|
# which installs change capture on the source tables and starts each
|
|
124
|
-
# new transform's backfill.
|
|
125
|
-
#
|
|
124
|
+
# new transform's backfill. Any number of processes may set it: one
|
|
125
|
+
# takes the instance's producer lock and leads, the rest stand by and
|
|
126
|
+
# take over when it goes away, so a rolling deploy needs no
|
|
127
|
+
# operator step.
|
|
126
128
|
# - drain_threads: how many threads apply staged changes to the targets.
|
|
127
129
|
# Some process must run at least one per instance, or no definition
|
|
128
130
|
# reaches :live.
|
|
@@ -130,13 +132,20 @@ module Trellis
|
|
|
130
132
|
# instance owns (its calls', and its background client's), invisible
|
|
131
133
|
# to Ruby's own thread sizing, so kept small and explicit. Each
|
|
132
134
|
# instance has its own; nothing is shared between instances.
|
|
135
|
+
# - max_call_pool_size: the most connections this instance's call pool
|
|
136
|
+
# (its API calls) opens at once. Connections open as calls need them
|
|
137
|
+
# and close after 60 seconds idle. The background client's pool is
|
|
138
|
+
# sized by staging and drain_threads, not by this.
|
|
139
|
+
# - call_pool_wait_timeout_ms: how long a call waits for a free call-pool
|
|
140
|
+
# connection before failing with a ConnectivityError.
|
|
133
141
|
#
|
|
134
142
|
# The defaults (staging: false, drain_threads: 0) run nothing in the
|
|
135
143
|
# background, which is what a migration step or a console wants. The
|
|
136
144
|
# staging worker reads Trellis's tables as it starts, so a staging: true
|
|
137
145
|
# connect to an unmigrated schema fails: migrate first.
|
|
138
146
|
def connect(url:, schema: "trellis", target_schema: "public", staging: false,
|
|
139
|
-
drain_threads: 0, worker_threads: 2
|
|
147
|
+
drain_threads: 0, worker_threads: 2, max_call_pool_size: 20,
|
|
148
|
+
call_pool_wait_timeout_ms: 30_000)
|
|
140
149
|
string_option!(:url, url)
|
|
141
150
|
string_option!(:schema, schema)
|
|
142
151
|
string_option!(:target_schema, target_schema)
|
|
@@ -152,7 +161,19 @@ module Trellis
|
|
|
152
161
|
"worker_threads must be a positive Integer, got: #{worker_threads.inspect}"
|
|
153
162
|
end
|
|
154
163
|
|
|
155
|
-
|
|
164
|
+
unless max_call_pool_size.is_a?(Integer) && max_call_pool_size >= 1
|
|
165
|
+
raise ValidationError,
|
|
166
|
+
"max_call_pool_size must be a positive Integer, got: #{max_call_pool_size.inspect}"
|
|
167
|
+
end
|
|
168
|
+
unless call_pool_wait_timeout_ms.is_a?(Integer) &&
|
|
169
|
+
call_pool_wait_timeout_ms.between?(0, MAX_TIMEOUT_MS)
|
|
170
|
+
raise ValidationError,
|
|
171
|
+
"call_pool_wait_timeout_ms must be a non-negative Integer, got: " \
|
|
172
|
+
"#{call_pool_wait_timeout_ms.inspect}"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
handle = Native.connect(url, schema, target_schema, staging, drain_threads, worker_threads,
|
|
176
|
+
max_call_pool_size, call_pool_wait_timeout_ms)
|
|
156
177
|
instance = new(handle, schema)
|
|
157
178
|
register(instance)
|
|
158
179
|
instance
|
data/lib/trellis/pg.rb
CHANGED
|
@@ -71,7 +71,7 @@ require_relative "instance"
|
|
|
71
71
|
# calls on a handle it inherited raise Trellis::ForkedHandleError, except
|
|
72
72
|
# Trellis.shutdown, which leaves the parent's handle alone and does nothing.
|
|
73
73
|
# So does Trellis.connect in a child forked while its parent's handle was
|
|
74
|
-
# running
|
|
74
|
+
# running.
|
|
75
75
|
module Trellis
|
|
76
76
|
@default = nil
|
|
77
77
|
@lock = Mutex.new
|
data/lib/trellis/railtie.rb
CHANGED
|
@@ -49,7 +49,7 @@ module Trellis
|
|
|
49
49
|
# - config.trellis.connect_on_boot = false, for a server that forks with
|
|
50
50
|
# no hook to shut the handle down first (see below).
|
|
51
51
|
#
|
|
52
|
-
# A handle doesn't survive fork
|
|
52
|
+
# A handle doesn't survive fork. A preforking server that
|
|
53
53
|
# loads the app before forking (Puma's preload_app!, on by default with
|
|
54
54
|
# more than one worker) must shut the boot handle down before it forks,
|
|
55
55
|
# and connect one in each worker:
|
data/lib/trellis/schema_dump.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Trellis
|
|
|
10
10
|
# appends the `INSERT`s that describe how the schema came to be. A database
|
|
11
11
|
# built from the file (`db:schema:load`, `db:prepare`, the test database
|
|
12
12
|
# `maintain_test_schema!` loads) then has every transform defined, with no
|
|
13
|
-
# Trellis step to run: Trellis's worker finds the definitions unbuilt and builds them (
|
|
13
|
+
# Trellis step to run: Trellis's worker finds the definitions unbuilt and builds them (ADR-0010).
|
|
14
14
|
#
|
|
15
15
|
# Needs `config.active_record.schema_format = :sql`: a `schema.rb` can't
|
|
16
16
|
# carry another schema's tables or triggers. With `:ruby`, the dump raises
|
|
@@ -156,7 +156,7 @@ module Trellis
|
|
|
156
156
|
|
|
157
157
|
# The keywords of a libpq connection string or URI. A failure names
|
|
158
158
|
# what was being read and never the string or libpq's message, which
|
|
159
|
-
# can quote part of a password (as Trellis's own parse does
|
|
159
|
+
# can quote part of a password (as Trellis's own parse does).
|
|
160
160
|
def conninfo(url, what)
|
|
161
161
|
PG::Connection.conninfo_parse(url.to_s).to_h { |entry| [entry[:keyword], entry[:val]] }
|
|
162
162
|
rescue PG::Error
|
data/lib/trellis/values.rb
CHANGED
|
@@ -376,11 +376,11 @@ module Trellis
|
|
|
376
376
|
Divergence = Data.define(:kind, :key, :column, :persisted, :recomputed, :table, :detail)
|
|
377
377
|
|
|
378
378
|
# The configuration this process's handle connected with, as Trellis.config
|
|
379
|
-
# returns it: connect's schema and target_schema, and the
|
|
380
|
-
#
|
|
379
|
+
# returns it: connect's schema and target_schema, and the handle's call pool
|
|
380
|
+
# as it was built: max_call_pool_size and call_pool_wait_timeout_ms.
|
|
381
381
|
#
|
|
382
382
|
# Not the url: a connection string can carry a password, and a Config is
|
|
383
383
|
# the kind of value that ends up in a log line whole. The caller already
|
|
384
384
|
# has the url it connected with.
|
|
385
|
-
Config = Data.define(:schema, :target_schema, :
|
|
385
|
+
Config = Data.define(:schema, :target_schema, :max_call_pool_size, :call_pool_wait_timeout_ms)
|
|
386
386
|
end
|
data/lib/trellis/version.rb
CHANGED