Alastair
07/06/2026, 3:52 PMMarvin
07/06/2026, 3:52 PMMarvin
07/06/2026, 3:55 PMqueue-a
- worker 2 listens only to queue-b
- deployment X submits to queue-a
- deployment Y submits to queue-b
That gives you indirect targeting.
Example:
bash
prefect worker start --pool my-pool --work-queue queue-a
prefect worker start --pool my-pool --work-queue queue-b
Then configure deployment A to use queue-a, deployment B to use queue-b.
Important caveat
If two workers listen to the same queue, you are not guaranteed which one will pick up a given run. It’s effectively first-available / first-to-claim.
So if you need deterministic routing:
- use different queues, and/or
- use different work pools
Recommended pattern
If your intent is something like:
- one worker on machine A
- one worker on machine B
- certain flows must go to A, others to B
then use either:
1. One work pool, multiple work queues
- worker A -> queue-a
- worker B -> queue-b
or
2. Separate work pools
- pool-a
- pool-b
Separate pools are often cleaner if the infrastructure/environment is meaningfully different.
CLI reference
Worker startup supports binding to a pool and queue(s):
prefect worker start --help
Docs:
- Work pools
- Workers
- Work queue management
If you want, I can also show you exactly how to configure a deployment to land on a specific work queue in prefect.yaml or in Python.Alastair
07/06/2026, 3:57 PMMarvin
07/06/2026, 3:57 PMMarvin
07/06/2026, 3:57 PMqueue-a
- worker B -> listens only on queue-b
then assigning a deployment to queue-a is a reasonable way to make sure it runs on worker A’s infrastructure.
And yes, your understanding is correct:
- one worker per queue -> effectively deterministic routing
- multiple workers on the same queue -> any of those workers may pick up the run
A couple practical notes:
- Keep both workers in the same work pool if the only difference is routing
- Use different work pools if the base infrastructure or job template differs substantially
- Queue-based routing is a normal pattern in Prefect 3.x
If you want, I can give you a minimal example of:
1. creating the queues,
2. starting each worker against its queue, and
3. assigning a deployment to the right queue in prefect.yaml or Python.Alastair
07/06/2026, 4:09 PMMarvin
07/06/2026, 4:09 PMMarvin
07/06/2026, 4:11 PMSIGTERM / SIGINT for graceful shutdown
- the worker stops polling and lets active runs finish
2. Pausing the work pool
- prevents workers in that pool from picking up new runs
- does not stop the worker process itself
So the nicest built-in pattern is usually:
- pause the pool (or otherwise stop new work from arriving)
- send SIGTERM to the worker process from your process manager / host environment
Example:
bash
prefect work-pool pause my-pool
kill -TERM <worker-pid>
CLI reference:
- Work pools
- Workers
I also verified the pause command exists:
bash
prefect work-pool pause my-pool
About “a flow that kills its own worker”
Technically, yes, a flow could do something crude like os.kill(...) if it can discover the right PID and has permission, but I would not recommend that pattern:
- brittle
- tightly couples flow code to worker process internals
- easy to kill the wrong process
- not a Prefect-supported lifecycle mechanism
Also, a flow run is not given an official “shutdown my worker” API.
Recommended approaches
If you want a worker to terminate after some condition, better options are:
- run the worker under systemd, Docker, Kubernetes, supervisord, etc.
- have that supervisor decide when to stop/restart it
- optionally pause the pool first so no new work is claimed
If your goal is “worker A should do one thing, then go away”, you might also consider:
- launching the worker externally for a bounded period
- or using --run-once-style behavior if your workflow fits that model
If you want, I can help sketch a safe self-draining pattern for your setup, e.g.:
- worker listens on queue A
- flow requests drain
- external controller notices and sends SIGTERM
- worker exits after current run completes
That’s usually the cleanest approximation of “remote shutdown.”Alastair
07/06/2026, 4:15 PMMarvin
07/06/2026, 4:16 PMMarvin
07/06/2026, 4:16 PMsession-abc123
- start one ECS worker task listening only on that queue
- deployments / triggered runs for that session target only that queue
- every operator action updates a TTL record
- an external reaper checks TTL expiry
- on expiry:
- stop sending work to that queue
- optionally pause the queue/pool or mark session closed in your app
- wait for any active run to finish
- stop the ECS task
- optionally delete the queue if it was ephemeral
This gives you deterministic routing and a nice cleanup boundary.
About EventBridge schedule per interaction
That can work, but I’d consider whether a single periodic reaper is simpler than constantly updating schedules.
Two possible designs:
Option A: reschedule EventBridge every interaction
- on each command, push expiry out by N minutes
- EventBridge fires at expiry and stops the ECS task
Pros:
- direct
- low conceptual overhead
Cons:
- more moving pieces
- repeated schedule mutation
- possible race conditions around updates near firing time
Option B: periodic sweeper / reaper
- store last_activity_at or expires_at somewhere durable
- run a small periodic checker every minute
- checker stops any ECS worker session whose expires_at < now and is idle
Pros:
- simpler state model
- easier to reason about
- better if you’ll have many concurrent sessions
- easier to add “don’t kill if run is active”
Cons:
- timeout precision is to the polling interval, not exact second
For most session systems, Option B is usually more robust.
Very important design point: how do you know whether it is safe to stop?
You’ll want your controller to know at least:
- ECS task ID / ARN for the worker session
- associated Prefect work pool + queue
- session expiry time
- whether a flow run is currently active
If you can detect active work, then your shutdown logic becomes:
- if expired and idle -> stop ECS task now
- if expired and busy -> mark draining, then stop after completion
How to make “draining” work well
Since Prefect does not let you stop a single worker via API, draining usually means preventing more work from being submitted to that session.Marvin
07/06/2026, 4:16 PMprefect work-pool pause because it affects the whole pool, not just one session worker.
In your architecture, that suggests:
- don’t use work-pool pause for per-session draining if many sessions share the pool
- instead use:
- one queue per session, and
- application-level routing control, and
- ECS stop logic per session
That seems much more aligned with your goal.
Practical recommendation
Given what you described, I would recommend:
- one shared work pool for this worker type
- one dedicated work queue per ad hoc session
- one ECS worker task per session listening only to that queue
- MCP tracks expires_at
- periodic reaper checks for expired sessions
- if expired:
- block new submissions for that session
- if no active flow run, stop ECS task
- if active flow run, wait and stop when complete
That’s a solid design.
When to use EventBridge schedule mutation anyway
If you only ever have a small number of sessions and you already have scheduling logic in place, your current plan is perfectly acceptable. I’d just add protections for:
- schedule fires while a run is still active
- operator sends a new command right as the shutdown fires
- ECS task is already gone
- Prefect run is stuck / long-running
Bottom line
Yes — using ECS/EventBridge to enforce worker session timeout is a good approach, and likely the right one.
If you want the most robust version, I’d lean toward:
- session-specific queues
- application-managed expiry state
- periodic reaper instead of constantly rewriting EventBridge schedules
If you want, I can help you design the exact control flow for:
1. session startup,
2. run submission to the right queue,
3. idle timeout extension, and
4. safe shutdown of the ECS worker task.Bring your towel and join one of the fastest growing data communities. Welcome to our second-generation open source orchestration platform, a completely rethought approach to dataflow automation.
Powered by