Determine How Far a Request Was Processed
Analyze ambiguous outcomes caused by process termination and response loss, then establish a minimum safety boundary with transactions, idempotency, outbox, and reconciliation.
Key takeaways
- The hardest distributed-systems failure is often an ambiguous outcome where nobody can prove whether a request succeeded
- A process can stop between database commit, an external API call, message publication, and response delivery
- A failed response is not proof of failed processing, so the same command must be safe to inspect or execute again
- The minimum safety boundary combines business invariants, local transactions, idempotency, Outbox, bounded retries, and reconciliation
- Replicas and automatic restart improve availability for new requests but do not recover the consistency of an interrupted request
- Reduce the problem with one store and simple asynchronous boundaries before selecting a complex pattern such as Saga
One request has more than success and failure
-
The outcome observed by a client can differ from the outcome durably stored by the server
- Termination before database commit normally rolls back a local transaction
- Termination after commit but before response leaves the change complete while the client sees a timeout or
5xx - Retrying as a new request can duplicate a payment, order, or points credit
-
A network timeout means unknown rather than failed
- The remote system might not have received the request
- It might have succeeded and lost only the response
- It might still be processing the request
- One generic exception handler cannot produce a safe retry policy for all three cases
| Termination point | Durable state | Client observation | Safe next action |
|---|---|---|---|
| Before database commit | No change | Disconnect or 5xx | Retry with the same key |
| After commit, before response | Change completed | Timeout or 5xx | Look up or retry with the same key |
| Before external API call | Internal preparation only | Pending or failed | Redeliver the stored command |
| After external success, before local result | External effect may exist | unknown | Query by external reference and reconcile |
| After message handling, before acknowledgement | Consumer change completed | Message redelivered | Deduplicate in an idempotent consumer |
Echo servers and transaction services fail differently
-
A stateless echo server usually loses only the request that was running when its process stops
- It has no durable partial commit to repair
- Another replica can accept a new request but cannot continue the disconnected one
- Resource headroom, probes, replicas, and timeout behavior are its main deployment checks
-
An order or payment service can leave side effects behind a failed response
- Payment authorization can complete before order persistence fails
- Inventory reservation can complete before event publication fails
- A refund can complete while its operation remains stuck in
compensating
-
Different infrastructure events expose a similar application failure
- Kubernetes OOM kills, Pod replacement, and node failure can terminate the process
- OpenStack VM reboot, compute-host failure, and network loss can terminate the connection
- Deployment and operations automation can create the same boundary during otherwise healthy traffic
Fix business invariants in the durable store first
-
Define each fact that may happen once as both a requirement and a database constraint
- One
order_idhas at most one successful payment - Total refunds for a
payment_idcannot exceed the captured amount - One
reservation_idapplies an inventory reservation only once - Reusing an idempotency key with a different request body returns
409 Conflict
- One
-
An application-side existence check cannot prevent duplicates by itself
- Concurrent requests can both observe that a record is absent
- A database
PRIMARY KEY,UNIQUE, or conditionalUPDATEmust be the final atomic guard - A constraint conflict should lead to the existing result rather than an internal server error
-
Prefer one local transaction whenever one database can enforce the invariant
- Complete validation and authorization before side effects
- Store the order and idempotent result in the same commit boundary
- Let pre-commit termination roll back and post-commit retry discover the stored result
CREATE TABLE operations (
scope text NOT NULL,
idempotency_key text NOT NULL,
request_hash text NOT NULL,
status text NOT NULL,
response_code integer,
response_body jsonb,
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (scope, idempotency_key)
);Add durable boundaries when work leaves the database
-
A dual write that updates a database and then publishes a message becomes inconsistent when the second call fails
- Store the business change and outbox event in one database transaction
- Let a separate publisher deliver unpublished events to the broker
- Allow duplicate delivery because the publisher can die immediately after a successful publish
-
Consumers must be idempotent under at-least-once delivery
- Track a stable
message_idor business key - Acknowledge only after the local transaction commits
- Move exhausted work to a dead-letter queue with an approved replay procedure
- Track a stable
- An external API timeout needs authoritative status lookup and reconciliation
- Connect the provider's idempotency key to the internal
operation_id - Let a reconciler inspect operations that remain
pendingbeyond their deadline - Use
review_requiredand an operator decision when automatic classification is unsafe
- Connect the provider's idempotency key to the internal
Design retry and termination as one contract
-
Every network call needs a timeout, retryable-error classification, attempt limit, and total time budget
- Connection refusal, selected
5xxresponses, and rate limits can use backoff with jitter - Validation errors, authorization failures, and insufficient inventory are permanent failures
- A gateway must not retry non-idempotent writes without a stable key
- Connection refusal, selected
-
Graceful shutdown improves availability but is not a consistency prerequisite
- Stop accepting new HTTP requests and queue leases on
SIGTERM - Finish current work within the grace period or return it to a safe checkpoint
- OOM and host failure can skip hooks, so every command boundary must remain safe under termination
- Stop accepting new HTTP requests and queue leases on
-
Represent long work as an operation resource that outlives an HTTP connection
- A start request can return
202 Acceptedwith anoperation_id - A status API distinguishes
pending,succeeded,failed,unknown, andreview_required - The client inspects the same operation instead of creating new work
- A start request can return
Failure-point tests become deployment gates
-
Inject termination immediately before and after side-effect boundaries rather than at arbitrary source lines
- Kill the process immediately before and after database commit
- Lose the response immediately after an external API succeeds
- Stop publishers and consumers around broker publish and acknowledgement
- Deliver identical HTTP requests and messages concurrently
-
Business measurements must survive infrastructure recovery
- Duplicate orders, duplicate payments, and negative inventory remain at zero
- Expired
pendingoperations are detected and reconciled - Outbox oldest age, retry count, DLQ count, and unknown outcome count feed alerts
- Logs, traces, and events share one
operation_idand causation ID
Conclusion and related documents
- The first deployment gate is evidence for invariants, local transactions, idempotency, Outbox, result lookup, and reconciliation rather than a workflow engine
- Saga and explicit workflow state become candidates only for long-running multi-service work that cannot fit within this boundary
- Saga, state machines, and orchestrators explains the maintenance cost outside the happy path
- Reconciliation in OpenStack and Kubernetes compares platform convergence with application design
References
Collaboration
Connect respectful conduct, questions, bugs, and feature proposals to the right channel and verifiable records.
Account for the Hidden Cost of Saga and Orchestration
Analyze the transition, compensation, versioning, and operational costs that Saga, state machines, choreography, and orchestrators add outside the happy path.