trellis-pg 0.1.0.pre.alpha → 0.1.0.pre.gamma
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 +56 -3
- 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 +35 -9
- data/lib/trellis/error.rb +1 -1
- data/lib/trellis/instance.rb +27 -6
- data/lib/trellis/metrics.rb +38 -0
- data/lib/trellis/pg.rb +3 -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 +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8839af0acc8f206000a1348bbba21b4ea1d06784bf7d677c0244f0c630863c00
|
|
4
|
+
data.tar.gz: 77f8ffb6fc607c81fcbb614a6238aa9c21770616a525aeda1df4a007247c0d59
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fc0f00f2deb17dcbb647ca2ff60d0da513e77987fc9d4206cf1c4f723412284d44f337120dc1c667a7d6149a07f144d97825a6f9cf473d2f1c3f18630e5c1e00
|
|
7
|
+
data.tar.gz: 8c00d355d02a8437815fedfa97e519c4ce2fe576e46a4614c273bd1c290c89729bc1f83e1c0176b315f5a26a1566ab74a01b5453b59f25a411308e415c93674e
|
data/README.md
CHANGED
|
@@ -103,6 +103,9 @@ Nothing in the binding uses it.
|
|
|
103
103
|
and `self_check_job` returns `nil` for the replaced job or one whose
|
|
104
104
|
transform was dropped. With no drain worker anywhere in the fleet a job
|
|
105
105
|
stays `:queued`.
|
|
106
|
+
- `Trellis::Metrics.render_prometheus`: the process-wide metrics registry as
|
|
107
|
+
Prometheus text, for a `/metrics` route the app already serves (the
|
|
108
|
+
binding opens no port; see [Metrics](#metrics)). It needs no handle.
|
|
106
109
|
|
|
107
110
|
```ruby
|
|
108
111
|
require "trellis/pg"
|
|
@@ -144,10 +147,59 @@ Trellis.shutdown
|
|
|
144
147
|
| `staging:` | `false` | run the staging worker (installs change capture, starts backfills) here |
|
|
145
148
|
| `drain_threads:` | `0` | threads applying staged changes to the targets |
|
|
146
149
|
| `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 |
|
|
150
|
+
| `max_call_pool_size:` | `20` | the most connections the handle's call pool opens; the background pool is sized by `staging:` and `drain_threads:` |
|
|
151
|
+
| `call_pool_wait_timeout_ms:` | `30_000` | how long a call waits for a free call-pool connection before a `ConnectivityError` |
|
|
152
|
+
|
|
153
|
+
The defaults run nothing in the background. Some process in a fleet must set
|
|
154
|
+
`staging: true` and some process must run drain threads, or no definition ever
|
|
155
|
+
reaches `:live`. Any number of processes may set `staging: true`: one takes the
|
|
156
|
+
producer lock and leads, the rest stand by and take over when it goes away, so a
|
|
157
|
+
rolling deploy needs no operator step. Under a preforking Puma with
|
|
158
|
+
`TRELLIS_WORKER=1`, every Puma worker connects with `staging: true`, so all but
|
|
159
|
+
one stand by; that is harmless, and each polls the lock every 5 seconds (see
|
|
160
|
+
[Rolling deploys](../../docs/embedding.md#rolling-deploys)).
|
|
161
|
+
|
|
162
|
+
### Resources
|
|
163
|
+
|
|
164
|
+
At the defaults, a handle holds up to 20 Postgres connections (its call pool,
|
|
165
|
+
opened as calls need them and closed after 60 seconds idle) and 3 threads. A
|
|
166
|
+
worker handle (`staging: true, drain_threads: N`) also owns a background pool
|
|
167
|
+
of up to `2N + 5` connections (`2N + 4` without staging, `1` with staging
|
|
168
|
+
alone), closed after 60 seconds idle, and two connections that stay open: the
|
|
169
|
+
staging session and one `LISTEN` shared by its drain threads. It holds 6
|
|
170
|
+
threads: drain threads are tasks on its runtime, not OS threads. Its memory is
|
|
171
|
+
bounded by row counts, not bytes: about `N` × 100,000 changed rows' images at
|
|
172
|
+
its peak. `max_call_pool_size:` lowers the 20 (the background pool is sized by
|
|
173
|
+
the handle's roles, not by an option), and `worker_threads:` sets the threads.
|
|
174
|
+
Every handle (each `Trellis::Instance` too) costs this again. The full
|
|
175
|
+
accounting is in
|
|
176
|
+
[`docs/embedding.md`](../../docs/embedding.md#what-a-handle-uses).
|
|
177
|
+
|
|
178
|
+
### Metrics
|
|
179
|
+
|
|
180
|
+
The engine's metrics live in one registry per process, holding the series of
|
|
181
|
+
every handle in it; `Trellis::Metrics.render_prometheus` renders them in
|
|
182
|
+
Prometheus's text format. Every series carries a `trellis_instance` label
|
|
183
|
+
(`<database>/<catalog schema>`), so instances in one process stay apart, and
|
|
184
|
+
`trellis_instance_up` is 1 while an instance's handle runs and 0 after it
|
|
185
|
+
shuts down. The metrics themselves are listed in
|
|
186
|
+
[`docs/observability.md`](../../docs/observability.md).
|
|
187
|
+
|
|
188
|
+
The binding opens no port, so serve the text from a route of the app's own:
|
|
147
189
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
190
|
+
```ruby
|
|
191
|
+
# config/routes.rb
|
|
192
|
+
get "/metrics", to: proc {
|
|
193
|
+
[200, { "content-type" => "text/plain; version=0.0.4" }, [Trellis::Metrics.render_prometheus]]
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Under a forking server each worker process has its own registry, holding
|
|
198
|
+
only its own handles' series, so Prometheus scrapes each worker (or the
|
|
199
|
+
worker handle, `staging: true`, runs in a process of its own that it
|
|
200
|
+
scrapes). A process forked while its parent had a handle running can't
|
|
201
|
+
render the registry, as it can't connect: it raises
|
|
202
|
+
`Trellis::ForkedHandleError` (see [Forking servers](#forking-servers)).
|
|
151
203
|
|
|
152
204
|
### Several instances
|
|
153
205
|
|
|
@@ -501,6 +553,7 @@ source rows; a test database has none, so that is immediate. Runtime state
|
|
|
501
553
|
## Layout
|
|
502
554
|
|
|
503
555
|
- `lib/`: the `Trellis` module and `Trellis::Instance`, its error classes and its `Data` values,
|
|
556
|
+
`Trellis::Metrics`,
|
|
504
557
|
the Rails integration (`trellis/railtie.rb`, `trellis/migration.rb`),
|
|
505
558
|
loaded only in an app that has Rails, and the test helpers
|
|
506
559
|
(`trellis/test_support.rb`), loaded only when required.
|
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-gamma"
|
|
1733
|
+
source = "git+https://github.com/salesforce-misc/trellis?rev=c271ef149159d9309174ca476d792c89128bce99#c271ef149159d9309174ca476d792c89128bce99"
|
|
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-gamma"
|
|
1756
|
+
source = "git+https://github.com/salesforce-misc/trellis?rev=c271ef149159d9309174ca476d792c89128bce99#c271ef149159d9309174ca476d792c89128bce99"
|
|
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-gamma"
|
|
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-gamma"
|
|
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 = "c271ef149159d9309174ca476d792c89128bce99" }
|
|
32
|
+
trellis-embed = { git = "https://github.com/salesforce-misc/trellis", rev = "c271ef149159d9309174ca476d792c89128bce99" }
|
|
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
|
|
@@ -1220,6 +1231,20 @@ fn statement_kinds() -> Vec<&'static str> {
|
|
|
1220
1231
|
.collect()
|
|
1221
1232
|
}
|
|
1222
1233
|
|
|
1234
|
+
/// The process-wide metrics registry in Prometheus text exposition format:
|
|
1235
|
+
/// `trellis::Metrics::render_prometheus`, the series of every handle in this
|
|
1236
|
+
/// process. It needs no handle.
|
|
1237
|
+
///
|
|
1238
|
+
/// The registry sits behind a lock the engine's threads take, so a process
|
|
1239
|
+
/// forked while its parent had an engine running may have inherited it held:
|
|
1240
|
+
/// rendering there raises `Trellis::ForkedHandleError`, as [`connect`] does,
|
|
1241
|
+
/// rather than risk the hang. Rendering is a short walk of the registry,
|
|
1242
|
+
/// so it keeps the GVL.
|
|
1243
|
+
fn render_prometheus(ruby: &Ruby) -> Result<String, Error> {
|
|
1244
|
+
engine::check().map_err(|err| forked_handle_error(ruby, forked_while_running(&err)))?;
|
|
1245
|
+
Ok(trellis::Metrics::new().render_prometheus())
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1223
1248
|
#[magnus::init]
|
|
1224
1249
|
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
1225
1250
|
// Interns every symbol a result can carry up front, from the closed
|
|
@@ -1229,7 +1254,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
1229
1254
|
}
|
|
1230
1255
|
|
|
1231
1256
|
let native = ruby.define_module("Trellis")?.define_module("Native")?;
|
|
1232
|
-
native.define_module_function("connect", function!(connect,
|
|
1257
|
+
native.define_module_function("connect", function!(connect, 8))?;
|
|
1233
1258
|
native.define_module_function("error_codes", function!(error_codes, 0))?;
|
|
1234
1259
|
native.define_module_function("status_names", function!(status_names, 0))?;
|
|
1235
1260
|
native.define_module_function("quarantine_states", function!(quarantine_states, 0))?;
|
|
@@ -1241,6 +1266,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
1241
1266
|
native.define_module_function("capture_failure_kinds", function!(capture_failure_kinds, 0))?;
|
|
1242
1267
|
native.define_module_function("index_roles", function!(index_roles, 0))?;
|
|
1243
1268
|
native.define_module_function("statement_kinds", function!(statement_kinds, 0))?;
|
|
1269
|
+
native.define_module_function("render_prometheus", function!(render_prometheus, 0))?;
|
|
1244
1270
|
|
|
1245
1271
|
let handle = native.define_class("Handle", ruby.class_object())?;
|
|
1246
1272
|
handle.define_method("owner_pid", method!(Handle::owner_pid, 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
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Trellis
|
|
4
|
+
# The engine's metrics, in Prometheus text exposition format: the Rust
|
|
5
|
+
# crate's trellis::Metrics#render_prometheus
|
|
6
|
+
# (docs/decisions/0009-observability-decisions.md).
|
|
7
|
+
#
|
|
8
|
+
# The registry is process-wide: one body holds the series of every Trellis
|
|
9
|
+
# handle in this process, so it needs no handle and works before
|
|
10
|
+
# Trellis.connect and after Trellis.shutdown. Every series carries a
|
|
11
|
+
# trellis_instance="<database>/<catalog schema>" label, so instances in one
|
|
12
|
+
# process stay apart. trellis_instance_up is 1 while an instance has a
|
|
13
|
+
# running handle and 0 after its last one shuts down; the other gauges of a
|
|
14
|
+
# stopped instance disappear after 60 seconds. See docs/observability.md.
|
|
15
|
+
#
|
|
16
|
+
# The binding opens no port. Serve the text from a route the app already
|
|
17
|
+
# has, for example in Rails:
|
|
18
|
+
#
|
|
19
|
+
# # config/routes.rb
|
|
20
|
+
# get "/metrics", to: proc {
|
|
21
|
+
# [200, { "content-type" => "text/plain; version=0.0.4" }, [Trellis::Metrics.render_prometheus]]
|
|
22
|
+
# }
|
|
23
|
+
#
|
|
24
|
+
# In a forking server each worker process has its own registry, holding
|
|
25
|
+
# only its own handles' series, so scrape every worker (or run the worker
|
|
26
|
+
# handle, staging: true, in a process of its own and scrape that).
|
|
27
|
+
#
|
|
28
|
+
# A process forked while its parent had an engine running may have
|
|
29
|
+
# inherited the registry's lock held, so rendering there raises
|
|
30
|
+
# Trellis::ForkedHandleError, as Trellis.connect does.
|
|
31
|
+
module Metrics
|
|
32
|
+
# Renders the registry's current contents: # HELP and # TYPE lines, then
|
|
33
|
+
# each series' samples.
|
|
34
|
+
def self.render_prometheus
|
|
35
|
+
Native.render_prometheus
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/trellis/pg.rb
CHANGED
|
@@ -24,6 +24,7 @@ else
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
require_relative "instance"
|
|
27
|
+
require_relative "metrics"
|
|
27
28
|
|
|
28
29
|
# Embedded Trellis for Ruby apps: a native extension over the `trellis`
|
|
29
30
|
# crate's BlockingTrellis, so a Rails app can define and run streaming
|
|
@@ -48,6 +49,7 @@ require_relative "instance"
|
|
|
48
49
|
# - Operations: request_backfill, detect_capture_disruptions, has_live_drain_workers?,
|
|
49
50
|
# has_live_staging_worker?, watermark_token, await_converged, self_check,
|
|
50
51
|
# self_check_job.
|
|
52
|
+
# - Metrics: Trellis::Metrics.render_prometheus, which needs no handle.
|
|
51
53
|
#
|
|
52
54
|
# # A deploy's migration step: the defaults run nothing in the background.
|
|
53
55
|
# Trellis.connect(url: "host=localhost dbname=app")
|
|
@@ -71,7 +73,7 @@ require_relative "instance"
|
|
|
71
73
|
# calls on a handle it inherited raise Trellis::ForkedHandleError, except
|
|
72
74
|
# Trellis.shutdown, which leaves the parent's handle alone and does nothing.
|
|
73
75
|
# So does Trellis.connect in a child forked while its parent's handle was
|
|
74
|
-
# running
|
|
76
|
+
# running.
|
|
75
77
|
module Trellis
|
|
76
78
|
@default = nil
|
|
77
79
|
@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
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: trellis-pg
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.0.pre.
|
|
4
|
+
version: 0.1.0.pre.gamma
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Salesforce
|
|
@@ -41,6 +41,7 @@ files:
|
|
|
41
41
|
- ext/trellis_ruby/src/lib.rs
|
|
42
42
|
- lib/trellis/error.rb
|
|
43
43
|
- lib/trellis/instance.rb
|
|
44
|
+
- lib/trellis/metrics.rb
|
|
44
45
|
- lib/trellis/migration.rb
|
|
45
46
|
- lib/trellis/pg.rb
|
|
46
47
|
- lib/trellis/railtie.rb
|