Retry Policies — Complete Design and Implementation Guide
In this tutorial, you will learn about Retry Policies. We cover key concepts, practical examples, and best practices to help you master this topic.
Retry policies define when and how to retry operations through configurable rules for maximum attempts, backoff Strategy, error classification, and total time budgets.
What You'll Learn
By the end of this tutorial, you will design retry policies for different operation types, compose policies, set retry budgets, and centralize retry configuration.
Why It Matters
Different operations need different retry behavior. A payment retry differs from a file read retry. Policies keep retry configuration organized and consistent.
Real-World Use
DodaTech defines retry policies per service in a configuration file. The database service uses one policy, the HTTP client uses another, and message processing uses a third.
Retry Policy Learning Path
flowchart LR
A[Messaging Retry] --> B[Retry Policies]
B --> C[Policy Design]
C --> D[Policy Composition]
B --> E{You Are Here}
style E fill:#f90,color:#fff
Defining a Retry Policy
A retry policy is a configuration object that specifies all aspects of retry behavior for a specific operation type.
class RetryPolicy {
constructor(config) {
this.name = config.name;
this.maxAttempts = config.maxAttempts || 3;
this.baseDelay = config.baseDelay || 200;
this.maxDelay = config.maxDelay || 30000;
this.strategy = config.strategy || "exponential";
this.timeout = config.timeout || 30000;
this.retryableErrors = config.retryableErrors || [];
this.jitter = config.jitter || "none";
}
getDelay(attempt) {
let delay;
switch (this.strategy) {
case "fixed":
delay = this.baseDelay;
break;
case "incremental":
delay = this.baseDelay + (attempt * this.baseDelay);
break;
case "exponential":
default:
delay = this.baseDelay * Math.pow(2, attempt);
break;
}
delay = Math.min(delay, this.maxDelay);
if (this.jitter === "full") {
delay = Math.random() * delay;
} else if (this.jitter === "equal") {
delay = (delay / 2) + (Math.random() * (delay / 2));
}
return Math.floor(delay);
}
isRetryable(error) {
if (this.retryableErrors.length === 0) return true;
return this.retryableErrors.some(pattern => {
if (typeof pattern === "string") {
return error.message.includes(pattern) || error.code === pattern;
}
if (pattern instanceof RegExp) {
return pattern.test(error.message);
}
return false;
});
}
}
// Usage
const httpPolicy = new RetryPolicy({
name: "http-api",
maxAttempts: 3,
baseDelay: 500,
maxDelay: 15000,
strategy: "exponential",
jitter: "full",
timeout: 10000,
retryableErrors: ["ETIMEDOUT", "ECONNREFUSED", /^5\d{2}$/]
});
Policy Registry
A centralized registry stores all retry policies and makes them accessible throughout the application.
class RetryPolicyRegistry {
constructor() {
this.policies = new Map();
}
register(policy) {
this.policies.set(policy.name, policy);
}
get(name) {
const policy = this.policies.get(name);
if (!policy) throw new Error(`Unknown policy: ${name}`);
return policy;
}
async execute(policyName, fn) {
const policy = this.get(policyName);
const start = Date.now();
for (let attempt = 0; attempt < policy.maxAttempts; attempt++) {
if (Date.now() - start > policy.timeout) {
throw new Error(`Policy ${policyName} timed out after ${policy.timeout}ms`);
}
try {
return await fn();
} catch (err) {
if (!policy.isRetryable(err)) throw err;
if (attempt === policy.maxAttempts - 1) throw err;
const delay = policy.getDelay(attempt);
await new Promise(r => setTimeout(r, delay));
}
}
}
}
// Setup
const registry = new RetryPolicyRegistry();
registry.register(new RetryPolicy({
name: "db-query",
maxAttempts: 3,
baseDelay: 100,
strategy: "exponential",
jitter: "equal",
retryableErrors: ["40001", "40P01"]
}));
registry.register(new RetryPolicy({
name: "http-api",
maxAttempts: 5,
baseDelay: 200,
maxDelay: 30000,
strategy: "exponential",
jitter: "full",
timeout: 60000,
retryableErrors: ["ECONNRESET", "ETIMEDOUT"]
}));
// Usage
const result = await registry.execute("http-api", () => fetchData());
Policy Composition
Complex operations may need different policies for different phases. Compose policies for multi-step operations.
class ComposedPolicy {
constructor(phases) {
this.phases = phases;
}
async execute(context) {
for (const phase of this.phases) {
context.currentPhase = phase.name;
await phase.policy.execute(phase.name, () => phase.action(context));
}
}
}
// Example: file upload with two phases
const uploadPolicy = new ComposedPolicy([
{
name: "upload-file",
policy: new RetryPolicy({
name: "upload",
maxAttempts: 3,
baseDelay: 1000,
maxDelay: 10000,
strategy: "exponential",
retryableErrors: ["ETIMEDOUT"]
}),
action: async (ctx) => { await uploadToStorage(ctx.file); }
},
{
name: "notify-success",
policy: new RetryPolicy({
name: "notify",
maxAttempts: 5,
baseDelay: 200,
strategy: "exponential",
jitter: "full",
retryableErrors: [/^5/]
}),
action: async (ctx) => { await sendWebhook(ctx.file.id); }
}
]);
Common Mistakes
One-size-fits-all policy -- Using the same retry configuration for all operations. Different operations need different policies.
Not documenting policy behavior -- Developers need to know what retry behavior to expect. Document each policy's configuration.
Policies that are too permissive -- Retrying forever or retrying non-retryable errors wastes resources.
Policies that are too strict -- Not retrying enough causes unnecessary failures. Balance retry count with user experience.
Hardcoding policy values -- Policies should be configurable without code changes. Use environment variables or config files.
Practice Questions
What should a retry policy configuration include? Max attempts, base delay, max delay, backoff strategy, jitter type, timeout, and retryable error patterns.
Why use a policy registry instead of inline configuration? Centralization ensures consistency, makes policies discoverable, and enables easy configuration changes.
How do you compose policies for multi-step operations? Define phases, each with its own policy. Execute phases sequentially. Different phases may have different retry needs.
Challenge: Design a retry policy that adapts based on time of day.
function getAdaptivePolicy() {
const hour = new Date().getHours();
if (hour >= 2 && hour <= 5) {
return new RetryPolicy({ maxAttempts: 5, baseDelay: 1000 });
}
return new RetryPolicy({ maxAttempts: 3, baseDelay: 200 });
}
FAQ
Mini Project
Build a complete retry policy system with a registry, multiple policies, error classification, and execution engine.
const policies = {
"db-read": {
maxAttempts: 3,
baseDelay: 50,
maxDelay: 1000,
strategy: "exponential",
retryable: ["40001", "40P01", "08006"]
},
"db-write": {
maxAttempts: 5,
baseDelay: 100,
maxDelay: 5000,
strategy: "exponential",
jitter: "equal",
retryable: ["40001", "40P01"]
},
"http-get": {
maxAttempts: 3,
baseDelay: 200,
maxDelay: 10000,
strategy: "exponential",
jitter: "full",
totalTimeout: 30000,
retryable: ["ECONNRESET", "ETIMEDOUT", "429", "5xx"]
},
"http-post": {
maxAttempts: 2,
baseDelay: 500,
maxDelay: 5000,
strategy: "fixed",
retryable: ["ECONNRESET", "ETIMEDOUT"]
},
"queue-processing": {
maxAttempts: 5,
baseDelay: 1000,
maxDelay: 60000,
strategy: "exponential",
jitter: "full",
totalTimeout: 300000,
retryable: ["timeout", "connection"]
}
};
class PolicyEngine {
constructor(policies) {
this.policies = policies;
}
async execute(policyName, fn) {
const policy = this.policies[policyName];
const start = Date.now();
for (let attempt = 0; attempt < policy.maxAttempts; attempt++) {
try {
return await fn();
} catch (err) {
const elapsed = Date.now() - start;
if (elapsed > (policy.totalTimeout || 60000)) {
throw new Error(`Policy ${policyName} total timeout exceeded`);
}
if (attempt === policy.maxAttempts - 1) throw err;
if (!this.isRetryable(err, policy)) throw err;
const delay = this.getDelay(policy, attempt);
await new Promise(r => setTimeout(r, delay));
}
}
}
isRetryable(err, policy) {
return policy.retryable.some(p => err.code === p || err.message.includes(p));
}
getDelay(policy, attempt) {
let delay = policy.baseDelay * Math.pow(2, attempt);
delay = Math.min(delay, policy.maxDelay);
if (policy.jitter === "full") delay = Math.random() * delay;
if (policy.jitter === "equal") delay = (delay / 2) + Math.random() * (delay / 2);
return Math.floor(delay);
}
}
What's Next
Now that you understand retry policies, explore testing retry logic. Then learn about monitoring retry performance.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro