Software Architecture / Interactions: distribution, failure, and operation / Lesson 8
Design asynchronous work with explicit completion and pressure
Use queues to decouple timing while accounting for durability, duplicate delivery, backlog, and user-visible outcomes.
On this page
Lesson goals
- Distinguish accepted work from completed work and define acknowledgement boundaries.
- Reason about backlog growth and bounded admission without a live broker.
A queue changes the conversation with the user
A synchronous call returns after a result is available within the request deadline. An asynchronous command can return before business completion, so the system needs a durable work identity and a way to report progress or final failure. Accepted must not be labeled confirmed unless the business definition truly permits it. For a workshop report export, a job record and later download may be suitable. For the last seat, delaying the allocation decision may require a pending state and a different user promise.
Treat acknowledgement as a designed boundary
A producer needs to know when the broker has durably accepted a message according to the selected broker configuration. A consumer must decide whether acknowledgement follows successful processing, a durable handoff, or another explicit event. A crash before acknowledgement can cause redelivery; a crash after an early acknowledgement can lose work. These windows differ by product and transaction model. Assume duplicate delivery is possible unless a precisely scoped stronger guarantee is documented and verified. Business effects still need deduplication or an idempotent state transition.
{"message_id":"m-51","type":"ReportRequested","schema_version":1,"job_id":"job-9","requested_at":"2026-10-28T10:00:00Z"}
The envelope distinguishes delivery identity from the business job identity. Timestamp and schema version support interpretation, but they provide neither ordering nor durability by themselves.
A buffer cannot fix a permanent capacity deficit
When arrival rate exceeds sustainable completion rate, backlog grows even if the API responds quickly. Queue length alone is incomplete: oldest-message age and expected completion time can better express user impact. The offline arithmetic below uses fictional constant rates to expose this relationship. Real systems have bursts, retries, variable service times, and capacity limits, so this model is a planning aid rather than a forecast. Increasing worker concurrency may merely move contention to a database or external provider that cannot absorb more parallel requests.
arrival_per_minute = 12
completion_per_minute = 9
minutes = 5
backlog = max(0, (arrival_per_minute - completion_per_minute) * minutes)
print(f"backlog={backlog}")
print(f"net_growth_per_minute={arrival_per_minute - completion_per_minute}")
backlog=15
net_growth_per_minute=3
The fictional empty queue accumulates fifteen items over five minutes, growing by three per minute. No broker, worker, load generator, or external service is started.
Bound admission, retries, and poison-message handling
Backpressure means reducing or refusing incoming work when downstream capacity cannot safely keep up. Define queue limits, expiration, tenant fairness, and an honest rejection response. Bounded retries need a terminal outcome and an owned investigation path for persistent failures. A dead-letter queue is storage for unresolved messages, not a repair policy; replaying everything blindly can repeat harmful effects or overload the same dependency. Record the original identity and failure reason, then require a compatible fix and controlled replay conditions.
Specify ordering only where the business needs it
Parallel consumers can complete messages out of arrival order. If a workshop update must follow its creation, consider per-workshop sequencing, version checks, or state transitions that reject stale events. Global ordering is often more expensive than the actual requirement. Explain how expired jobs, cancelled work, and user polling interact with final states. Verify duplicate, delayed, and out-of-order examples in an isolated implementation before claiming reliability. This lesson models messages and rates only; it does not establish guarantees for any particular broker or deployment.
Common errors and fixes
An accepted export is shown as completed.
- Why it happens
- Transport acceptance was confused with business completion.
- How to fix it
- Expose a durable job state and final result.
- Prevent it next time
- Define user-facing states before adding the queue.
More workers make the external provider fail faster.
- Why it happens
- Concurrency ignored the downstream capacity limit.
- How to fix it
- Bound concurrency and admission against measured dependency capacity.
- Prevent it next time
- Observe age, completion rate, and dependency saturation together.
A dead-letter queue grows without ownership.
- Why it happens
- Failure storage was mistaken for resolution.
- How to fix it
- Assign diagnosis, retention, correction, and replay responsibilities.
- Prevent it next time
- Test a persistent poison message and its terminal path.
Check your understanding
1. At 20 jobs per minute arriving and 15 completing, what happens after ten minutes from empty?
Show worked solution
Under the stated constant-rate model, fifty jobs accumulate. A quick enqueue response hides this delay rather than removing it. Investigate workload reduction, increased sustainable downstream capacity, or explicit admission limits. The arithmetic does not determine a safe worker count because processing time and dependency contention are unspecified.
2. A consumer crashes after sending mail but before acknowledgement. What design issue follows?
Show worked solution
Redelivery can send the same notification again. Track a stable delivery identity and use an idempotent provider operation where available, or define a business-tolerated duplicate policy. An in-memory flag is insufficient after a crash. State exactly which effects can be deduplicated and which remain uncertain.
Key takeaways
- Asynchrony moves completion and failure into an explicit workflow.
- Queues need pressure limits, ownership, and duplicate handling.