Skip to content

Commit 7baf5d8

Browse files
deploy: ecd5bc0
1 parent ce54354 commit 7baf5d8

192 files changed

Lines changed: 1119 additions & 700 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

2.0/llms-full.txt

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6437,6 +6437,9 @@ A small cluster is a modest extension of the single-node model:
64376437

64386438
- Run two or more API containers behind a load balancer.
64396439
- Run one or more worker containers for each task queue.
6440+
- Configure [task queue admission](/docs/2.0/polyglot/task-queue-admission)
6441+
for queues that protect a tenant, external API, database pool, or other
6442+
shared downstream dependency.
64406443
- Use shared external MySQL or PostgreSQL for durable history.
64416444
- Use shared Redis or another lock-capable network cache so long polls, queue
64426445
wakeups, and cache locks work across hosts.
@@ -7420,6 +7423,11 @@ REDIS_DB=0
74207423

74217424
Cache must support [atomic locks](https://laravel.com/docs/12.x/cache#atomic-locks). Queue drivers: Redis, Amazon SQS, Beanstalkd, database.
74227425

7426+
Atomic cache locks are required for server-side
7427+
[task queue admission caps](/docs/2.0/polyglot/task-queue-admission) and
7428+
query-task backpressure. Use Redis for multi-node deployments that need
7429+
workflow, activity, or query admission to hold across every server process.
7430+
74237431
### Authentication
74247432

74257433
The server supports three auth modes:
@@ -7687,6 +7695,10 @@ server returns `404` with `reason: "namespace_not_found"`.
76877695

76887696
See the [server README](https://github.com/durable-workflow/server#getting-started-end-to-end-workflow) for a curl-based walkthrough.
76897697

7698+
See [Task Queue Admission](/docs/2.0/polyglot/task-queue-admission) to tune
7699+
worker registration slots, server-side active lease caps, and query-task
7700+
backpressure.
7701+
76907702
## CLI
76917703

76927704
The [Durable Workflow CLI](/docs/2.0/polyglot/cli) provides a shell interface to the server:
@@ -7715,6 +7727,10 @@ dw workflow:start --type=my-workflow --input-file=input.json
77157727

77167728
See the [CLI install page](/docs/2.0/polyglot/cli#install) for a platform-detecting installer and direct binary downloads.
77177729

7730+
Task queue commands include admission status for workflow tasks, activity
7731+
tasks, and query tasks. Use them to distinguish missing workers, saturated
7732+
worker slots, server-side throttling, and query-task overflow.
7733+
77187734
## Deployment
77197735

77207736
Use the [self-hosting deployment guide](/docs/2.0/deployment) to choose a
@@ -7927,6 +7943,12 @@ dw task-queue:list # List active task queues
79277943
dw task-queue:describe <queue> # Describe a task queue
79287944
```
79297945

7946+
`task-queue:list` shows admission status columns for workflow, activity, and
7947+
query tasks. `task-queue:describe` expands the same server payload with active
7948+
leases, remaining capacity, budget source, and query-task pending capacity.
7949+
See [Task Queue Admission](/docs/2.0/polyglot/task-queue-admission) for the
7950+
operator tuning contract behind those fields.
7951+
79307952
### Namespaces
79317953

79327954
```bash
@@ -8360,6 +8382,12 @@ worker and drives one workflow to a terminal state with sequential polling. Use
83608382
| `max_concurrent_workflow_tasks` | `10` | Max parallel workflow tasks |
83618383
| `max_concurrent_activity_tasks` | `10` | Max parallel activity tasks |
83628384
| `shutdown_timeout` | `30.0` | Seconds to drain in-flight tasks on stop |
8385+
8386+
The two `max_concurrent_*` values are advertised to the server during worker
8387+
registration and appear in task queue admission diagnostics. Treat them as the
8388+
worker's local capacity. Use server-side
8389+
[task queue admission](/docs/2.0/polyglot/task-queue-admission) caps when a
8390+
namespace or queue needs a hard shared budget across multiple workers.
83638391
| `metrics` | client's recorder | Optional metrics recorder for poll and task counters/histograms |
83648392

83658393
## Logging
@@ -9042,6 +9070,165 @@ $workflowBridge = app(WorkflowTaskBridge::class);
90429070
$activityBridge = app(ActivityTaskBridge::class);
90439071
```
90449072

9073+
# Task Queue Admission
9074+
9075+
Task queue admission keeps one queue, tenant, or downstream dependency from consuming the whole worker fleet. Durable Workflow exposes admission in three layers:
9076+
9077+
- worker registrations advertise local workflow and activity slots
9078+
- the server can cap active workflow and activity leases per namespace and queue
9079+
- query tasks have a bounded pending queue so synchronous reads fail fast instead of growing without limit
9080+
9081+
Use admission controls when a queue is tied to a rate-limited dependency, tenants share the same server, or operators need to prove why a workflow is waiting.
9082+
9083+
## How The Budget Is Applied
9084+
9085+
Workflow and activity polling starts with the workers that are currently registered for a namespace and task queue. Each worker advertises `max_concurrent_workflow_tasks` and `max_concurrent_activity_tasks`; the server sums active, non-stale workers to calculate the queue's registered slot capacity.
9086+
9087+
Server-side active lease caps are optional. When configured, the server checks a short-lived cache lock before leasing the next workflow or activity task. If the cap is full, polling returns no task for that poll instead of exceeding the budget.
9088+
9089+
Query tasks are different: the control plane enqueues an ephemeral query task and waits for a worker response. `DW_QUERY_TASK_MAX_PENDING_PER_QUEUE` caps how many pending query tasks can exist for each namespace and task queue. When the queue is full, new queries return `query_task_queue_full` with HTTP `429`. If the cache store cannot provide the lock needed to mutate the query-task queue, queries return `query_task_queue_unavailable` with HTTP `503`.
9090+
9091+
## Server Configuration
9092+
9093+
Set global caps when every queue should share the same ceiling:
9094+
9095+
```bash
9096+
DW_WORKFLOW_TASK_MAX_ACTIVE_LEASES_PER_QUEUE=25
9097+
DW_ACTIVITY_TASK_MAX_ACTIVE_LEASES_PER_QUEUE=100
9098+
DW_QUERY_TASK_MAX_PENDING_PER_QUEUE=1024
9099+
```
9100+
9101+
Use `DW_TASK_QUEUE_ADMISSION_OVERRIDES` when specific queues need different budgets. Keys are checked in this order: `namespace:task_queue`, `task_queue`, then `*`.
9102+
9103+
```bash
9104+
DW_TASK_QUEUE_ADMISSION_OVERRIDES='{
9105+
"production:payments": {
9106+
"workflow_tasks": { "max_active_leases_per_queue": 8 },
9107+
"activity_tasks": { "max_active_leases_per_queue": 12 }
9108+
},
9109+
"email": {
9110+
"activity_tasks": { "max_active_leases_per_queue": 4 }
9111+
},
9112+
"*": {
9113+
"workflow_tasks": { "max_active_leases_per_queue": 50 }
9114+
}
9115+
}'
9116+
```
9117+
9118+
The override value also accepts `max_active_leases` as an alias for `max_active_leases_per_queue`.
9119+
9120+
Cache must support atomic locks for server-side active lease caps and query-task admission. Redis is the recommended cache store for multi-node deployments.
9121+
9122+
## Worker Slot Registration
9123+
9124+
Python workers expose local semaphores through `Worker(...)` and send the same values during registration:
9125+
9126+
```python
9127+
worker = Worker(
9128+
client,
9129+
task_queue="payments",
9130+
workflows=[PaymentWorkflow],
9131+
activities=[charge_card, send_receipt],
9132+
max_concurrent_workflow_tasks=8,
9133+
max_concurrent_activity_tasks=12,
9134+
)
9135+
```
9136+
9137+
For custom HTTP workers, send the slot fields to `POST /api/worker/register`:
9138+
9139+
```json
9140+
{
9141+
"worker_id": "payments-python-1",
9142+
"task_queue": "payments",
9143+
"runtime": "python",
9144+
"supported_workflow_types": ["payments.PaymentWorkflow"],
9145+
"supported_activity_types": ["payments.charge_card", "payments.send_receipt"],
9146+
"max_concurrent_workflow_tasks": 8,
9147+
"max_concurrent_activity_tasks": 12
9148+
}
9149+
```
9150+
9151+
Worker slots are not a hard tenant budget by themselves. They describe what active workers can currently process. Add server caps when you need a queue-wide ceiling that still holds if more workers are deployed.
9152+
9153+
## Inspect Admission
9154+
9155+
Use the CLI when debugging:
9156+
9157+
```bash
9158+
dw task-queue:list
9159+
dw task-queue:describe payments
9160+
dw task-queue:describe payments --json | jq '.admission'
9161+
```
9162+
9163+
The server exposes the same data through:
9164+
9165+
- `GET /api/task-queues`
9166+
- `GET /api/task-queues/{name}`
9167+
9168+
An admission payload has three sections:
9169+
9170+
```json
9171+
{
9172+
"workflow_tasks": {
9173+
"status": "throttled",
9174+
"active_worker_count": 3,
9175+
"configured_slot_count": 24,
9176+
"leased_count": 8,
9177+
"ready_count": 5,
9178+
"available_slot_count": 16,
9179+
"server_max_active_leases_per_queue": 8,
9180+
"server_active_lease_count": 8,
9181+
"server_remaining_active_lease_capacity": 0,
9182+
"server_lock_required": true,
9183+
"server_lock_supported": true,
9184+
"budget_source": "worker_registration.max_concurrent_workflow_tasks",
9185+
"server_budget_source": "server.admission.queue_overrides"
9186+
},
9187+
"activity_tasks": {
9188+
"status": "accepting",
9189+
"configured_slot_count": 36,
9190+
"server_max_active_leases_per_queue": 12,
9191+
"server_remaining_active_lease_capacity": 4
9192+
},
9193+
"query_tasks": {
9194+
"status": "accepting",
9195+
"max_pending_per_queue": 1024,
9196+
"approximate_pending_count": 7,
9197+
"remaining_pending_capacity": 1017,
9198+
"lock_supported": true,
9199+
"budget_source": "server.query_tasks.max_pending_per_queue"
9200+
}
9201+
}
9202+
```
9203+
9204+
## Status Reference
9205+
9206+
| Section | Status | Meaning |
9207+
|---------|--------|---------|
9208+
| Workflow/activity | `accepting` | Active workers have available slots and no server cap is full. |
9209+
| Workflow/activity | `throttled` | The optional server-side active lease cap is full. |
9210+
| Workflow/activity | `saturated` | Registered worker slots are all leased, even if no server cap is configured. |
9211+
| Workflow/activity | `no_slots` | Active workers registered zero slots for that task kind. |
9212+
| Workflow/activity | `no_active_workers` | No active, non-stale worker is polling that queue. |
9213+
| Workflow/activity | `unavailable` | A configured server cap needs a cache lock, but the lock is unavailable. |
9214+
| Query | `accepting` | The pending query-task queue has remaining capacity. |
9215+
| Query | `full` | Pending query tasks reached `DW_QUERY_TASK_MAX_PENDING_PER_QUEUE`; new queries return HTTP `429`. |
9216+
| Query | `unavailable` | The query-task queue cannot acquire its cache lock; new queries return HTTP `503`. |
9217+
9218+
## Tuning Pattern
9219+
9220+
1. Start with worker slots sized to the process: CPU-bound workflow tasks are usually lower than I/O-heavy activity tasks.
9221+
2. Add server caps for queues that protect a tenant, external API, database pool, or legacy service.
9222+
3. Inspect `dw task-queue:describe <queue>` during load. `saturated` means add worker capacity or lower workflow fan-out. `throttled` means the server cap is doing its job. `no_active_workers` means the queue has no healthy poller.
9223+
4. Keep query-task capacity large enough for normal operator reads, but low enough to fail fast during incidents. Query-task overflow is backpressure, not data loss.
9224+
9225+
## Related Guides
9226+
9227+
- [Server](/docs/2.0/polyglot/server)
9228+
- [CLI](/docs/2.0/polyglot/cli)
9229+
- [Python SDK](/docs/2.0/polyglot/python)
9230+
- [Worker Protocol](/docs/2.0/polyglot/worker-protocol)
9231+
90459232
# Version Compatibility
90469233

90479234
This page documents version compatibility across Durable Workflow components. All components validate version compatibility at runtime and provide clear error messages when incompatible versions are detected.

0 commit comments

Comments
 (0)