@ewhauser/world-celld 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/NOTICE +1 -1
- package/README.md +106 -106
- package/celld-queue-worker/README.md +26 -0
- package/celld-queue-worker/worker.ts +3 -0
- package/celld-queue-worker/wrangler.jsonc +26 -0
- package/celld-worker/README.md +33 -25
- package/celld-worker/worker.ts +1 -1
- package/celld-worker/wrangler.jsonc +12 -8
- package/dist/index.d.ts +32 -70
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -88
- package/dist/index.js.map +1 -1
- package/dist/queue-consumer.d.ts +33 -0
- package/dist/queue-consumer.d.ts.map +1 -0
- package/dist/queue-consumer.js +75 -0
- package/dist/queue-consumer.js.map +1 -0
- package/dist/queue-delivery-DzNOtCYA.d.ts +49 -0
- package/dist/queue-delivery-DzNOtCYA.d.ts.map +1 -0
- package/dist/{util-Cu09mVzK.js → queue-protocol-CmJNMH_5.js} +60 -12
- package/dist/queue-protocol-CmJNMH_5.js.map +1 -0
- package/dist/queue-protocol-Ds44uuND.js +104 -0
- package/dist/queue-protocol-Ds44uuND.js.map +1 -0
- package/dist/testing.d.ts +3 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +420 -833
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +106 -181
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +602 -955
- package/dist/worker.js.map +1 -1
- package/docs/celld-v0.5-upgrade.md +130 -0
- package/package.json +11 -3
- package/dist/util-Cu09mVzK.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.5.0 (unreleased)
|
|
6
|
+
|
|
7
|
+
### Breaking changes
|
|
8
|
+
|
|
9
|
+
- Internal Queue delivery now requires the `QueueDeliveryRpc` named entrypoint.
|
|
10
|
+
Deploy matching consumer and primary worker scripts together; the former
|
|
11
|
+
`/v1/queue/deliver` route is removed with no fallback.
|
|
12
|
+
|
|
13
|
+
- Require celld v0.5.0. Stop the entire old fleet before starting v0.5.0 nodes;
|
|
14
|
+
mixed-version fleets and older runtime compatibility are not supported.
|
|
15
|
+
- Configure worker variables in deployment `vars`. Removed `CELLD_VAR_*` node
|
|
16
|
+
overrides are not supported; both scripts must deploy the same `WORLD_SECRET`.
|
|
17
|
+
- Remove `CELLD_STORAGE_PROBE`, `CELLD_OUTPUT_GATE`, and `CELLD_OTEL_SINK` from
|
|
18
|
+
node environments. Use `CELLD_OTEL=1` for bucket telemetry or a collector base
|
|
19
|
+
URL for OTLP. Storage checks and durability gating are mandatory.
|
|
20
|
+
|
|
21
|
+
### Improvements
|
|
22
|
+
|
|
23
|
+
- Adopt the runtime's native Queue batching, alarm, storage, and recovery
|
|
24
|
+
improvements with the existing World retention and payload-storage design.
|
|
25
|
+
- Document opt-in telemetry and development variables; verify persisted bucket
|
|
26
|
+
telemetry in the native runtime smoke.
|
|
27
|
+
- Update runtime pins and verified downloads, and ship the
|
|
28
|
+
[upgrade guide](docs/celld-v0.5-upgrade.md) in the npm package.
|
|
29
|
+
|
|
5
30
|
## 0.4.1 (2026-08-24)
|
|
6
31
|
|
|
7
32
|
## What's Changed
|
package/NOTICE
CHANGED
|
@@ -12,7 +12,7 @@ vinnymac/worlds:
|
|
|
12
12
|
src/apply-event.ts
|
|
13
13
|
src/storage.ts
|
|
14
14
|
src/streamer.ts
|
|
15
|
-
src/queue.ts (modified: Cloudflare Queues
|
|
15
|
+
src/queue.ts (modified: Cloudflare Queues adapted to celld Queues and R2)
|
|
16
16
|
src/test-mocks.ts (modified)
|
|
17
17
|
src/util.ts
|
|
18
18
|
src/worker/durable-objects/WorkflowRunDO.ts
|
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@ A [Workflow DevKit](https://useworkflow.dev) `World` backed by
|
|
|
4
4
|
[celld](https://github.com/denoland/celld).
|
|
5
5
|
|
|
6
6
|
`world-celld` stores workflow runs, hooks, and streams in celld cells. Scheduled
|
|
7
|
-
work is delivered
|
|
7
|
+
work is delivered through celld's native Queues, with run-bearing message bodies
|
|
8
|
+
stored in the fleet's object store. This gives Node applications a
|
|
8
9
|
self-hosted alternative to platform-specific Workflow backends.
|
|
9
10
|
|
|
10
11
|
> [!WARNING]
|
|
@@ -15,7 +16,7 @@ self-hosted alternative to platform-specific Workflow backends.
|
|
|
15
16
|
|
|
16
17
|
- Workflow run, step, event, and hook persistence
|
|
17
18
|
- Durable streams
|
|
18
|
-
- Delayed work, retries, deduplication, and dead-letter
|
|
19
|
+
- Delayed work, retries, consumer-side deduplication, and dead-letter routing
|
|
19
20
|
- Configurable cleanup of terminal workflow payloads
|
|
20
21
|
- An authenticated HTTP connection between a Node application and a celld fleet
|
|
21
22
|
- An in-process fleet for local development and conformance testing
|
|
@@ -70,20 +71,47 @@ const world = createCelldWorld({
|
|
|
70
71
|
});
|
|
71
72
|
```
|
|
72
73
|
|
|
73
|
-
`WORKFLOW_BASE_URL` (or `baseUrl`) is where
|
|
74
|
+
`WORKFLOW_BASE_URL` (or `baseUrl`) is where the native Queue bridge delivers flow
|
|
74
75
|
requests. It must be reachable from every celld node.
|
|
75
76
|
|
|
76
77
|
## Deploy the worker
|
|
77
78
|
|
|
78
|
-
Before deploying, you need a celld v0.
|
|
79
|
-
object store that meets celld's conditional-write requirements.
|
|
80
|
-
currently tested runtime baseline. Refer to the
|
|
79
|
+
Before deploying, you need a celld v0.5.0 fleet, `esbuild` on `PATH`, and an
|
|
80
|
+
object store that meets celld's conditional-write requirements. Refer to the
|
|
81
81
|
[celld documentation](https://github.com/denoland/celld) for fleet and storage
|
|
82
82
|
setup.
|
|
83
83
|
|
|
84
|
+
**world-celld 0.5.0 is a breaking release and requires celld v0.5.0.**
|
|
85
|
+
Older runtimes and node-level worker-variable overrides are unsupported.
|
|
86
|
+
|
|
87
|
+
For an existing fleet, follow the [v0.5.0 upgrade and feature review](docs/celld-v0.5-upgrade.md).
|
|
88
|
+
Stop every old celld node before starting v0.5.0; this is not a rolling upgrade.
|
|
89
|
+
|
|
90
|
+
Before either deploy, set `vars.WORLD_SECRET` in both copied Wrangler configs
|
|
91
|
+
to the same secret from your secret manager. Set optional
|
|
92
|
+
`vars.WORKFLOW_CALLBACK_SECRET` in the primary config and
|
|
93
|
+
`vars.WORKFLOW_RETENTION_MS` there to the desired maximum workflow age
|
|
94
|
+
(for example, `"7776000000"` for 90 days). Use private deployment copies outside
|
|
95
|
+
source control: celld persists these variables in the deployment stored in the
|
|
96
|
+
fleet bucket. v0.5.0 rejects `CELLD_VAR_*`; `.dev.vars` is read only by
|
|
97
|
+
`celld dev`, not by `celld deploy`.
|
|
98
|
+
|
|
99
|
+
No second storage service is required for queue payloads. celld v0.5.0 serves
|
|
100
|
+
the `WORKFLOW_QUEUE_PAYLOADS` binding from the existing fleet bucket under
|
|
101
|
+
`r2/workflow-world-queue-payloads/`. `r2_buckets` is the Wrangler-compatible
|
|
102
|
+
configuration key for that binding; it does not require Cloudflare R2.
|
|
103
|
+
|
|
104
|
+
Deploy the Queue consumer first, then the primary HTTP worker. The consumer
|
|
105
|
+
binds the primary worker's `QueueDeliveryRpc` named entrypoint; deploy matching
|
|
106
|
+
versions of both scripts during a maintenance window. The former internal
|
|
107
|
+
`/v1/queue/deliver` route is removed. The order matters:
|
|
108
|
+
the consumer deploy creates the Queue attachment; the primary deploy must go last
|
|
109
|
+
so it remains the fleet's public application.
|
|
110
|
+
|
|
84
111
|
From a source checkout:
|
|
85
112
|
|
|
86
113
|
```sh
|
|
114
|
+
celld deploy ./celld-queue-worker --bucket s3://my-cells-bucket
|
|
87
115
|
celld deploy ./celld-worker --bucket s3://my-cells-bucket
|
|
88
116
|
```
|
|
89
117
|
|
|
@@ -91,29 +119,40 @@ From an installed package, first copy the deployable worker into your project:
|
|
|
91
119
|
|
|
92
120
|
```sh
|
|
93
121
|
cp -R node_modules/@ewhauser/world-celld/celld-worker ./workflow-world
|
|
122
|
+
cp -R node_modules/@ewhauser/world-celld/celld-queue-worker ./workflow-world-queue
|
|
123
|
+
celld deploy ./workflow-world-queue --bucket s3://my-cells-bucket
|
|
94
124
|
celld deploy ./workflow-world --bucket s3://my-cells-bucket
|
|
95
125
|
```
|
|
96
126
|
|
|
97
|
-
|
|
98
|
-
|
|
127
|
+
If upgrading from the former QueueDO implementation, this is a hard queue
|
|
128
|
+
cutover. Existing `QueueDO` messages and dead letters are
|
|
129
|
+
not migrated into celld's native Queue; drain or otherwise account for them
|
|
130
|
+
before upgrading an existing fleet. The old QueueDO cell data becomes
|
|
131
|
+
unreachable after the binding is removed.
|
|
132
|
+
|
|
133
|
+
The worker rejects stateful requests unless `WORLD_SECRET` is configured in its
|
|
134
|
+
deployment. Use that same secret for the application
|
|
135
|
+
(`CELLD_WORLD_SECRET`). Start nodes with the storage and listener settings:
|
|
99
136
|
|
|
100
137
|
```sh
|
|
101
|
-
CELLD_VAR_WORLD_SECRET="$CELLD_WORLD_SECRET" \
|
|
102
|
-
CELLD_VAR_WORKFLOW_RETENTION_MS=7776000000 \
|
|
103
138
|
celld --bucket s3://my-cells-bucket \
|
|
104
139
|
--listen 0.0.0.0:8080 \
|
|
105
140
|
--internal-listen 10.0.0.12:8081 \
|
|
106
141
|
--advertise 10.0.0.12:8081
|
|
107
142
|
```
|
|
108
143
|
|
|
109
|
-
|
|
144
|
+
Keep deployment configs containing secrets out of source control. Keep
|
|
110
145
|
celld's internal listener on a trusted network; the World bearer token protects
|
|
111
146
|
the worker RPC routes, not celld's administrative endpoints.
|
|
112
147
|
|
|
113
|
-
|
|
114
|
-
`WORKFLOW_RETENTION_MS` at `0` to disable that policy.
|
|
148
|
+
Setting `vars.WORKFLOW_RETENTION_MS` to `"7776000000"` enables a fleet-wide
|
|
149
|
+
maximum workflow age of 90 days. Leave `WORKFLOW_RETENTION_MS` at `0` to disable that policy.
|
|
115
150
|
|
|
116
|
-
More deployment detail is in [`celld-worker/README.md`](./celld-worker/README.md)
|
|
151
|
+
More deployment detail is in [`celld-worker/README.md`](./celld-worker/README.md)
|
|
152
|
+
and [`celld-queue-worker/README.md`](./celld-queue-worker/README.md).
|
|
153
|
+
|
|
154
|
+
For opt-in bucket or collector telemetry and local `.dev.vars` setup, see
|
|
155
|
+
the [operational examples](docs/celld-v0.5-upgrade.md#adopt-telemetry).
|
|
117
156
|
|
|
118
157
|
## How it works
|
|
119
158
|
|
|
@@ -129,11 +168,14 @@ celld worker router
|
|
|
129
168
|
|-- HookTokenDO 32 stable token-ownership shards
|
|
130
169
|
|-- HookIdDO 32 stable hook-id lookup shards
|
|
131
170
|
|-- StreamDO stream chunks and run/stream indexes
|
|
132
|
-
|
|
171
|
+
|-- object storage run-bearing queue payload bodies in the fleet bucket
|
|
172
|
+
`-- native Queue producer
|
|
133
173
|
|
|
|
134
|
-
| HTTP callbacks
|
|
135
174
|
v
|
|
136
|
-
|
|
175
|
+
native Queue --> companion consumer --> QueueDeliveryRpc.deliver()
|
|
176
|
+
|
|
|
177
|
+
v
|
|
178
|
+
Workflow application /.well-known/workflow/v1/flow
|
|
137
179
|
```
|
|
138
180
|
|
|
139
181
|
The application-side package implements the Workflow `World` interface and
|
|
@@ -147,8 +189,8 @@ Index routing is a versioned hard-cutover protocol. Run catalog writes hash the
|
|
|
147
189
|
natural keys across 32 shards each. A single stateless worker request queries
|
|
148
190
|
all 16 catalog shards in parallel and merges their lexicographic pages before
|
|
149
191
|
authoritative RunDO reads. The RunDO is also the only permanent lifecycle
|
|
150
|
-
authority: hook shards consult it directly, while
|
|
151
|
-
fences compact after bounded protocol leases. Token and hook-ID admission are
|
|
192
|
+
authority: hook shards and queue idempotency claims consult it directly, while
|
|
193
|
+
catalog expiry fences compact after bounded protocol leases. Token and hook-ID admission are
|
|
152
194
|
each transactional inside their natural-key shards. Exact claim IDs plus a
|
|
153
195
|
serialized cancellation fence resolve ambiguous delivery without pretending
|
|
154
196
|
the two ownership shards form a cross-Durable-Object transaction. Stateless cohesive index routes keep
|
|
@@ -172,7 +214,6 @@ variable is shown below.
|
|
|
172
214
|
| `secret` | `CELLD_WORLD_SECRET` | required with `fleetUrl` |
|
|
173
215
|
| `baseUrl` | `WORKFLOW_BASE_URL` | `http://localhost:$PORT` |
|
|
174
216
|
| `deploymentId` | `CELLD_DEPLOYMENT_ID` | `celld-default` |
|
|
175
|
-
| `queueShards` | — | `1` |
|
|
176
217
|
| `runRetentionMs` | `CELLD_RUN_RETENTION_MS` | `0` (disabled) |
|
|
177
218
|
| `streamLongPollMs` | — | `20000` |
|
|
178
219
|
| `streamFlushIntervalMs` | — | `0` |
|
|
@@ -180,30 +221,22 @@ variable is shown below.
|
|
|
180
221
|
|
|
181
222
|
The deployed worker also accepts these celld variables:
|
|
182
223
|
|
|
183
|
-
| Variable
|
|
184
|
-
|
|
|
185
|
-
| `WORLD_SECRET`
|
|
186
|
-
| `WORKFLOW_CALLBACK_SECRET`
|
|
187
|
-
| `
|
|
188
|
-
| `
|
|
189
|
-
| `QUEUE_DELIVERY_TIMEOUT_MS` | `300000` | Callback timeout (maximum `300000`) |
|
|
190
|
-
| `WORKFLOW_RETENTION_MS` | `0` | Maximum run age from creation; includes active runs |
|
|
191
|
-
| `WORKFLOW_RETENTION_BATCH_SIZE` | `128` | Runs admitted by each cron sweep (maximum `1000`) |
|
|
192
|
-
| `WORKFLOW_RETENTION_QUEUE_SHARDS` | `1` | Queue-shard fallback for older runs |
|
|
193
|
-
|
|
194
|
-
`queueShards` is part of queue placement and is pinned when a queue cell is
|
|
195
|
-
first used. It may be any positive safe integer; the `128` storage-operation
|
|
196
|
-
batch limit does not cap the fleet's total shard count. Drain pending work
|
|
197
|
-
before changing it.
|
|
224
|
+
| Variable | Default | Purpose |
|
|
225
|
+
| ------------------------------- | ------- | ---------------------------------------------------- |
|
|
226
|
+
| `WORLD_SECRET` | none | Required bearer secret for RPC routes |
|
|
227
|
+
| `WORKFLOW_CALLBACK_SECRET` | none | Sent with deliveries as `x-workflow-callback-secret` |
|
|
228
|
+
| `WORKFLOW_RETENTION_MS` | `0` | Maximum run age from creation; includes active runs |
|
|
229
|
+
| `WORKFLOW_RETENTION_BATCH_SIZE` | `128` | Runs admitted by each cron sweep (maximum `1000`) |
|
|
198
230
|
|
|
199
231
|
Queue deadlines use fixed-width 13-digit epoch-millisecond keys. A requested
|
|
200
232
|
`delaySeconds` or handler redelivery timeout is accepted only when its deadline
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
are chunked at the host timer limit without changing this
|
|
233
|
+
leaves room for the 15-minute idempotency-claim window below the absolute
|
|
234
|
+
`9999999999999` limit. A suspension that cannot preserve that headroom becomes
|
|
235
|
+
a normal broker retry instead of being clamped. celld's native
|
|
236
|
+
producer delay is capped at 86,400 seconds, so the consumer chains longer waits
|
|
237
|
+
while preserving the absolute deadline and stable message identity. Long
|
|
238
|
+
test-mode waits are chunked at the host timer limit without changing this
|
|
239
|
+
deadline contract.
|
|
207
240
|
|
|
208
241
|
## Workflow retention
|
|
209
242
|
|
|
@@ -217,8 +250,9 @@ failed, and cancelled runs.
|
|
|
217
250
|
The packaged worker declares an hourly UTC celld cron trigger. Each occurrence
|
|
218
251
|
scans one bounded creation-time catalog page and asks the authoritative run
|
|
219
252
|
cells to enforce the cutoff. A run cell immediately fences reads, writes, stream
|
|
220
|
-
activity, and queue work, removes its catalog entry, then finishes the
|
|
221
|
-
bounded stream, queue, and payload cleanup phases through
|
|
253
|
+
activity, and queue payload work, removes its catalog entry, then finishes the
|
|
254
|
+
existing bounded stream, object-store queue-payload, and run-payload cleanup phases through
|
|
255
|
+
its durable alarm.
|
|
222
256
|
Repeated cron invocations and alarm retries are idempotent.
|
|
223
257
|
|
|
224
258
|
One occurrence admits at most `WORKFLOW_RETENTION_BATCH_SIZE` runs (default
|
|
@@ -227,11 +261,6 @@ backlog, later occurrences continue with the next oldest entries. Edit
|
|
|
227
261
|
`triggers.crons` in the copied `celld-worker/wrangler.jsonc` if hourly discovery
|
|
228
262
|
does not provide the desired expiration resolution or catch-up rate.
|
|
229
263
|
|
|
230
|
-
New runs persist the application's `queueShards` value for complete queue
|
|
231
|
-
cleanup. `WORKFLOW_RETENTION_QUEUE_SHARDS` is the fallback for runs created
|
|
232
|
-
before that metadata existed and must match the placement used by those runs.
|
|
233
|
-
Both values may be any positive safe integer.
|
|
234
|
-
|
|
235
264
|
### Terminal payload retention
|
|
236
265
|
|
|
237
266
|
When `runRetentionMs` is greater than zero, a terminal transition atomically
|
|
@@ -240,8 +269,10 @@ step, hook, and stream data remains readable until that deadline. Active and
|
|
|
240
269
|
pending runs are never eligible for automatic cleanup.
|
|
241
270
|
|
|
242
271
|
At expiration, the run cell fences new writes and removes the run's derived
|
|
243
|
-
indexes, stream chunks,
|
|
244
|
-
|
|
272
|
+
indexes, stream chunks, object-store queue payloads, and durable run payloads.
|
|
273
|
+
Native Queue pointer messages can remain until celld's fixed four-day retention
|
|
274
|
+
expires; a pointer whose object-store body was removed is acknowledged as permanently gone
|
|
275
|
+
and cannot resurrect the run. Cleanup is a persisted, idempotent state machine. Each alarm processes
|
|
245
276
|
one bounded page, persists its progress, and re-arms the next alarm; an
|
|
246
277
|
interrupted phase records its error and retries with capped backoff.
|
|
247
278
|
|
|
@@ -273,10 +304,8 @@ fleet-wide maximum age are both enabled, the earlier deadline wins.
|
|
|
273
304
|
|
|
274
305
|
- Delivery is at least once. Workflow steps and other external side effects
|
|
275
306
|
must be idempotent.
|
|
276
|
-
- Queue
|
|
277
|
-
`
|
|
278
|
-
`purgeDeadLetters` deletes at most 128 entries per call; repeat it while its
|
|
279
|
-
result reports `hasMore`.
|
|
307
|
+
- Native Queue operations are administered with celld's `queue info`, `peek`,
|
|
308
|
+
`pause`, `resume`, `purge`, and `redrive` commands.
|
|
280
309
|
- Run cells expose retention status, scheduling, immediate cleanup, and alarm
|
|
281
310
|
recovery through `world.retention`.
|
|
282
311
|
- A new application URL applies to newly enqueued messages. Existing messages
|
|
@@ -307,14 +336,21 @@ pnpm test:integration
|
|
|
307
336
|
|
|
308
337
|
### Real celld restart smoke
|
|
309
338
|
|
|
310
|
-
The required CI smoke owns native celld v0.
|
|
339
|
+
The required CI smoke owns native celld v0.5.0 and MinIO processes on loopback,
|
|
311
340
|
uses fresh temporary bucket and runtime state, and kills celld with `SIGKILL`
|
|
312
341
|
before deleting its local working state and starting a new process against the
|
|
313
342
|
bucket-backed state. It checks that:
|
|
314
343
|
|
|
344
|
+
- opt-in runtime telemetry is persisted as nonempty Parquet files in the fleet bucket;
|
|
315
345
|
- acknowledged run and stream state survives the process restart;
|
|
316
346
|
- an accepted delayed queue message that becomes due while celld is down is
|
|
317
|
-
delivered once after the
|
|
347
|
+
delivered once after the native broker is restored;
|
|
348
|
+
- the companion Queue consumer can call the named `QueueDeliveryRpc` service
|
|
349
|
+
entrypoint and recover run-bearing payloads from the fleet object store;
|
|
350
|
+
- transient callbacks increment attempts while suspension preserves the attempt
|
|
351
|
+
count and message identity;
|
|
352
|
+
- invalid credentials, envelopes, and attempts are rejected across real RPC;
|
|
353
|
+
- the former HTTP delivery route returns 404;
|
|
318
354
|
- multi-page retention cleanup continues from a persisted nonterminal phase;
|
|
319
355
|
- cancelling an in-flight HTTP long poll leaves the stream writable and
|
|
320
356
|
readable.
|
|
@@ -326,9 +362,8 @@ pnpm test:integration:celld-smoke
|
|
|
326
362
|
```
|
|
327
363
|
|
|
328
364
|
The runner downloads celld and MinIO artifacts at pinned versions and verifies
|
|
329
|
-
their SHA-256 digests before use.
|
|
330
|
-
|
|
331
|
-
the storage probe and runs exactly one celld process at a time. It proves the
|
|
365
|
+
their SHA-256 digests before use. celld performs its required storage-contract
|
|
366
|
+
probe at startup; the smoke runs exactly one celld process at a time. It proves the
|
|
332
367
|
single-process restart boundaries above, not multi-node ownership, handoff, or
|
|
333
368
|
failover correctness.
|
|
334
369
|
|
|
@@ -340,13 +375,12 @@ message reaches a successful callback, including forced `503` redeliveries. A
|
|
|
340
375
|
second workload creates terminal runs with streams and delayed queue messages,
|
|
341
376
|
then verifies complete payload cleanup without resurrection. Results include
|
|
342
377
|
queue and cleanup throughput plus p50, p95, p99, and maximum latency and are
|
|
343
|
-
saved under `.perf-results/`. The harness
|
|
378
|
+
saved under `.perf-results/`. The harness pins celld v0.5.0 and deploys the same
|
|
379
|
+
two-script native Queue topology as the restart smoke.
|
|
344
380
|
|
|
345
|
-
>
|
|
346
|
-
>
|
|
347
|
-
>
|
|
348
|
-
> the queue's persistence, redelivery, and performance paths only; it does not
|
|
349
|
-
> validate multi-node ownership or failover correctness.
|
|
381
|
+
> This harness runs a single celld node and leaves the required storage-contract
|
|
382
|
+
> probe enabled. Passing it does not establish multi-node fencing, ownership
|
|
383
|
+
> handoff, or production suitability for a particular object-store deployment.
|
|
350
384
|
|
|
351
385
|
With Docker and the Compose plugin installed, run:
|
|
352
386
|
|
|
@@ -354,43 +388,10 @@ With Docker and the Compose plugin installed, run:
|
|
|
354
388
|
pnpm test:perf:minio
|
|
355
389
|
```
|
|
356
390
|
|
|
357
|
-
The defaults send 1,000 messages with concurrency 32
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
The following directional baseline was recorded on August 21, 2026. MinIO and
|
|
363
|
-
celld ran as native arm64 processes because Docker was unavailable on the test
|
|
364
|
-
machine; container results will differ.
|
|
365
|
-
|
|
366
|
-
| Environment | Value |
|
|
367
|
-
| ----------- | -------------------------------------------------------------------------------------------------------- |
|
|
368
|
-
| Machine | MacBook Pro (Mac15,8), Apple M3 Max, 16 cores (12 performance, 4 efficiency), 64 GB RAM |
|
|
369
|
-
| OS | macOS 26.5.2 (25F84), arm64 |
|
|
370
|
-
| Services | celld v0.3.0; MinIO RELEASE.2025-09-07T16-13-09Z |
|
|
371
|
-
| Workload | 1,000 messages; concurrency 32; 2 queue shards; 256-byte target payload; every 20th message retried once |
|
|
372
|
-
|
|
373
|
-
| Metric | Result |
|
|
374
|
-
| ----------------------- | --------------------------------------------------------------------------------: |
|
|
375
|
-
| Delivery integrity | 1,000 accepted, 1,000 delivered, 0 missing, 0 duplicate successes, 0 dead letters |
|
|
376
|
-
| Callback attempts | 1,050, including 50 forced redeliveries |
|
|
377
|
-
| Enqueue throughput | 865.66 messages/s |
|
|
378
|
-
| Delivery throughput | 794.60 messages/s |
|
|
379
|
-
| Enqueue latency | p50 26.06 ms; p95 70.72 ms; p99 146.59 ms; max 157.70 ms |
|
|
380
|
-
| All delivery latency | p50 65.05 ms; p95 184.47 ms; p99 225.10 ms; max 328.75 ms |
|
|
381
|
-
| First-attempt latency | p50 63.65 ms; p95 177.93 ms; p99 209.38 ms; max 226.86 ms |
|
|
382
|
-
| Retried-message latency | p50 114.81 ms; p95 286.35 ms; p99 328.75 ms; max 328.75 ms |
|
|
383
|
-
| Final queue state | 0 pending, 0 in flight, 0 dead letters across both shards |
|
|
384
|
-
|
|
385
|
-
The retention workload in the same run tombstoned 100 of 100 terminal runs,
|
|
386
|
-
expired all 100 reads, deleted 400 payload keys, 100 streams, and 100 delayed
|
|
387
|
-
queue messages, and left both queue shards empty.
|
|
388
|
-
|
|
389
|
-
Use these numbers as a smoke-test reference, not a portable performance
|
|
390
|
-
guarantee. For regression tracking, compare repeated runs on the same machine
|
|
391
|
-
and runtime configuration. Two shards gave the best latency balance for this
|
|
392
|
-
workload; benchmark your own traffic before changing the shard count. A fleet
|
|
393
|
-
pins its shard count on first use, so changing it requires draining the queues.
|
|
391
|
+
The defaults send 1,000 messages with concurrency 32 and force every twentieth
|
|
392
|
+
message through one `503` redelivery. The old QueueDO/shard baseline is not
|
|
393
|
+
comparable to the native celld Queue path and was intentionally removed; record
|
|
394
|
+
a fresh machine-specific baseline before enabling budgets.
|
|
394
395
|
|
|
395
396
|
Override the workload or set machine-specific regression budgets with
|
|
396
397
|
environment variables:
|
|
@@ -398,18 +399,17 @@ environment variables:
|
|
|
398
399
|
```sh
|
|
399
400
|
PERF_MESSAGES=10000 \
|
|
400
401
|
PERF_CONCURRENCY=64 \
|
|
401
|
-
PERF_QUEUE_SHARDS=16 \
|
|
402
402
|
PERF_MIN_DELIVERY_PER_SECOND=100 \
|
|
403
403
|
PERF_MAX_DELIVERY_P99_MS=5000 \
|
|
404
404
|
pnpm test:perf:minio
|
|
405
405
|
```
|
|
406
406
|
|
|
407
407
|
Useful controls are `PERF_PAYLOAD_BYTES`, `PERF_RETRY_EVERY`,
|
|
408
|
-
`PERF_TIMEOUT_MS`,
|
|
408
|
+
`PERF_TIMEOUT_MS`,
|
|
409
409
|
`PERF_MIN_ENQUEUE_PER_SECOND`, `PERF_MIN_DELIVERY_PER_SECOND`, and
|
|
410
410
|
`PERF_MAX_DELIVERY_P99_MS`. Throughput and latency budgets default to disabled
|
|
411
|
-
because local machines vary. Message-loss,
|
|
412
|
-
|
|
411
|
+
because local machines vary. Message-loss, message-ID, callback validity, and
|
|
412
|
+
duplicate-success checks are always enforced.
|
|
413
413
|
|
|
414
414
|
Bug reports and focused pull requests are welcome. Please include a regression
|
|
415
415
|
test for behavior changes and run the checks above before submitting a PR.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# world-celld Queue consumer
|
|
2
|
+
|
|
3
|
+
This companion celld v0.5.0 script consumes the native `workflow-world` Queue
|
|
4
|
+
and forwards each delivery to the primary `workflow-world` script through a
|
|
5
|
+
`QueueDeliveryRpc` named service binding. The consumer calls
|
|
6
|
+
`deliver(secret, envelope, attempt)` and handles `complete`, `suspend`, or `retry`
|
|
7
|
+
results. RPC exceptions retry without acknowledgment. The separate script keeps the broker consumer attachment independent of the
|
|
8
|
+
public HTTP worker.
|
|
9
|
+
|
|
10
|
+
Deploy it before the primary worker so the Queue consumer attachment exists:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
cp -r node_modules/@ewhauser/world-celld/celld-queue-worker ./workflow-world-queue
|
|
14
|
+
celld deploy ./workflow-world-queue --bucket s3://my-cells-bucket
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Before deploying, set `vars.WORLD_SECRET` in the copied Wrangler config to the
|
|
18
|
+
same bearer secret used by the primary worker. Keep that private deployment
|
|
19
|
+
config out of source control. v0.5.0 rejects `CELLD_VAR_*` node overrides.
|
|
20
|
+
Deploy the primary worker after this script so it remains the fleet's
|
|
21
|
+
public application. Native Queue operations are available through `celld queue
|
|
22
|
+
info`, `peek`, `pause`, `resume`, `purge`, and `redrive`.
|
|
23
|
+
|
|
24
|
+
The primary worker must export `QueueDeliveryRpc`, and this script's service
|
|
25
|
+
binding must set `entrypoint` to that name. Deploy matching worker versions
|
|
26
|
+
during a maintenance window; there is no fallback to `/v1/queue/deliver`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "workflow-world-queue-consumer",
|
|
3
|
+
"main": "worker.ts",
|
|
4
|
+
"compatibility_date": "2026-01-01",
|
|
5
|
+
"services": [
|
|
6
|
+
{ "binding": "WORLD_SERVICE", "service": "workflow-world", "entrypoint": "QueueDeliveryRpc" },
|
|
7
|
+
],
|
|
8
|
+
"queues": {
|
|
9
|
+
"producers": [{ "binding": "WORKFLOW_QUEUE", "queue": "workflow-world" }],
|
|
10
|
+
"consumers": [
|
|
11
|
+
{
|
|
12
|
+
"queue": "workflow-world",
|
|
13
|
+
"max_batch_size": 10,
|
|
14
|
+
"max_batch_timeout": 1,
|
|
15
|
+
"max_retries": 100,
|
|
16
|
+
"dead_letter_queue": "workflow-world-dlq",
|
|
17
|
+
"max_concurrency": 25,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
"vars": {
|
|
22
|
+
// REQUIRED. Inject the same value used by the primary worker through
|
|
23
|
+
// vars in a private deployment copy of this config; never commit secrets.
|
|
24
|
+
"WORLD_SECRET": "",
|
|
25
|
+
},
|
|
26
|
+
}
|
package/celld-worker/README.md
CHANGED
|
@@ -1,52 +1,60 @@
|
|
|
1
1
|
# world-celld worker
|
|
2
2
|
|
|
3
|
-
The celld-deployable half of `@ewhauser/world-celld`:
|
|
4
|
-
(WorkflowRunDO, RunCatalogDO, HookTokenDO, HookIdDO, StreamDO
|
|
5
|
-
|
|
6
|
-
Storage
|
|
7
|
-
bounded binary batch writes and binary long-poll
|
|
3
|
+
The primary celld-deployable half of `@ewhauser/world-celld`: five cell classes
|
|
4
|
+
(WorkflowRunDO, RunCatalogDO, HookTokenDO, HookIdDO, and StreamDO) behind an
|
|
5
|
+
authenticated HTTP router, plus the `QueueDeliveryRpc` named entrypoint
|
|
6
|
+
for the companion consumer. Storage and control methods use fixed JSON RPC
|
|
7
|
+
routes; stream chunks use bounded binary batch writes and binary long-poll
|
|
8
|
+
reads. Queue producers use celld's native Queue binding, with run-bearing
|
|
9
|
+
payload bodies stored in the fleet's object store.
|
|
8
10
|
|
|
9
11
|
## Deploy
|
|
10
12
|
|
|
11
|
-
Copy this directory out of `node_modules` so
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
Copy this directory and the companion Queue consumer out of `node_modules` so
|
|
14
|
+
`celld deploy` can bundle them. Deploy the consumer first and this primary
|
|
15
|
+
worker last. Before deploying, set `vars.WORLD_SECRET` in both copied
|
|
16
|
+
Wrangler configs to the same secret from your secret manager. Keep those
|
|
17
|
+
private deployment copies out of source control; celld stores their variables
|
|
18
|
+
in the fleet bucket. Stop the whole old fleet before starting v0.5.0 nodes:
|
|
14
19
|
|
|
15
20
|
```sh
|
|
16
21
|
cp -r node_modules/@ewhauser/world-celld/celld-worker ./workflow-world
|
|
22
|
+
cp -r node_modules/@ewhauser/world-celld/celld-queue-worker ./workflow-world-queue
|
|
23
|
+
celld deploy ./workflow-world-queue --bucket s3://my-cells-bucket
|
|
17
24
|
celld deploy ./workflow-world --bucket s3://my-cells-bucket
|
|
18
25
|
```
|
|
19
26
|
|
|
27
|
+
Upgrading from the former QueueDO implementation is a hard cutover. Drain or
|
|
28
|
+
account for its pending messages and dead letters first; they are not migrated
|
|
29
|
+
to the native Queue.
|
|
30
|
+
|
|
20
31
|
Requirements:
|
|
21
32
|
|
|
22
|
-
- celld v0.
|
|
33
|
+
- celld v0.5.0 (the currently tested runtime baseline).
|
|
23
34
|
- `esbuild` on PATH (celld shells out to it).
|
|
24
35
|
- A bucket with conditional-write support (celld's fencing requirement).
|
|
25
|
-
- `WORLD_SECRET`
|
|
36
|
+
- `WORLD_SECRET` set in the deployment config (`vars.WORLD_SECRET`) —
|
|
26
37
|
the router fails closed with 503 while it is empty.
|
|
27
38
|
|
|
39
|
+
Queue payloads do not require another provider or another set of credentials.
|
|
40
|
+
celld maps the `WORKFLOW_QUEUE_PAYLOADS` binding into the fleet bucket under
|
|
41
|
+
`r2/workflow-world-queue-payloads/`. The `r2_buckets` key in `wrangler.jsonc`
|
|
42
|
+
names the Workers-compatible binding API; it does not require Cloudflare R2.
|
|
43
|
+
|
|
28
44
|
## Fleet-wide retention
|
|
29
45
|
|
|
30
46
|
The bundled `wrangler.jsonc` declares an hourly UTC cron trigger. It does no
|
|
31
|
-
catalog work by default. Set `
|
|
32
|
-
enable a maximum workflow age
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
CELLD_VAR_WORKFLOW_RETENTION_MS=7776000000 \
|
|
36
|
-
celld --bucket s3://my-cells-bucket
|
|
37
|
-
```
|
|
47
|
+
catalog work by default. Set `vars.WORKFLOW_RETENTION_MS` to `"7776000000"`
|
|
48
|
+
in the primary Wrangler config and redeploy to enable a maximum workflow age
|
|
49
|
+
measured from run creation. v0.5.0 rejects `CELLD_VAR_*` node overrides.
|
|
50
|
+
`.dev.vars` overrides apply only to `celld dev`, not production deployments.
|
|
38
51
|
|
|
39
52
|
`7776000000` is 90 days. The policy includes pending and running workflows as
|
|
40
53
|
well as terminal ones. Each cron occurrence admits at most
|
|
41
54
|
`WORKFLOW_RETENTION_BATCH_SIZE` runs (default `128`); the existing per-run alarm
|
|
42
|
-
state machine finishes bounded index, stream, queue, and payload
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
`WORKFLOW_RETENTION_QUEUE_SHARDS` to the historical positive-safe-integer
|
|
46
|
-
queue-shard count when cleaning runs created before this worker version. The
|
|
47
|
-
`128` multikey storage-operation limit is not a total-shard ceiling. Edit
|
|
48
|
-
`triggers.crons` in the copied config if hourly discovery is not the desired
|
|
49
|
-
resolution.
|
|
55
|
+
state machine finishes bounded index, stream, object-store queue-payload, and run-payload
|
|
56
|
+
cleanup. Edit `triggers.crons` in the copied config if hourly discovery is not
|
|
57
|
+
the desired resolution.
|
|
50
58
|
|
|
51
59
|
Point the app at any node's public listener:
|
|
52
60
|
|
package/celld-worker/worker.ts
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
{ "name": "WORKFLOW_RUN_CATALOG", "class_name": "RunCatalogDO" },
|
|
13
13
|
{ "name": "WORKFLOW_HOOK_TOKENS", "class_name": "HookTokenDO" },
|
|
14
14
|
{ "name": "WORKFLOW_HOOK_IDS", "class_name": "HookIdDO" },
|
|
15
|
-
{ "name": "WORKFLOW_QUEUE", "class_name": "QueueDO" },
|
|
16
15
|
],
|
|
17
16
|
},
|
|
18
17
|
"migrations": [
|
|
@@ -24,10 +23,20 @@
|
|
|
24
23
|
"RunCatalogDO",
|
|
25
24
|
"HookTokenDO",
|
|
26
25
|
"HookIdDO",
|
|
27
|
-
"QueueDO",
|
|
28
26
|
],
|
|
29
27
|
},
|
|
30
28
|
],
|
|
29
|
+
"queues": {
|
|
30
|
+
"producers": [{ "binding": "WORKFLOW_QUEUE", "queue": "workflow-world" }],
|
|
31
|
+
},
|
|
32
|
+
// celld maps this Workers-compatible binding into the existing fleet bucket
|
|
33
|
+
// at r2/<bucket_name>/; it does not require a Cloudflare R2 deployment.
|
|
34
|
+
"r2_buckets": [
|
|
35
|
+
{
|
|
36
|
+
"binding": "WORKFLOW_QUEUE_PAYLOADS",
|
|
37
|
+
"bucket_name": "workflow-world-queue-payloads",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
31
40
|
// Fleet-wide maximum-age retention is discovered hourly. The handler is a
|
|
32
41
|
// no-op while WORKFLOW_RETENTION_MS is "0". Change this expression if a
|
|
33
42
|
// different sweep cadence is required; celld cron schedules are UTC.
|
|
@@ -35,7 +44,7 @@
|
|
|
35
44
|
"vars": {
|
|
36
45
|
// REQUIRED: bearer secret for the world's RPC and binary stream surface.
|
|
37
46
|
// Authenticated routes fail closed with 503 while this is empty. Inject the real value
|
|
38
|
-
//
|
|
47
|
+
// into vars in a private deployment copy of this config; never commit secrets.
|
|
39
48
|
"WORLD_SECRET": "",
|
|
40
49
|
// Optional: forwarded as x-workflow-callback-secret on queue deliveries.
|
|
41
50
|
"WORKFLOW_CALLBACK_SECRET": "",
|
|
@@ -44,10 +53,5 @@
|
|
|
44
53
|
"WORKFLOW_RETENTION_MS": "0",
|
|
45
54
|
// Maximum runs admitted to cleanup by one hourly sweep (1..1000).
|
|
46
55
|
"WORKFLOW_RETENTION_BATCH_SIZE": "128",
|
|
47
|
-
// Must match createCelldWorld({ queueShards }) so every queue shard is
|
|
48
|
-
// included in cleanup for active runs discovered by the sweep.
|
|
49
|
-
"WORKFLOW_RETENTION_QUEUE_SHARDS": "1",
|
|
50
|
-
// Optional queue tuning (strings): QUEUE_MAX_ATTEMPTS, QUEUE_MAX_INFLIGHT,
|
|
51
|
-
// QUEUE_DELIVERY_TIMEOUT_MS.
|
|
52
56
|
},
|
|
53
57
|
}
|