Practical guide to implementing exponential retries and circuit breakers in ERP–marketplace integrations
By Kevin Marcondes · Published on · 8 min read
This practical guide shows how to implement exponential retries and circuit breakers in ERP–marketplace integrations to keep systems resilient without creating duplicate orders or breaking API limits. Learn when to use each pattern, how to configure backoff with jitter, design idempotency, and set up monitoring. Useful for engineers and product owners alike.
When to use retries and circuit breakers in ERP–marketplace integrations
Marketplace integrations face timeouts, traffic spikes, billing-window load, and strict rate limits. Exponential retries and circuit breakers help stabilize flows, prevent cascading failures, and reduce rework.
Signals that you need these patterns: frequent 429/5xx responses, duplicated orders, mismatched inventory, latency spikes, and time-sensitive billing windows.
At high volume, combine these patterns with queues to decouple systems and smooth peaks. See decision criteria in SQS vs managed RabbitMQ.
- APIs with aggressive rate limits (429) around month-end.
- Intermittent timeouts and occasional DNS failures.
- POSTs that create duplicate orders when retried naively.
- Nightly processes that congest integrations and reports.
Exponential backoff with jitter: parameters and example
Fixed-interval retries amplify congestion. Use exponential backoff with jitter to space retries and avoid synchronized bursts. After each failure the wait grows exponentially and random jitter disperses client attempts.
Practical parameters: max attempts, growth factor, max backoff, jitter mode (full or equal), and a total operation deadline. Respect the marketplace's rate limits and abort with a clear error when the deadline is exceeded.
Useful references: Exponential Backoff and Jitter (AWS) and Google Cloud backoff guidance.
- Max attempts: caps retries to surface real failures.
- Base delay and factor: e.g., 200 ms base and factor 2.
- Max backoff: avoids excessively long waits.
- Jitter: 'full' reduces client synchronization.
- Total deadline: cancel if the business time window is exceeded.
function retryWithExponentialBackoff(op, isRetryable, maxAttempts=6,
base=0.2s, factor=2, maxDelay=10s,
jitter='full', deadline=30s) {
start = now()
attempt = 1
while (true) {
try {
return op()
} catch (err) {
if (attempt >= maxAttempts || !isRetryable(err)) throw err
elapsed = now() - start
if (elapsed > deadline) throw DeadlineExceeded
// Exponential delay
delay = min(maxDelay, base * (factor ** (attempt - 1)))
// Jitter
if (jitter == 'full') delay = random(0, delay)
if (jitter == 'equal') delay = (delay / 2) + random(0, delay / 2)
sleep(delay)
attempt++
}
}
}
Idempotency: preventing duplicate orders and updates
Retries are only safe when operations are idempotent. For POSTs, generate an idempotency key per order/entity and persist the associated result. Retries with the same key produce the same effect without creating duplicates.
Design the ERP to record processing by unique keys with metadata: final status, normalized payload, timestamp and optional TTL where applicable. Never retry POSTs without idempotency.
For asynchronous flows, use queues and deduplication controls. Understand eventual-delivery trade-offs and idempotency keys in ERP-to-ecommerce eventual delivery.
- Define key scope: per order, item, invoice, or business operation.
- Store a payload hash to detect subtle differences.
- Return the same 200/201 body when reprocessing the same key.
- Expire keys when the domain allows it.
- Include the key in logs and metrics for traceability.
Circuit breaker in practice: states, thresholds and fallbacks
A circuit breaker prevents overwhelming unstable services. States: closed (normal), open (block calls), and half-open (limited probes). It opens when a high failure rate is observed over a sliding window.
Set clear thresholds: error rate, consecutive timeouts, minimum sample volume, and open duration. Plan operation-specific fallbacks.
Common strategies: enqueue for retry, serve recent cache, or degrade functionality (e.g., postpone image updates, keep price/stock read-only).
- Sliding window with a minimum N calls for reliable stats.
- Composite errors: count 5xx, timeouts and 429; don't count validation 4xx.
- Half-open with a small number of probes spaced apart.
- Backoff before re-closing after a cooling period.
- Expose breaker state via metrics and health checks.
class CircuitBreaker {
state = 'CLOSED'
openedAt = null
failureCount = 0
successCount = 0
function call(fn, isRetryable) {
if (this.state == 'OPEN') {
if (now() - this.openedAt < cooldown) throw OpenCircuit
this.state = 'HALF_OPEN'
this.failureCount = 0; this.successCount = 0
}
try {
res = retryWithExponentialBackoff(fn, isRetryable)
if (this.state == 'HALF_OPEN') this.successCount++
if (this.successCount >= halfOpenSuccesses) this.close()
return res
} catch (e) {
this.failureCount++
if (this.shouldOpen()) this.open()
throw e
}
}
function shouldOpen() { return this.failureRate() > threshold && this.sampleSize() >= minSamples }
function open() { this.state = 'OPEN'; this.openedAt = now() }
function close() { this.state = 'CLOSED'; this.failureCount = 0; this.successCount = 0 }
}
Which errors deserve a retry? Decision table
Not every error should be retried. Classify errors first. Below is a practical map for ERP–marketplace integrations. Adjust according to each API's policy and your business contract.
| Error category | Examples | Retry? | Recommended strategy |
|---|---|---|---|
| Rate limit | 429, Retry-After header | Yes | Exponential backoff with jitter respecting Retry-After; record saturation metric |
| Transient provider failure | 5xx, timeout, DNS | Yes | Retries with deadline; wrap with a circuit breaker |
| Conflict / reprocessing | 409, resource already exists | Depends | Check idempotency; recover state and proceed |
| Validation error | 400, invalid schema | No | Fix payload; alert and send to dead-letter |
| Auth / authorization | 401 / 403 | No | Renew credentials; block flow until resolved |
| Business rule | 422, tax or business limit | No | Handle in ERP; queue for manual review |
| Unknown status after POST | Timeout after POST | Yes (if idempotent) | Retry with same idempotency key; if exhausted, query resource before resending |
Observability: metrics, structured logs and correlation
Without visibility, retries become a black box. Instrument each integration with metrics, structured logs and traces. Use correlation IDs to follow a request end-to-end from the ERP to the marketplace.
Structure logs as JSON including idempotency key, correlation id, attempt count, delays and circuit breaker state. See how to standardize in structured JSON logs and correlation and centralize with Fluentd + Elasticsearch.
- Metrics: attempts, success per operation, errors by category.
- Latency histograms and end‑to‑end timing.
- Gauges: breaker state and time open.
- Alerts on error rate and rate-limit saturation.
- Dashboards by marketplace, operation and account.
Testing, fault injection and safe rollout
Test retries and breakers under real failure conditions. Simulate 429, 5xx, timeouts, extreme jitter and controlled slowness. Validate the system doesn't duplicate orders and respects business deadlines.
Use progressive rollout: feature flags per marketplace/operation, comparative metrics and an easy rollback path. In legacy systems flags let you enable behavior by account or route. See practices in feature flags in PHP monoliths.
- Use the marketplace sandbox when available.
- Load tests focused on peaks and billing windows.
- Fault injection: simulate DNS failures and vary latency.
- Canaries by percentage of accounts/stores.
- Incident runbook with steps and contact list.
Frequently asked questions
How do I decide the number of attempts and the backoff interval?
Base it on the operation's SLO and the marketplace limits. Use few attempts with exponential backoff and jitter, a max backoff, and a total deadline. If the business window is short, prefer fewer attempts and a quick fallback to a queue or manual review.
Does a circuit breaker hide problems from the team?
No — it prevents collapse while signaling risk. Expose breaker state, failure rate and open reasons via metrics. Create clear alerts so the team can investigate root causes. The breaker is containment, not a permanent fix.
How to avoid duplicates when retrying POSTs?
Implement idempotency: generate a key per business operation, persist the result and respond consistently on retries. In uncertain status (timeout), retry with the same key or fetch the resource before resending. Do not retry POSTs without idempotency.
Should I always use a queue with retries and a circuit breaker?
Not always. Queues help decouple and control retries but add complexity. For critical or high-volume integrations queues are beneficial. Evaluate deadlines, volume and marketplace SLAs to decide coupling and reprocessing patterns.
When should I stop retrying and surface an error to the user or operator?
Stop when the business deadline is hit, max attempts are exhausted, or the circuit breaker opens. Record the reason, place the item in a review queue if applicable, and notify the operator with categorized error details, operation and marketplace.
Need help reviewing the resilience of your integrations?
If you want to apply retries with backoff, idempotency and circuit breakers without risking duplicates or outages, let’s discuss your scenario and next steps.
Talk about resilient integrationsSee also
- Practical guide: active‑passive failover between two VPS with Keepalived, HAProxy & lsyncd for synchronized uploads
- Guide: technical checklist to prepare a Debian VPS with Docker Compose for PHP apps in production
- Practical guide to VPS sizing and PHP‑FPM tuning for Magento/OpenMage stores with seasonal peaks: from sizing to load testing
- Practical guide: rebuild PostgreSQL indexes in production using CREATE INDEX CONCURRENTLY and partial indexes (safe step‑by‑step)