rollbridge 0.1.24 → 0.1.26
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.
- package/AGENTS.md +55 -0
- package/README.md +63 -39
- package/TODO.md +5 -4
- package/docs/cli.md +26 -6
- package/docs/config.md +28 -8
- package/docs/nginx.md +5 -4
- package/docs/tensorbuzz-runbook.md +91 -125
- package/docs/troubleshooting.md +18 -9
- package/docs/velocious.md +93 -170
- package/docs/workers.md +81 -103
- package/package.json +1 -1
package/AGENTS.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Rollbridge contributor guidance
|
|
2
|
+
|
|
3
|
+
Rollbridge is the process supervisor and local traffic switcher. Keep deployment
|
|
4
|
+
tool concerns outside this repository: Rampway owns activation transactions,
|
|
5
|
+
deploy locks, release-retention metadata, and on-disk cleanup; Rollbridge owns
|
|
6
|
+
process lifecycle, retained generations, ports, and recovery state.
|
|
7
|
+
|
|
8
|
+
## Background-jobs lifecycle invariant
|
|
9
|
+
|
|
10
|
+
- One runtime generation is one release-scoped `background-jobs-main` plus its
|
|
11
|
+
worker pool. Start the complete candidate generation before activation.
|
|
12
|
+
- After activation, retire the old generation as one unit. Its main stops
|
|
13
|
+
schedules, new dispatch, and new ordinary worker handoffs; its workers stop
|
|
14
|
+
accepting handoffs. The old main remains running with those workers and owns
|
|
15
|
+
their connections, lease fencing, report acceptance and acknowledgement, and
|
|
16
|
+
durable store transitions. The worker/reporting side durably retries terminal
|
|
17
|
+
reports, tracks outstanding report promises, enforces per-job execution
|
|
18
|
+
timeouts, and owns and reaps child runners. Main and workers remain one release
|
|
19
|
+
generation until every accepted handoff settles.
|
|
20
|
+
- Returned or retried work may be dispatched by the new active generation. A
|
|
21
|
+
retired main never dispatches it again. Old workers never reconnect or hand
|
|
22
|
+
over to the new main during a normal deploy.
|
|
23
|
+
- Jobs generations may overlap for hours on release-scoped endpoints. Beacon may
|
|
24
|
+
remain shared on `7330`; jobs-main must use a per-release port range.
|
|
25
|
+
- Deploy success is candidate activation and health. Deploy completion must not
|
|
26
|
+
wait for retired jobs generations, workers, jobs, HTTP/WebSocket connections,
|
|
27
|
+
or other retained services. HTTP and jobs drains are independent; an HTTP
|
|
28
|
+
drain finishing or timing out must not stop a live jobs generation.
|
|
29
|
+
- Persist and recover retired-generation ownership across later deploys and
|
|
30
|
+
supervisor/host recovery. Multiple generations may drain concurrently. Report
|
|
31
|
+
release references so the deployment tool can keep them pinned until no
|
|
32
|
+
retained process uses them.
|
|
33
|
+
- Runtime-owner replacement transfers or preserves this durable supervision and
|
|
34
|
+
returns once the replacement is healthy. It is never a synchronous full
|
|
35
|
+
shutdown. Per-job timeouts remain valid, but a normal worker-shutdown timeout
|
|
36
|
+
is not the deploy solution; legitimate multi-hour drains are valid.
|
|
37
|
+
|
|
38
|
+
Documentation must distinguish required architecture from behavior not yet
|
|
39
|
+
implemented. Do not claim production compliance when source/config still uses a
|
|
40
|
+
fixed jobs-main, worker adoption by a new main, destructive orphan recovery, or
|
|
41
|
+
synchronous cleanup.
|
|
42
|
+
|
|
43
|
+
## Validation and publication
|
|
44
|
+
|
|
45
|
+
The project is ESM JavaScript with JSDoc type checking. Package scripts are the
|
|
46
|
+
source of truth: `npm run typecheck`, `npm run lint`, `npm test`, and the combined
|
|
47
|
+
`npm run all-checks`. Run focused checks for the files changed; documentation-only
|
|
48
|
+
work requires at least `git diff --check` plus any existing repository-owned
|
|
49
|
+
Markdown/link check, without installing dependencies.
|
|
50
|
+
|
|
51
|
+
Work on a feature branch and open a normal pull request against `master`; never
|
|
52
|
+
push feature work directly to `master`. Releases are maintainer-only and use
|
|
53
|
+
`npm run release:patch` from an up-to-date clean default branch after
|
|
54
|
+
`npm run all-checks`; do not edit package versions or publish during ordinary PR
|
|
55
|
+
work. See `docs/releasing.md` for the complete release checklist.
|
package/README.md
CHANGED
|
@@ -2,7 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Rollbridge is a Node.js process supervisor and local traffic switcher for zero-downtime deploys.
|
|
4
4
|
|
|
5
|
-
Nginx points at one stable Rollbridge proxy port. Deploy tooling asks Rollbridge to start a new release, health-check it, switch new traffic to it
|
|
5
|
+
Nginx points at one stable Rollbridge proxy port. Deploy tooling asks Rollbridge to start a new release, health-check it, and switch new traffic to it. Retirement then continues asynchronously: HTTP/WebSocket connections and retained process generations drain independently after the deploy command returns.
|
|
6
|
+
|
|
7
|
+
> **Required jobs-generation contract:** a release-scoped background-jobs
|
|
8
|
+
> runtime is its own `background-jobs-main` plus worker pool. Rollbridge starts a
|
|
9
|
+
> complete candidate generation before activation. After activation the retired
|
|
10
|
+
> main stops schedules, new dispatch, and new worker handoffs, but remains with
|
|
11
|
+
> its old workers to supervise every handoff it already made until all jobs and
|
|
12
|
+
> workers settle. Old workers never move to the new main. Generations may overlap
|
|
13
|
+
> for hours on separate ports, and neither deploy completion nor HTTP drain
|
|
14
|
+
> completion waits for or kills them. See [Background-job worker
|
|
15
|
+
> deployment](docs/workers.md) and the [Velocious deployment
|
|
16
|
+
> guide](docs/velocious.md).
|
|
17
|
+
|
|
18
|
+
This is the required architecture, not evidence that every released runtime or
|
|
19
|
+
consumer config already implements durable recovery and release-reference
|
|
20
|
+
reporting; verify source and config before claiming compliance.
|
|
6
21
|
|
|
7
22
|
## Install
|
|
8
23
|
|
|
@@ -47,23 +62,28 @@ export default {
|
|
|
47
62
|
processes: [
|
|
48
63
|
{
|
|
49
64
|
id: "beacon",
|
|
50
|
-
policy: "
|
|
65
|
+
policy: "service",
|
|
51
66
|
cwd: "{{releasePath}}",
|
|
52
67
|
command: "env VELOCIOUS_BEACON_PORT={{port}} npx velocious beacon",
|
|
53
|
-
port:
|
|
68
|
+
port: 7330
|
|
54
69
|
},
|
|
55
70
|
{
|
|
56
71
|
id: "background-jobs-worker",
|
|
57
72
|
policy: "companion",
|
|
58
73
|
cwd: "{{releasePath}}",
|
|
74
|
+
env: {VELOCIOUS_BACKGROUND_JOBS_PORT: "{{ports.background-jobs-main}}"},
|
|
59
75
|
command: "npx velocious background-jobs-worker",
|
|
76
|
+
nonBlockingDrain: true,
|
|
77
|
+
gracefulStopMs: "indefinite",
|
|
60
78
|
outputLines: 200
|
|
61
79
|
},
|
|
62
80
|
{
|
|
63
81
|
id: "background-jobs-main",
|
|
64
82
|
policy: "service",
|
|
83
|
+
deployStrategy: "handoff",
|
|
65
84
|
cwd: "{{releasePath}}",
|
|
66
|
-
command: "npx velocious background-jobs-main"
|
|
85
|
+
command: "env VELOCIOUS_BACKGROUND_JOBS_PORT={{port}} npx velocious background-jobs-main",
|
|
86
|
+
port: {from: 7331, to: 7399}
|
|
67
87
|
},
|
|
68
88
|
{
|
|
69
89
|
id: "web",
|
|
@@ -118,10 +138,11 @@ See [`docs/config.md`](docs/config.md#processesmemory).
|
|
|
118
138
|
memory: {limitBytes: 536870912, warnBytes: 402653184, checkIntervalMs: 5000}
|
|
119
139
|
```
|
|
120
140
|
|
|
121
|
-
Set a process's `stopSignal` (default `"SIGTERM"`) to the signal it quiets on
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
141
|
+
Set a process's `stopSignal` (default `"SIGTERM"`) to the signal it quiets on.
|
|
142
|
+
This is a process-level stop mechanism, not the primary deploy-completion
|
|
143
|
+
mechanism for a release-scoped jobs generation. A retired generation may remain
|
|
144
|
+
for hours, and its old jobs-main must stay available until its workers finish.
|
|
145
|
+
For example, a generic worker that drains on `SIGINT`:
|
|
125
146
|
|
|
126
147
|
```js
|
|
127
148
|
{id: "worker", policy: "companion", command: "…", stopSignal: "SIGINT", gracefulStopMs: 60000}
|
|
@@ -138,18 +159,19 @@ queue. See [`docs/config.md`](docs/config.md#processesreplicas).
|
|
|
138
159
|
{id: "worker", policy: "companion", command: "npx velocious background-jobs-worker", replicas: 4}
|
|
139
160
|
```
|
|
140
161
|
|
|
141
|
-
For workers that quiesce or drain via a command, set a `lifecycle` block —
|
|
162
|
+
For generic workers that quiesce or drain via a command, set a `lifecycle` block —
|
|
142
163
|
Rollbridge runs `quietCommand`, then drains (`drainCommand`/`drainTimeoutMs`),
|
|
143
164
|
then `stopCommand`/`stopSignal`, then `SIGKILL` after `gracefulStopMs` when
|
|
144
165
|
gracefully stopping the process. Each hook is bounded so it can't wedge a stop.
|
|
145
166
|
|
|
146
|
-
Set `nonBlockingDrain: true` on a worker companion
|
|
147
|
-
|
|
148
|
-
|
|
167
|
+
Set `nonBlockingDrain: true` on a jobs worker companion so it stops accepting new
|
|
168
|
+
handoffs as soon as its release retires, independently of the proxied connection
|
|
169
|
+
drain. Its release-scoped handoff jobs-main remains running to supervise existing
|
|
170
|
+
handoffs and exits only after the worker pool has drained.
|
|
149
171
|
|
|
150
|
-
See [`docs/workers.md`](docs/workers.md) for the full
|
|
151
|
-
deployment pattern
|
|
152
|
-
|
|
172
|
+
See [`docs/workers.md`](docs/workers.md) for the full release-generation
|
|
173
|
+
deployment pattern: a handoff `background-jobs-main`, its companion worker pool,
|
|
174
|
+
independent quiescence, and durable supervision while retained generations drain.
|
|
153
175
|
|
|
154
176
|
Set `releaseRetention` to bound how many stopped (drained) releases the daemon
|
|
155
177
|
keeps in memory and reports in `status`. `keep` (default `10`) retains the most
|
|
@@ -270,8 +292,9 @@ A release-scoped helper (for example a background worker bound to one release).
|
|
|
270
292
|
It starts **before** the proxied process in the same release, so release-local
|
|
271
293
|
dependencies are ready before the health check, and it is auto-restarted while
|
|
272
294
|
its release is active. Each release gets its own companions; a release's
|
|
273
|
-
companions stop when that release is drained and retired after a newer
|
|
274
|
-
takes over.
|
|
295
|
+
companions normally stop when that release is drained and retired after a newer
|
|
296
|
+
release takes over. A jobs worker configured with `nonBlockingDrain` instead
|
|
297
|
+
quiesces at retirement and drains independently of HTTP/WebSocket connections.
|
|
275
298
|
|
|
276
299
|
```js
|
|
277
300
|
{
|
|
@@ -279,7 +302,8 @@ takes over.
|
|
|
279
302
|
policy: "companion",
|
|
280
303
|
cwd: "{{releasePath}}",
|
|
281
304
|
command: "npx velocious background-jobs-worker",
|
|
282
|
-
|
|
305
|
+
nonBlockingDrain: true,
|
|
306
|
+
gracefulStopMs: "indefinite"
|
|
283
307
|
}
|
|
284
308
|
```
|
|
285
309
|
|
|
@@ -301,36 +325,36 @@ new copies simultaneously during a deploy would be unsafe.
|
|
|
301
325
|
|
|
302
326
|
### `service`
|
|
303
327
|
|
|
304
|
-
A daemon-wide
|
|
305
|
-
Velocious Beacon
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
it restarts from the newest good release. It keeps restarting until the daemon
|
|
310
|
-
shuts down.
|
|
328
|
+
A service can be daemon-wide and persistent, or release-scoped with
|
|
329
|
+
`deployStrategy: "handoff"`. Velocious Beacon is normally persistent on a stable
|
|
330
|
+
port. `background-jobs-main` is not: it must be a handoff service with one port
|
|
331
|
+
per release so old workers keep their old coordinator while new workers use the
|
|
332
|
+
candidate coordinator.
|
|
311
333
|
|
|
312
334
|
```js
|
|
313
335
|
{
|
|
314
336
|
id: "background-jobs-main",
|
|
315
337
|
policy: "service",
|
|
338
|
+
deployStrategy: "handoff",
|
|
316
339
|
cwd: "{{releasePath}}",
|
|
317
340
|
command: "npx velocious background-jobs-main",
|
|
318
|
-
port: 7331
|
|
341
|
+
port: {from: 7331, to: 7399}
|
|
319
342
|
}
|
|
320
343
|
```
|
|
321
344
|
|
|
322
345
|
### Deploy ordering
|
|
323
346
|
|
|
324
|
-
On `rollbridge deploy`,
|
|
347
|
+
On `rollbridge deploy`, the required ordering is:
|
|
325
348
|
|
|
326
|
-
1. starts any
|
|
349
|
+
1. starts any missing persistent service and the candidate's handoff services;
|
|
327
350
|
2. starts the new release's `companion`s, then its `proxied` process, and
|
|
328
351
|
health-checks the proxied process;
|
|
329
352
|
3. switches new traffic to the new release;
|
|
330
|
-
4.
|
|
353
|
+
4. marks the previous jobs-main and worker pool retired as one generation;
|
|
331
354
|
5. replaces `singleton`s (stops the old one, then starts the new one);
|
|
332
|
-
6.
|
|
333
|
-
|
|
355
|
+
6. returns success without waiting for the previous generation or its independent
|
|
356
|
+
HTTP/WebSocket drain; Rollbridge supervises all retained drains in the
|
|
357
|
+
background and reaps each generation only after its handoffs and workers end.
|
|
334
358
|
|
|
335
359
|
If the new release fails to start or health-check, the previous release stays
|
|
336
360
|
active and any service started during this deploy is rolled back.
|
|
@@ -413,13 +437,13 @@ rollbridge daemon --config /srv/ticket-server/rollbridge.js \
|
|
|
413
437
|
```
|
|
414
438
|
|
|
415
439
|
External supervisors that need to replace a foreground owner without waiting
|
|
416
|
-
for
|
|
417
|
-
health-checks the exact release first. Only then does it retire the accepted
|
|
418
|
-
owner's proxy/control listeners
|
|
419
|
-
|
|
420
|
-
binds the stable listeners
|
|
421
|
-
owner untouched. This is opt-in;
|
|
422
|
-
their existing behavior.
|
|
440
|
+
for retained generations to drain add `--takeover-owner`. The candidate starts
|
|
441
|
+
and health-checks the exact release first. Only then does it retire the accepted
|
|
442
|
+
owner's proxy/control listeners. Durable supervision of old jobs-main/worker
|
|
443
|
+
generations must be preserved or transferred while the attested replacement
|
|
444
|
+
binds the stable listeners; replacement does not mean full synchronous shutdown.
|
|
445
|
+
Candidate bootstrap failure leaves the accepted owner untouched. This is opt-in;
|
|
446
|
+
ordinary daemon bootstrap and `shutdown` keep their existing behavior.
|
|
423
447
|
|
|
424
448
|
The four bootstrap inputs are all-or-nothing and use absolute config/release
|
|
425
449
|
paths. Rollbridge binds its proxy, activates the release through the normal
|
|
@@ -600,7 +624,7 @@ daemon holds its log file open, so logrotate needs `copytruncate`.
|
|
|
600
624
|
|
|
601
625
|
## Deployment Notes
|
|
602
626
|
|
|
603
|
-
Run migrations before `rollbridge deploy`, and keep migrations backwards-compatible while old and new web
|
|
627
|
+
Run migrations before `rollbridge deploy`, and keep migrations backwards-compatible while old and new web and jobs generations overlap. Velocious Beacon may be a persistent fixed-port `service`; configure `background-jobs-main` as a release-scoped handoff service on a port range. A normal deploy may leave several retired generations draining concurrently.
|
|
604
628
|
|
|
605
629
|
See [`docs/deploy-recipes.md`](docs/deploy-recipes.md) for ready-to-use shell, CI, and Capistrano recipes that drive Rollbridge through its CLI, and [`docs/troubleshooting.md`](docs/troubleshooting.md) for diagnosing health-check failures, port conflicts, stale sockets, crash loops, and stuck draining releases.
|
|
606
630
|
|
package/TODO.md
CHANGED
|
@@ -30,11 +30,12 @@ This roadmap tracks planned Rollbridge features and documentation. Rollbridge sh
|
|
|
30
30
|
- [x] Distinguish crash restarts, deploy replacements, manual restarts, and memory restarts in status/events. (Per-process `lastStartReason` + a `reason` on the `process started` event; the `memory` reason is wired and fires once memory supervision restarts a process.)
|
|
31
31
|
- [x] Add a `restart` CLI command for a single process, a policy group, or all non-proxied workers.
|
|
32
32
|
- [x] Keep restart behavior safe for job workers by using lifecycle hooks before termination. (Manual restart, memory restart, and deploy-drain stops all run the `lifecycle` hooks via `stop()`.)
|
|
33
|
-
- [x] Graceful
|
|
33
|
+
- [x] Graceful process-stop controls for job workers.
|
|
34
34
|
- [x] Add generic lifecycle hooks such as `quietCommand`, `drainCommand`, `drainTimeoutMs`, and `stopCommand` (per-process `lifecycle`).
|
|
35
35
|
- [x] Support signal-only lifecycle steps for workers that can quiet on a Unix signal. (Per-process `stopSignal`; sent before the `SIGKILL`-after-`gracefulStopMs` fallback.)
|
|
36
|
-
- [x] Add a non-blocking drain mode so
|
|
37
|
-
- [x] Document
|
|
36
|
+
- [x] Add a non-blocking drain mode so a worker can quiesce at release retirement independently of the HTTP/WebSocket connection drain (`nonBlockingDrain`). This control alone does not provide durable retired-generation supervision.
|
|
37
|
+
- [x] Document the required Velocious release-generation contract (`docs/velocious.md` and `docs/workers.md`) without treating documentation as proof that the runtime implements it.
|
|
38
|
+
- [ ] Implement and verify durable release-scoped jobs-main retirement: owned-handoff supervision, recovery across daemon/host replacement, multiple concurrent retired generations, and release-reference reporting for cleanup pins.
|
|
38
39
|
- [x] Replicas and stable worker indexes. (Supported on port-less `companion` processes; `proxied`/`singleton`/ported processes stay single.)
|
|
39
40
|
- [x] Allow one process config to start multiple replicas (`replicas`, companion-only for now).
|
|
40
41
|
- [x] Expose `ROLLBRIDGE_REPLICA_INDEX`, replica count, and per-replica template context (`{{replicaIndex}}`/`{{replicaCount}}`).
|
|
@@ -91,7 +92,7 @@ This roadmap tracks planned Rollbridge features and documentation. Rollbridge sh
|
|
|
91
92
|
- [x] Write a CLI reference for `daemon`, `ensure-daemon`, `deploy`, `status`, `stop`, `shutdown`, and future commands (`docs/cli.md`).
|
|
92
93
|
- [x] Expand process policy docs with deployment examples for `proxied`, `companion`, `singleton`, and `service`.
|
|
93
94
|
- [x] Document memory checks and auto-restart behavior after the feature lands (`docs/config.md` → `processes[].memory`).
|
|
94
|
-
- [x] Document
|
|
95
|
+
- [x] Document the required background-job generation pattern (`docs/workers.md`: release-scoped jobs-main + worker pool, independent quiescence, durable retained supervision, and deploy completion that does not wait for drains).
|
|
95
96
|
- [x] Document worker lifecycle hooks (`docs/config.md` → `processes[].lifecycle`, `docs/workers.md`).
|
|
96
97
|
- [x] Add a Velocious deployment guide with Beacon, background-jobs-main, background-jobs-worker, and web process examples (`docs/velocious.md`).
|
|
97
98
|
- [x] Add an Nginx guide with WebSocket headers, timeouts, and common failure modes (`docs/nginx.md`).
|
package/docs/cli.md
CHANGED
|
@@ -66,10 +66,16 @@ and waits for control-socket deployments.
|
|
|
66
66
|
|
|
67
67
|
`--takeover-owner` requires the complete bootstrap tuple. It bootstraps and
|
|
68
68
|
health-checks the replacement before sending the current daemon the private
|
|
69
|
-
retirement command.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
retirement command. The current `performOwnerRetirement` path quiesces every
|
|
70
|
+
service, singleton, starting release, and retained release, releases the stable
|
|
71
|
+
listeners, and starts asynchronous `stop()` calls for all of them. It neither
|
|
72
|
+
preserves nor transfers retained-generation supervision to the replacement.
|
|
73
|
+
The replacement can bind before those stops finish. A bootstrap failure occurs
|
|
74
|
+
before retirement, so the previously accepted owner remains available.
|
|
75
|
+
|
|
76
|
+
A compliant future owner handoff must instead preserve or transfer durable
|
|
77
|
+
supervision of retained generations without handing old workers to a new
|
|
78
|
+
jobs-main. Current `--takeover-owner` does not provide that behavior.
|
|
73
79
|
|
|
74
80
|
## `ensure-daemon`
|
|
75
81
|
|
|
@@ -120,12 +126,26 @@ rollbridge deploy --release-path <path>
|
|
|
120
126
|
[--daemon-start-timeout-ms <ms>]
|
|
121
127
|
```
|
|
122
128
|
|
|
123
|
-
Starts the prepared release, health-checks the proxied
|
|
124
|
-
traffic to it
|
|
129
|
+
Starts the complete prepared release generation, health-checks the proxied
|
|
130
|
+
process, and switches new traffic to it. The current daemon then starts
|
|
131
|
+
retirement of the previous release asynchronously, so the command does not wait
|
|
132
|
+
for old workers, jobs, or HTTP/WebSocket connections to finish. Prints
|
|
125
133
|
`{"status": "success", "activeReleaseId": "...", "previousReleaseId": "..."}`.
|
|
126
134
|
If the new release fails to start or health-check, the previous release stays
|
|
127
135
|
active and the command errors.
|
|
128
136
|
|
|
137
|
+
After candidate activation, `Daemon.deploy()` synchronously waits for singleton
|
|
138
|
+
replacement before starting `drainAndPrune` and returning. A replacement failure
|
|
139
|
+
can therefore return a non-zero result while the candidate remains active, and a
|
|
140
|
+
slow replacement delays both the response and retirement of the old release.
|
|
141
|
+
|
|
142
|
+
This is a process-lifetime non-blocking drain, not durable supervision across a
|
|
143
|
+
daemon or host restart. It continues across later deploys only while the same
|
|
144
|
+
daemon remains alive. After a restart, surviving PIDs from persisted state are
|
|
145
|
+
advisory orphans that Rollbridge cannot re-adopt; explicit `recover --force`
|
|
146
|
+
stops them. Restart-surviving retained-generation ownership and recovery remain
|
|
147
|
+
required future behavior.
|
|
148
|
+
|
|
129
149
|
Before each deploy, the daemon reloads the config path it was started with.
|
|
130
150
|
Compatible process and lifecycle changes apply to the new release and govern
|
|
131
151
|
how the previous release retires, including updated `nonBlockingDrain`,
|
package/docs/config.md
CHANGED
|
@@ -75,7 +75,7 @@ to let a deploy group talk to the daemon.
|
|
|
75
75
|
| `proxy.upstreamHost` | string | `proxy.host`, or `"127.0.0.1"` when `proxy.host` is `0.0.0.0`/`::` | Host Rollbridge uses for release health checks and proxy targets. |
|
|
76
76
|
| `proxy.healthPath` | string | `"/ping"` | Default health-check path for proxied processes. |
|
|
77
77
|
| `proxy.healthTimeoutMs` | number | `30000` | Default health-check timeout for proxied processes. |
|
|
78
|
-
| `proxy.drainTimeoutMs` | number | `60000` | How long to drain
|
|
78
|
+
| `proxy.drainTimeoutMs` | number | `60000` | How long to drain HTTP/WebSocket connections before stopping the retired proxied process. Expiry never stops an independently draining jobs generation. |
|
|
79
79
|
| `proxy.forceStopTimeoutMs` | number | `10000` | Default per-process graceful-stop timeout (`SIGTERM`, then `SIGKILL`). |
|
|
80
80
|
|
|
81
81
|
## `releaseRetention`
|
|
@@ -212,6 +212,20 @@ range** so old and new instances can run at the same time:
|
|
|
212
212
|
|
|
213
213
|
Reference it from same-release processes with `{{ports.background-jobs-main}}`.
|
|
214
214
|
During a deploy, old workers keep the old port and new workers get the new port.
|
|
215
|
+
For background jobs, the required compliant architecture makes the handoff
|
|
216
|
+
service and its workers one release generation. Immediately after activation,
|
|
217
|
+
retirement must quiesce the old jobs-main's scheduling, dispatch, and new worker
|
|
218
|
+
handoffs while keeping it with its workers until their accepted work settles.
|
|
219
|
+
Workers are not adopted by the new service.
|
|
220
|
+
|
|
221
|
+
That immediate old-main quiescence is **required future compliance behavior**,
|
|
222
|
+
not current `deployStrategy: "handoff"` behavior. Today the release group starts
|
|
223
|
+
`stop()` for `nonBlockingDrain` companions at retirement but sends no retirement
|
|
224
|
+
or quiescence notice to the handoff service. It waits for the connection drain,
|
|
225
|
+
stops other dependent processes, waits for the non-blocking companion stops, and
|
|
226
|
+
only then stops the handoff service. Consequently, old and new jobs-main
|
|
227
|
+
instances can overlap scheduling and dispatch ownership. Do not treat the
|
|
228
|
+
configuration above alone as compliance with the background-jobs contract.
|
|
215
229
|
|
|
216
230
|
### `processes[].lifecycle`
|
|
217
231
|
|
|
@@ -220,6 +234,11 @@ deploy's drain, a `rollbridge restart`, a memory restart, or shutdown. They let
|
|
|
220
234
|
job worker quiesce and finish in-flight work before it is terminated. Omit
|
|
221
235
|
`lifecycle` for the default behavior (just `stopSignal` then `SIGKILL`).
|
|
222
236
|
|
|
237
|
+
These hooks describe an individual process stop. They do not define when a
|
|
238
|
+
deploy completes and must not impose a short normal-drain deadline on a jobs
|
|
239
|
+
generation. Per-job timeouts remain the correct bound for genuinely hung jobs;
|
|
240
|
+
legitimate hours-long generation drains are valid.
|
|
241
|
+
|
|
223
242
|
| Field | Type | Default | Description |
|
|
224
243
|
| --- | --- | --- | --- |
|
|
225
244
|
| `lifecycle.quietCommand` | string | unset | Run first to tell the process to stop accepting new work. |
|
|
@@ -253,15 +272,16 @@ That keeps a worker alive in case the draining web process still depends on it
|
|
|
253
272
|
but it also holds a background worker open for the whole connection drain.
|
|
254
273
|
|
|
255
274
|
Set `nonBlockingDrain: true` on a `companion` whose work is independent of the
|
|
256
|
-
proxied process (a job worker on a shared queue).
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
275
|
+
proxied process (a job worker on a shared queue). Rollbridge starts that
|
|
276
|
+
companion's configured stop sequence **as soon as the release is retired**, in
|
|
277
|
+
parallel with the connection drain, rather than after it. Its quiet command or
|
|
278
|
+
signal must make the worker stop accepting new handoffs. The asynchronous
|
|
279
|
+
release drain continues after the deploy response. As described above, current
|
|
280
|
+
Rollbridge does not simultaneously quiesce the handoff service, so this setting
|
|
281
|
+
alone does not prevent overlapping jobs-main scheduling or dispatch ownership.
|
|
262
282
|
|
|
263
283
|
```js
|
|
264
|
-
{id: "worker", policy: "companion", command: "…", nonBlockingDrain: true, stopSignal: "SIGINT", gracefulStopMs:
|
|
284
|
+
{id: "worker", policy: "companion", command: "…", nonBlockingDrain: true, stopSignal: "SIGINT", gracefulStopMs: "indefinite"}
|
|
265
285
|
```
|
|
266
286
|
|
|
267
287
|
### `processes[].restart`
|
package/docs/nginx.md
CHANGED
|
@@ -67,10 +67,11 @@ Related Rollbridge timeouts (configured in `rollbridge.js`, not Nginx):
|
|
|
67
67
|
|
|
68
68
|
- `proxy.healthTimeoutMs` gates how long a new release has to become healthy
|
|
69
69
|
before a deploy aborts — it does not affect request timeouts.
|
|
70
|
-
- `proxy.drainTimeoutMs` is how long Rollbridge keeps an old
|
|
71
|
-
in-flight connections during a deploy. Keep Nginx's
|
|
72
|
-
WebSocket locations comfortably above it so the front
|
|
73
|
-
connections Rollbridge is still draining.
|
|
70
|
+
- `proxy.drainTimeoutMs` is how long Rollbridge keeps an old proxied web process
|
|
71
|
+
alive for in-flight connections during a deploy. Keep Nginx's
|
|
72
|
+
`proxy_read_timeout` for WebSocket locations comfortably above it so the front
|
|
73
|
+
end doesn't cut connections Rollbridge is still draining. Retained jobs
|
|
74
|
+
generations drain independently and are not stopped when this timeout expires.
|
|
74
75
|
|
|
75
76
|
## Forwarded headers
|
|
76
77
|
|
|
@@ -1,129 +1,95 @@
|
|
|
1
|
-
# TensorBuzz
|
|
1
|
+
# TensorBuzz Rollbridge runbook
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[`examples/tensorbuzz.com.js`](../examples/tensorbuzz.com.js)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
[Running under systemd](../README.md#running-under-systemd)). For the general
|
|
8
|
-
Velocious topology and the worker recipe, see [`docs/velocious.md`](velocious.md).
|
|
3
|
+
This runbook defines the required TensorBuzz backend topology. The example at
|
|
4
|
+
[`examples/tensorbuzz.com.js`](../examples/tensorbuzz.com.js) is illustrative;
|
|
5
|
+
verify the deployed consumer config and Rollbridge implementation before
|
|
6
|
+
asserting production compliance.
|
|
9
7
|
|
|
10
|
-
## Ports
|
|
8
|
+
## Ports and generations
|
|
11
9
|
|
|
12
|
-
| Port | Process |
|
|
10
|
+
| Port | Process | Contract |
|
|
13
11
|
| --- | --- | --- |
|
|
14
|
-
| `4500` | Rollbridge proxy |
|
|
15
|
-
| `7330` |
|
|
16
|
-
| `7331` | `background-jobs-main`
|
|
17
|
-
| `14500`–`14599` |
|
|
18
|
-
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
rollbridge
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
C=/etc/rollbridge/tensorbuzz.com.js
|
|
101
|
-
|
|
102
|
-
rollbridge status --config "$C" # active release, ports, per-process state
|
|
103
|
-
rollbridge logs --config "$C" --process web # recent stdout/stderr of a process
|
|
104
|
-
rollbridge events --config "$C" # deploys, switches, crashes, restarts
|
|
105
|
-
rollbridge doctor --config "$C" # pre-flight: socket, proxy port, state
|
|
106
|
-
rollbridge restart --config "$C" --process background-jobs-worker # bounce the worker
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Restarting `beacon` or `background-jobs-main` bounces a shared broker and briefly
|
|
110
|
-
disrupts everything that depends on it; prefer `deploy`/`rollback` for code
|
|
111
|
-
changes. See [`docs/troubleshooting.md`](troubleshooting.md) for health-check
|
|
112
|
-
failures, port conflicts, stale sockets, crash loops, and stuck draining
|
|
113
|
-
releases.
|
|
114
|
-
|
|
115
|
-
## Crash recovery
|
|
116
|
-
|
|
117
|
-
Set [`statePath`](config.md#statepath) in the config to have the daemon persist
|
|
118
|
-
its state. After a daemon crash or reboot, `rollbridge doctor` reports any
|
|
119
|
-
**orphaned** processes still alive from the previous daemon. To clean them up
|
|
120
|
-
before restarting the daemon, run `rollbridge recover` (a dry run that lists
|
|
121
|
-
them), then `rollbridge recover --force` to stop them:
|
|
122
|
-
|
|
123
|
-
```bash
|
|
124
|
-
rollbridge recover --config /etc/rollbridge/tensorbuzz.com.js # list leftovers
|
|
125
|
-
rollbridge recover --config /etc/rollbridge/tensorbuzz.com.js --force # stop them
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
A machine reboot kills every process, so there are usually no orphans afterward —
|
|
129
|
-
the daemon just starts fresh.
|
|
12
|
+
| `4500` | Rollbridge proxy | Stable public upstream for Nginx. |
|
|
13
|
+
| `7330` | Beacon | Shared persistent service; fixed port is allowed. |
|
|
14
|
+
| `7331`–`7399` | `background-jobs-main` | One allocated endpoint per release generation; never one fixed persistent coordinator. |
|
|
15
|
+
| `14500`–`14599` | web | One proxied endpoint per release. |
|
|
16
|
+
| none | workers | Same-release companions connected only to their generation's jobs-main. |
|
|
17
|
+
|
|
18
|
+
Each jobs generation contains one jobs-main and its complete worker pool, all
|
|
19
|
+
running the same release code. Several old generations may continue draining
|
|
20
|
+
while a newer generation is active.
|
|
21
|
+
|
|
22
|
+
## Required deploy order
|
|
23
|
+
|
|
24
|
+
1. Prepare the candidate release and run backwards-compatible migrations.
|
|
25
|
+
2. Start the candidate jobs-main on a new port, then its worker pool and web
|
|
26
|
+
process. Health-check web before activation.
|
|
27
|
+
3. Activate the candidate release and switch new traffic.
|
|
28
|
+
4. Retire the previous jobs-main and workers as one generation. Jobs-main stops
|
|
29
|
+
schedule ownership, new dispatch, and new handoffs; workers stop accepting
|
|
30
|
+
handoffs.
|
|
31
|
+
5. Return deploy success and release the deploy lock. Do not wait for old jobs,
|
|
32
|
+
workers, jobs-main, HTTP/WebSocket connections, or other retained services.
|
|
33
|
+
|
|
34
|
+
The retired jobs-main remains running on its old endpoint with its old workers.
|
|
35
|
+
For accepted handoffs it owns worker connections and heartbeats, lease fencing,
|
|
36
|
+
terminal-report acceptance and acknowledgement, and durable store transitions.
|
|
37
|
+
The old worker/reporting side durably retries terminal reports, tracks
|
|
38
|
+
outstanding report promises, enforces per-job execution timeouts, and owns and
|
|
39
|
+
reaps child runners. Returned or retried work becomes eligible for the new active
|
|
40
|
+
generation and is never redispatched by the retired main. Old workers do not
|
|
41
|
+
reconnect to or transfer their handoffs to the new main.
|
|
42
|
+
|
|
43
|
+
Old main and workers remain one release generation until every accepted handoff
|
|
44
|
+
settles. Only then, after every old worker exits, may jobs-main exit and
|
|
45
|
+
Rollbridge reap the generation. The referenced release directory stays pinned
|
|
46
|
+
against Rampway cleanup until that point.
|
|
47
|
+
|
|
48
|
+
## Independent drains
|
|
49
|
+
|
|
50
|
+
Set the worker companion to `nonBlockingDrain: true` so it quiesces when its
|
|
51
|
+
generation retires. HTTP/WebSocket connection drain continues independently.
|
|
52
|
+
Finishing or timing out the HTTP drain must never stop a still-draining jobs
|
|
53
|
+
generation. A legitimate multi-hour job and generation drain are valid.
|
|
54
|
+
|
|
55
|
+
Per-job timeouts remain the backstop for genuinely hung jobs. Do not use a short
|
|
56
|
+
worker-shutdown or supervisor timeout as the primary deploy solution; deployment
|
|
57
|
+
has already completed after candidate activation and health.
|
|
58
|
+
|
|
59
|
+
## Runtime-owner and recovery requirements
|
|
60
|
+
|
|
61
|
+
Required compliant behavior durably supervises every retired generation after
|
|
62
|
+
the deploy command returns, across later deploys and supervisor/host recovery.
|
|
63
|
+
Runtime-owner or version handoff must preserve or transfer that supervision and
|
|
64
|
+
return once the replacement is healthy. It must not perform full synchronous
|
|
65
|
+
shutdown, kill retained generations, or make the new jobs-main adopt old workers.
|
|
66
|
+
|
|
67
|
+
Recovery must reconstruct retained generation ownership, endpoints, release
|
|
68
|
+
paths, and process references. Do not treat them as generic orphans to force-stop
|
|
69
|
+
merely because a supervisor restarted. Cleanup becomes eligible only after the
|
|
70
|
+
last retained process exits.
|
|
71
|
+
|
|
72
|
+
Current Rollbridge does not yet meet those recovery and owner-handoff
|
|
73
|
+
requirements. Its non-blocking release drains last only for the current daemon's
|
|
74
|
+
lifetime; after restart it reports surviving PIDs as advisory, non-adoptable
|
|
75
|
+
orphans, and forced recovery stops them. `--takeover-owner` quiesces and starts
|
|
76
|
+
asynchronous stops for every managed process instead of transferring retained
|
|
77
|
+
generations.
|
|
78
|
+
|
|
79
|
+
## Operator checks
|
|
80
|
+
|
|
81
|
+
Use `rollbridge status`, logs, and events to confirm:
|
|
82
|
+
|
|
83
|
+
- the active jobs generation uses the active release and a unique jobs-main port;
|
|
84
|
+
- every retired generation retains its own jobs-main, workers, endpoint, and
|
|
85
|
+
pinned release path;
|
|
86
|
+
- the deploy command has returned while long drains continue;
|
|
87
|
+
- no retired jobs-main is dispatching new ordinary queued work;
|
|
88
|
+
- HTTP drain completion or timeout did not stop a jobs generation; and
|
|
89
|
+
- completed generations are reaped and Rampway is told their release references
|
|
90
|
+
ended so it can release the pins.
|
|
91
|
+
|
|
92
|
+
Do not restart Beacon or a jobs-main casually. Never use `shutdown`, forced
|
|
93
|
+
orphan recovery, or process signals as a substitute for the normal retained-
|
|
94
|
+
generation lifecycle. See [`docs/velocious.md`](velocious.md) and
|
|
95
|
+
[`docs/workers.md`](workers.md) for the architecture details.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -109,12 +109,21 @@ stays active.
|
|
|
109
109
|
in `state: "draining"` with non-zero `connections` (often `websocket`).
|
|
110
110
|
|
|
111
111
|
**Diagnose.** Long-lived connections (WebSockets, SSE, streaming responses) keep
|
|
112
|
-
the retired
|
|
113
|
-
`status` shows the release's
|
|
114
|
-
`drainStartedAt`.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
`proxy.drainTimeoutMs
|
|
119
|
-
|
|
120
|
-
|
|
112
|
+
the retired proxied web process alive until they close or
|
|
113
|
+
`proxy.drainTimeoutMs` elapses. `status` shows the release's
|
|
114
|
+
`connections.http`/`connections.websocket` and `drainStartedAt`. A retained jobs
|
|
115
|
+
generation has an independent lifecycle and may remain after the web drain ends.
|
|
116
|
+
|
|
117
|
+
**Fix.** The connection drain ends automatically when those connections close,
|
|
118
|
+
or after `proxy.drainTimeoutMs`. Rollbridge then stops the retired proxied web
|
|
119
|
+
process and other connection-dependent processes, including ordinary companions
|
|
120
|
+
with `nonBlockingDrain: false`. Lower the timeout only to shorten
|
|
121
|
+
HTTP/WebSocket retention, or make clients reconnect (for example, have the front
|
|
122
|
+
end close idle WebSockets on deploy). In the documented compliant jobs topology,
|
|
123
|
+
jobs companions use `nonBlockingDrain: true`, so timeout expiry affects only the
|
|
124
|
+
web side and must not stop a still-draining jobs generation.
|
|
125
|
+
|
|
126
|
+
Reporting release references to Rampway and pinning release directories against
|
|
127
|
+
on-disk cleanup are required future behavior, not implemented today. Current
|
|
128
|
+
Rollbridge `status` and `releaseRetention` govern only its in-memory release
|
|
129
|
+
records and do not fence Rampway cleanup.
|
package/docs/velocious.md
CHANGED
|
@@ -1,37 +1,35 @@
|
|
|
1
1
|
# Velocious deployment guide
|
|
2
2
|
|
|
3
|
-
A Velocious backend
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
A Velocious backend normally runs Beacon, `background-jobs-main`, a
|
|
4
|
+
`background-jobs-worker` pool, and the web/API server. For deploy lifecycle
|
|
5
|
+
purposes, jobs-main and its workers are one release-scoped **jobs generation**.
|
|
6
|
+
This topology is required; a single persistent fixed-port jobs-main is not a
|
|
7
|
+
safe coordinator for workers that may drain across releases.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
This page states the architecture contract. It does not by itself assert that a
|
|
10
|
+
particular Rollbridge release or consumer production config implements durable
|
|
11
|
+
retired-generation recovery; verify source and config before claiming compliance.
|
|
11
12
|
|
|
12
13
|
## Process mapping
|
|
13
14
|
|
|
14
|
-
| Velocious process |
|
|
15
|
+
| Velocious process | Rollbridge policy | Lifecycle |
|
|
15
16
|
| --- | --- | --- |
|
|
16
|
-
| `beacon` | `service` |
|
|
17
|
-
| `background-jobs-main` | `service` with `deployStrategy: "handoff"` |
|
|
18
|
-
| `background-jobs-worker` | `companion` | Release-scoped
|
|
19
|
-
| `web` | `proxied` |
|
|
20
|
-
|
|
21
|
-
See [README → Process Policies](../README.md#process-policies) for the full
|
|
22
|
-
semantics of each policy and [`docs/config.md`](config.md) for every field.
|
|
17
|
+
| `beacon` | persistent `service` | Shared broker; it may remain daemon-wide on fixed port `7330`. |
|
|
18
|
+
| `background-jobs-main` | `service` with `deployStrategy: "handoff"` | One endpoint per release; owns worker connections, lease fencing, report acceptance/acknowledgement, and durable store transitions. |
|
|
19
|
+
| `background-jobs-worker` | `companion` with `nonBlockingDrain: true` | Release-scoped pool; executes accepted work, owns child runners and execution timeouts, and durably retries terminal reports while tracking their promises. |
|
|
20
|
+
| `web` | `proxied` | Health-gated active HTTP/WebSocket target with a per-release port. |
|
|
23
21
|
|
|
24
22
|
## Example `rollbridge.js`
|
|
25
23
|
|
|
26
24
|
```js
|
|
27
|
-
// rollbridge.js
|
|
28
25
|
export default {
|
|
29
26
|
application: "tensorbuzz",
|
|
30
27
|
control: {path: "/tmp/rollbridge-tensorbuzz.sock"},
|
|
28
|
+
statePath: "/var/lib/rollbridge/tensorbuzz.json",
|
|
31
29
|
|
|
32
30
|
proxy: {
|
|
33
31
|
host: "127.0.0.1",
|
|
34
|
-
port: 4500,
|
|
32
|
+
port: 4500,
|
|
35
33
|
healthPath: "/ping",
|
|
36
34
|
healthTimeoutMs: 30000,
|
|
37
35
|
drainTimeoutMs: 60000,
|
|
@@ -39,7 +37,6 @@ export default {
|
|
|
39
37
|
},
|
|
40
38
|
|
|
41
39
|
processes: [
|
|
42
|
-
// Shared broker — one daemon-wide instance on a stable port.
|
|
43
40
|
{
|
|
44
41
|
id: "beacon",
|
|
45
42
|
policy: "service",
|
|
@@ -48,8 +45,6 @@ export default {
|
|
|
48
45
|
command: "npx velocious beacon",
|
|
49
46
|
port: 7330
|
|
50
47
|
},
|
|
51
|
-
|
|
52
|
-
// Job coordinator — one release-scoped service instance per deploy.
|
|
53
48
|
{
|
|
54
49
|
id: "background-jobs-main",
|
|
55
50
|
policy: "service",
|
|
@@ -63,23 +58,20 @@ export default {
|
|
|
63
58
|
command: "wait-for-it 127.0.0.1:{{ports.beacon}} --strict -- npx velocious background-jobs-main",
|
|
64
59
|
port: {from: 7331, to: 7399}
|
|
65
60
|
},
|
|
66
|
-
|
|
67
|
-
// Workers — one set per release; raise gracefulStopMs to let in-flight
|
|
68
|
-
// jobs finish during a deploy.
|
|
69
61
|
{
|
|
70
62
|
id: "background-jobs-worker",
|
|
71
63
|
policy: "companion",
|
|
64
|
+
nonBlockingDrain: true,
|
|
72
65
|
cwd: "{{releasePath}}/backend",
|
|
73
66
|
env: {
|
|
74
67
|
NODE_ENV: "production",
|
|
75
68
|
VELOCIOUS_BEACON_PORT: "{{ports.beacon}}",
|
|
76
69
|
VELOCIOUS_BACKGROUND_JOBS_PORT: "{{ports.background-jobs-main}}"
|
|
77
70
|
},
|
|
78
|
-
command: "wait-for-it 127.0.0.1:{{ports.
|
|
71
|
+
command: "wait-for-it 127.0.0.1:{{ports.background-jobs-main}} --strict -- npx velocious background-jobs-worker",
|
|
72
|
+
replicas: 4,
|
|
79
73
|
gracefulStopMs: "indefinite"
|
|
80
74
|
},
|
|
81
|
-
|
|
82
|
-
// Web/API — the one proxied process.
|
|
83
75
|
{
|
|
84
76
|
id: "web",
|
|
85
77
|
policy: "proxied",
|
|
@@ -89,7 +81,7 @@ export default {
|
|
|
89
81
|
VELOCIOUS_BEACON_PORT: "{{ports.beacon}}",
|
|
90
82
|
VELOCIOUS_BACKGROUND_JOBS_PORT: "{{ports.background-jobs-main}}"
|
|
91
83
|
},
|
|
92
|
-
command: "wait-for-it 127.0.0.1:{{ports.
|
|
84
|
+
command: "wait-for-it 127.0.0.1:{{ports.background-jobs-main}} --strict -- npx velocious server --host 127.0.0.1 --port {{port}}",
|
|
93
85
|
port: {from: 14500, to: 14599},
|
|
94
86
|
health: {path: "/ping", timeoutMs: 30000, intervalMs: 500}
|
|
95
87
|
}
|
|
@@ -97,146 +89,77 @@ export default {
|
|
|
97
89
|
}
|
|
98
90
|
```
|
|
99
91
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
`
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
### Worker recipe
|
|
175
|
-
|
|
176
|
-
A complete `background-jobs-worker` entry that runs a pool and finishes in-flight
|
|
177
|
-
jobs across a deploy:
|
|
178
|
-
|
|
179
|
-
```js
|
|
180
|
-
{
|
|
181
|
-
id: "background-jobs-worker",
|
|
182
|
-
policy: "companion",
|
|
183
|
-
cwd: "{{releasePath}}/backend",
|
|
184
|
-
env: {
|
|
185
|
-
NODE_ENV: "production",
|
|
186
|
-
VELOCIOUS_ENV: "production",
|
|
187
|
-
VELOCIOUS_BEACON_PORT: "{{ports.beacon}}",
|
|
188
|
-
VELOCIOUS_BACKGROUND_JOBS_PORT: "{{ports.background-jobs-main}}"
|
|
189
|
-
},
|
|
190
|
-
command: "wait-for-it 127.0.0.1:{{ports.beacon}} --strict -- wait-for-it 127.0.0.1:{{ports.background-jobs-main}} --strict -- npx velocious background-jobs-worker",
|
|
191
|
-
replicas: 4,
|
|
192
|
-
gracefulStopMs: "indefinite"
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
- `replicas: 4` runs four worker instances (`background-jobs-worker#0` … `#3`),
|
|
197
|
-
each with `ROLLBRIDGE_REPLICA_INDEX`/`ROLLBRIDGE_REPLICA_COUNT` if you shard work.
|
|
198
|
-
- On deploy the new release's workers start before traffic switches; the old
|
|
199
|
-
release's workers receive `SIGTERM` (the default `stopSignal`) when the old
|
|
200
|
-
release is retired, then wait to exit. With `gracefulStopMs: "indefinite"`,
|
|
201
|
-
Rollbridge does not send a `SIGKILL` fallback.
|
|
202
|
-
|
|
203
|
-
If your worker quiesces on a command or a non-default signal, add a `lifecycle`
|
|
204
|
-
block — Rollbridge runs `quietCommand`, drains for up to `drainTimeoutMs`, then
|
|
205
|
-
stops. For example, send a quiet signal to the worker's process group before the
|
|
206
|
-
drain:
|
|
207
|
-
|
|
208
|
-
```js
|
|
209
|
-
lifecycle: {quietCommand: "kill -TSTP -$ROLLBRIDGE_PID", drainTimeoutMs: 60000}
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### Choosing the jobs-main policy
|
|
213
|
-
|
|
214
|
-
`background-jobs-main` coordinates workers, so choose its lifecycle deliberately:
|
|
215
|
-
|
|
216
|
-
- **`service` with `deployStrategy: "handoff"`** — starts one coordinator per
|
|
217
|
-
release on a port from a range. New workers and web get the new release's port;
|
|
218
|
-
old workers keep the old release's port while they drain. This is the safest
|
|
219
|
-
default when the coordinator should run the same code version as its workers.
|
|
220
|
-
- **`service` with the default `deployStrategy: "persistent"`** — keeps one
|
|
221
|
-
daemon-wide coordinator on a stable port. Workers from every release talk to the
|
|
222
|
-
same coordinator, but it keeps running the release it was started from and only
|
|
223
|
-
adopts the latest template if it restarts later.
|
|
224
|
-
- **`singleton`** — stops the old instance and then starts the new one on each
|
|
225
|
-
deploy, so it always runs the latest release's code and two copies never
|
|
226
|
-
overlap. The trade-off: a brief coordination gap while it restarts.
|
|
227
|
-
|
|
228
|
-
Beacon is a broker rather than code that changes per release, so `service` is
|
|
229
|
-
almost always right for it.
|
|
230
|
-
|
|
231
|
-
## Verifying
|
|
232
|
-
|
|
233
|
-
After a deploy, `rollbridge status` should show `beacon` as a long-lived service
|
|
234
|
-
with an unchanged port, `background-jobs-main` as the active release's handoff
|
|
235
|
-
service, one `background-jobs-worker` for the active release, and the `web`
|
|
236
|
-
process `proxied` with its connection counts. Use
|
|
237
|
-
[`rollbridge logs --process <id>`](cli.md) to read recent output from any
|
|
238
|
-
process, and [`docs/troubleshooting.md`](troubleshooting.md) for health-check,
|
|
239
|
-
port, and draining problems.
|
|
240
|
-
|
|
241
|
-
For the front end, point Nginx at the stable `proxy.port` (here `4500`), never at
|
|
242
|
-
a release's web port — see [`docs/nginx.md`](nginx.md).
|
|
92
|
+
Beacon keeps its fixed port because it is intentionally shared. Jobs-main uses a
|
|
93
|
+
range because every release gets its own coordinator. Same-release
|
|
94
|
+
`{{ports.background-jobs-main}}` expansion ensures that old workers retain the
|
|
95
|
+
old endpoint while candidate workers use the candidate endpoint.
|
|
96
|
+
|
|
97
|
+
## Deploy and activation
|
|
98
|
+
|
|
99
|
+
Run backwards-compatible migrations before activation, then invoke
|
|
100
|
+
`rollbridge deploy` with the prepared release. Rollbridge starts the candidate
|
|
101
|
+
jobs-main, its complete worker pool, and the web process before health gating and
|
|
102
|
+
activation. A candidate startup or health failure leaves the previous release
|
|
103
|
+
active.
|
|
104
|
+
|
|
105
|
+
After successful activation, the deploy returns without waiting for any retired
|
|
106
|
+
generation or HTTP/WebSocket connection to finish. The old and new release code
|
|
107
|
+
may therefore overlap for hours. Keep schema, queue payloads, and external side
|
|
108
|
+
effects compatible across that window.
|
|
109
|
+
|
|
110
|
+
## Retired jobs-generation contract
|
|
111
|
+
|
|
112
|
+
Retire jobs-main and its workers as one unit:
|
|
113
|
+
|
|
114
|
+
- jobs-main relinquishes recurring schedule ownership and stops dispatching
|
|
115
|
+
queued work or making new worker handoffs;
|
|
116
|
+
- workers stop advertising or accepting new handoffs;
|
|
117
|
+
- jobs-main remains running on the old endpoint and owns worker connections and
|
|
118
|
+
heartbeats, lease fencing, terminal-report acceptance and acknowledgement, and
|
|
119
|
+
durable store transitions for its accepted handoffs;
|
|
120
|
+
- the old worker/reporting side durably retries terminal reports, tracks
|
|
121
|
+
outstanding report promises, enforces per-job execution timeouts, and owns and
|
|
122
|
+
reaps child runners;
|
|
123
|
+
- a job returned or retried to the shared queue becomes eligible for the new
|
|
124
|
+
active generation, and the retired main never dispatches it again;
|
|
125
|
+
- old workers never reconnect to or transfer their handoffs to the new jobs-main;
|
|
126
|
+
- old main and workers remain one release generation until all accepted work
|
|
127
|
+
settles; jobs-main exits only after that and after all workers drain and exit,
|
|
128
|
+
after which Rollbridge may reap the generation.
|
|
129
|
+
|
|
130
|
+
HTTP/WebSocket and jobs drains are independent. `proxy.drainTimeoutMs` bounds the
|
|
131
|
+
connection drain only; reaching it must not stop a still-draining jobs generation.
|
|
132
|
+
`nonBlockingDrain: true` starts worker quiescence at retirement rather than after
|
|
133
|
+
the HTTP drain.
|
|
134
|
+
|
|
135
|
+
The process supervisor must retain multiple old generations concurrently,
|
|
136
|
+
persist their ownership across later deploys and supervisor/host recovery, and
|
|
137
|
+
report every referenced release directory so Rampway can pin it against cleanup.
|
|
138
|
+
A runtime owner/version handoff preserves or transfers that supervision and
|
|
139
|
+
returns after the replacement is healthy; it is not a full synchronous shutdown.
|
|
140
|
+
|
|
141
|
+
These are target requirements. Current Rollbridge drains releases
|
|
142
|
+
asynchronously only while the same daemon remains alive, cannot re-adopt
|
|
143
|
+
surviving PIDs after restart, and stops rather than transfers all managed
|
|
144
|
+
processes during `--takeover-owner`; see [`docs/cli.md`](cli.md#daemon).
|
|
145
|
+
|
|
146
|
+
## Timeouts
|
|
147
|
+
|
|
148
|
+
Velocious per-job timeouts remain responsible for genuinely hung work. Rollbridge
|
|
149
|
+
stop signals, lifecycle hooks, and graceful-stop bounds remain emergency/process
|
|
150
|
+
controls. Do not use a short normal worker-shutdown timeout to make deployment
|
|
151
|
+
complete: deployment is already complete after healthy activation, and a
|
|
152
|
+
legitimate hours-long job makes an hours-long generation drain valid.
|
|
153
|
+
|
|
154
|
+
## Verification
|
|
155
|
+
|
|
156
|
+
After a deploy, status must be able to show the active generation and every
|
|
157
|
+
retired generation still draining, including each jobs-main endpoint, worker
|
|
158
|
+
pool, release path, and retention reference. Beacon may keep `7330`; active and
|
|
159
|
+
retired jobs-main instances must use different ports. Confirm that the deploy
|
|
160
|
+
command has returned even while retained generations remain and that an HTTP
|
|
161
|
+
drain timeout does not terminate them.
|
|
162
|
+
|
|
163
|
+
See [`docs/workers.md`](workers.md) for the focused lifecycle,
|
|
164
|
+
[`docs/config.md`](config.md) for configuration fields, and
|
|
165
|
+
[`docs/tensorbuzz-runbook.md`](tensorbuzz-runbook.md) for the consumer runbook.
|
package/docs/workers.md
CHANGED
|
@@ -1,117 +1,95 @@
|
|
|
1
|
-
# Background-job
|
|
1
|
+
# Background-job generation deployment
|
|
2
2
|
|
|
3
|
-
This
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
This is the required Rollbridge lifecycle for a background-jobs runtime. A
|
|
4
|
+
generation is release-scoped and contains its own `background-jobs-main` plus
|
|
5
|
+
its worker pool. It is not just a set of workers attached to one persistent
|
|
6
|
+
coordinator.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Process topology
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
release
|
|
12
|
-
|
|
13
|
-
release takes over. They start **before** the `proxied` web process, so they're
|
|
14
|
-
ready before traffic switches.
|
|
10
|
+
Configure jobs-main as a handoff `service` on a multi-port range and the workers
|
|
11
|
+
as same-release `companion`s. Beacon may remain a shared persistent service on a
|
|
12
|
+
fixed port such as `7330`.
|
|
15
13
|
|
|
16
14
|
```js
|
|
17
15
|
{
|
|
18
|
-
id: "
|
|
19
|
-
policy: "
|
|
20
|
-
|
|
21
|
-
command: "npx velocious background-jobs-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
## Scale the pool with `replicas`
|
|
26
|
-
|
|
27
|
-
Set `replicas` to run several identical workers (a port-less companion only).
|
|
28
|
-
Each instance runs as `worker#0`, `worker#1`, … and gets
|
|
29
|
-
`ROLLBRIDGE_REPLICA_INDEX` / `ROLLBRIDGE_REPLICA_COUNT` (and `{{replicaIndex}}` /
|
|
30
|
-
`{{replicaCount}}`), so an instance can claim a distinct shard, queue, or lock:
|
|
31
|
-
|
|
32
|
-
```js
|
|
33
|
-
{id: "worker", policy: "companion", command: "npx velocious background-jobs-worker", replicas: 4}
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Restart the pool with `rollbridge restart --process worker` (all replicas) or a
|
|
37
|
-
single instance with `rollbridge restart --process worker#0`.
|
|
38
|
-
|
|
39
|
-
## Finish in-flight jobs on stop (`stopSignal` + `gracefulStopMs`)
|
|
40
|
-
|
|
41
|
-
When Rollbridge stops a worker — during a deploy's drain, a `rollbridge restart`,
|
|
42
|
-
or shutdown — it sends the worker's **`stopSignal`** (default `SIGTERM`), waits up
|
|
43
|
-
to **`gracefulStopMs`**, then `SIGKILL`s it if it hasn't exited. That window is
|
|
44
|
-
the worker's chance to finish its current job and exit cleanly.
|
|
45
|
-
|
|
46
|
-
- Set `stopSignal` to the signal your worker quiets/drains on. Many job runners
|
|
47
|
-
finish the current job and exit on `SIGTERM` (the default); some use `SIGINT`
|
|
48
|
-
or `SIGQUIT`. Use the one your worker treats as "drain and exit".
|
|
49
|
-
- Set `gracefulStopMs` to at least your longest job's duration, so a job in
|
|
50
|
-
progress is not cut off by the `SIGKILL` fallback. Use `"indefinite"` only for
|
|
51
|
-
workers that are safe to leave draining until they exit on their own.
|
|
52
|
-
|
|
53
|
-
```js
|
|
16
|
+
id: "background-jobs-main",
|
|
17
|
+
policy: "service",
|
|
18
|
+
deployStrategy: "handoff",
|
|
19
|
+
command: "npx velocious background-jobs-main",
|
|
20
|
+
port: {from: 7331, to: 7399}
|
|
21
|
+
},
|
|
54
22
|
{
|
|
55
|
-
id: "worker",
|
|
23
|
+
id: "background-jobs-worker",
|
|
56
24
|
policy: "companion",
|
|
25
|
+
env: {VELOCIOUS_BACKGROUND_JOBS_PORT: "{{ports.background-jobs-main}}"},
|
|
57
26
|
command: "npx velocious background-jobs-worker",
|
|
58
27
|
replicas: 4,
|
|
59
|
-
|
|
28
|
+
nonBlockingDrain: true,
|
|
60
29
|
gracefulStopMs: "indefinite"
|
|
61
30
|
}
|
|
62
31
|
```
|
|
63
32
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
33
|
+
Each worker receives its generation's jobs-main port. Old workers keep that port
|
|
34
|
+
for their entire lifetime; normal deploy draining never hands them to, or lets
|
|
35
|
+
them reconnect to, the new jobs-main. `replicas` scales the pool as
|
|
36
|
+
`background-jobs-worker#0`, `#1`, and so on.
|
|
37
|
+
|
|
38
|
+
## Deploy and retirement sequence
|
|
39
|
+
|
|
40
|
+
1. Before activation, Rollbridge starts the candidate release's jobs-main and
|
|
41
|
+
complete worker pool, then starts and health-checks the candidate web process.
|
|
42
|
+
2. Activation switches new web traffic and makes the candidate jobs generation
|
|
43
|
+
active.
|
|
44
|
+
3. The previous jobs generation retires as one unit. Its jobs-main stops schedule
|
|
45
|
+
ownership, new queue dispatch, and new ordinary worker handoffs. Its workers
|
|
46
|
+
stop accepting handoffs.
|
|
47
|
+
4. The old jobs-main stays running with its old workers. It continues owning
|
|
48
|
+
their connections and heartbeats, lease fencing, terminal-report acceptance
|
|
49
|
+
and acknowledgement, and durable store transitions. The old worker/reporting
|
|
50
|
+
side durably retries terminal reports, tracks outstanding report promises,
|
|
51
|
+
enforces per-job execution timeouts, and owns and reaps child runners.
|
|
52
|
+
5. Work returned or retried to the shared queue becomes eligible for the new
|
|
53
|
+
active generation. The retired main never dispatches it again.
|
|
54
|
+
6. The old main and workers remain one release generation until every accepted
|
|
55
|
+
handoff settles. Only then, after every old worker drains and exits, may the
|
|
56
|
+
old jobs-main exit. Rollbridge then reaps the generation and reports that its
|
|
57
|
+
release reference ended so Rampway can release the retention pin.
|
|
58
|
+
|
|
59
|
+
Old and new generations may overlap for hours, each running its own release code
|
|
60
|
+
and jobs-main endpoint. Multiple retired generations may drain concurrently.
|
|
61
|
+
|
|
62
|
+
## Deploy completion is independent
|
|
63
|
+
|
|
64
|
+
The deploy succeeds when the candidate release is activated and healthy. The
|
|
65
|
+
command and deploy lock do not wait for old jobs generations, workers, jobs,
|
|
66
|
+
HTTP/WebSocket connections, or other retained services to finish. The required
|
|
67
|
+
supervisor contract durably retains generations after the command returns and
|
|
68
|
+
across later deploys and supervisor/host recovery. Every referenced release
|
|
69
|
+
directory must be reported to Rampway and stays pinned against cleanup until the
|
|
70
|
+
last retained process exits. Current Rollbridge provides asynchronous draining
|
|
71
|
+
only for the lifetime of the current daemon; see the current-behavior notes in
|
|
72
|
+
[`docs/config.md`](config.md#processesdeploystrategy) and
|
|
73
|
+
[`docs/cli.md`](cli.md#deploy).
|
|
74
|
+
|
|
75
|
+
HTTP/WebSocket drain and jobs drain are independent. Set `nonBlockingDrain: true`
|
|
76
|
+
so workers stop accepting new handoffs when retirement starts rather than after
|
|
77
|
+
the connection drain. Closing or timing out HTTP connections must never kill a
|
|
78
|
+
still-draining jobs-main or worker pool.
|
|
79
|
+
|
|
80
|
+
## Timeouts and failures
|
|
81
|
+
|
|
82
|
+
`stopSignal`, `lifecycle`, and `gracefulStopMs` remain useful process-stop tools,
|
|
83
|
+
and the jobs framework should enforce per-job timeouts for genuinely hung work.
|
|
84
|
+
They are not the primary deployment solution. A legitimate multi-hour job makes
|
|
85
|
+
a multi-hour generation drain valid; do not turn a normal worker-shutdown timeout
|
|
86
|
+
into the deploy deadline.
|
|
87
|
+
|
|
88
|
+
If a worker connection is actually lost, its old jobs-main applies lease fencing
|
|
89
|
+
and the durable store transitions that make work eligible to return or retry.
|
|
90
|
+
Returned work may then run in the active generation. Normal retirement does not
|
|
91
|
+
simulate a disconnect and does not make a new jobs-main adopt the old worker's
|
|
92
|
+
handoffs.
|
|
93
|
+
|
|
94
|
+
See [`docs/velocious.md`](velocious.md) for the complete topology and
|
|
95
|
+
[`docs/config.md`](config.md#processesdeploystrategy) for handoff-service fields.
|