Preview
Preview Feature — This feature is currently in preview and under active development. APIs and functionality may change. We recommend testing thoroughly before using in production.
Retry Strategies
Configure how Ductape handles job failures with automatic retries, backoff strategies, and error handling.
Basic Retry Configuration
Every dispatch method accepts a retries parameter that specifies how many times a job should be retried if it fails:
- TypeScript
- Java
- Go
- .NET
const job = await ductape.api.dispatch({
app: 'payment-service',
event: 'process_payment',
input: { body: { orderId: 'order_123' } },
retries: 3 // Retry up to 3 times on failure
});
Map<String, Object> job = ductape.api().dispatch(Map<String, Object>.of(
"app", "payment-service",
"event", "process_payment",
input: Map.of( body: Map.of( "orderId", "order_123" ) ),
"retries", 3 // Retry up to 3 times on failure
));
import "context"
job := client.Api.Dispatch(ctx, map[string]any{
"app": "payment-service",
"event": "process_payment",
input: { body: { "orderId": "order_123" } },
"retries": 3 // Retry up to 3 times on failure
});
var job = await await ductape.Api.DispatchAsync(new Dictionary<string, object?>
{
["app"] = "payment-service",
["event"] = "process_payment",
input: { body: { ["orderId"] = "order_123" } },
["retries"] = 3 // Retry up to 3 times on failure
});
Retry Behavior
When a job fails, Ductape automatically:
- Captures the error details
- Increments the retry counter
- Schedules a retry after the backoff delay
- Repeats until success or max retries reached
Default Behavior
| Setting | Default Value |
|---|---|
| Max retries | 0 (no retries) |
| Initial delay | 1 second |
| Backoff strategy | Exponential |
| Max delay | 5 minutes |
Retry Options
Configure advanced retry behavior with the retryConfig option:
- TypeScript
- Java
- Go
- .NET
const job = await ductape.api.dispatch({
app: 'email-service',
event: 'send_email',
input: { body: { to: 'user@example.com' } },
retries: 5,
retryConfig: {
initialDelay: 2000, // Start with 2 second delay
maxDelay: 300000, // Cap at 5 minutes
backoffMultiplier: 2, // Double delay each retry
retryableErrors: ['TIMEOUT', 'RATE_LIMITED']
}
});
Map<String, Object> job = ductape.api().dispatch(Map<String, Object>.of(
"app", "email-service",
"event", "send_email",
input: Map.of( body: Map.of( "to", "user@example.com" ) ),
"retries", 5,
retryConfig: Map.of(
"initialDelay", 2000, // Start with 2 second delay
"maxDelay", 300000, // Cap at 5 minutes
"backoffMultiplier", 2, // Double delay each retry
retryableErrors: ['TIMEOUT', 'RATE_LIMITED']
)
));
import "context"
job := client.Api.Dispatch(ctx, map[string]any{
"app": "email-service",
"event": "send_email",
input: { body: { "to": "user@example.com" } },
"retries": 5,
retryConfig: {
"initialDelay": 2000, // Start with 2 second delay
"maxDelay": 300000, // Cap at 5 minutes
"backoffMultiplier": 2, // Double delay each retry
retryableErrors: ['TIMEOUT', 'RATE_LIMITED']
}
});
var job = await await ductape.Api.DispatchAsync(new Dictionary<string, object?>
{
["app"] = "email-service",
["event"] = "send_email",
input: { body: { ["to"] = "user@example.com" } },
["retries"] = 5,
retryConfig: {
["initialDelay"] = 2000, // Start with 2 second delay
["maxDelay"] = 300000, // Cap at 5 minutes
["backoffMultiplier"] = 2, // Double delay each retry
retryableErrors: ['TIMEOUT', 'RATE_LIMITED']
}
});
Retry Configuration Options
interface IRetryConfig {
initialDelay?: number; // Initial delay in ms (default: 1000)
maxDelay?: number; // Maximum delay in ms (default: 300000)
backoffMultiplier?: number; // Multiplier for exponential backoff (default: 2)
retryableErrors?: string[]; // Only retry on these error types
nonRetryableErrors?: string[]; // Never retry on these error types
}
Backoff Strategies
Exponential Backoff (Default)
Delays increase exponentially with each retry:
Retry 1: 1s → Retry 2: 2s → Retry 3: 4s → Retry 4: 8s → Retry 5: 16s
- TypeScript
- Java
- Go
- .NET
const job = await ductape.api.dispatch({
app: 'api-client',
event: 'fetch_data',
input: { body: {} },
retries: 5,
retryConfig: {
initialDelay: 1000,
backoffMultiplier: 2
}
});
Map<String, Object> job = ductape.api().dispatch(Map<String, Object>.of(
"app", "api-client",
"event", "fetch_data",
input: Map.of( body: Map.of() ),
"retries", 5,
retryConfig: Map.of(
"initialDelay", 1000,
"backoffMultiplier", 2
)
));
import "context"
job := client.Api.Dispatch(ctx, map[string]any{
"app": "api-client",
"event": "fetch_data",
input: { body: {} },
"retries": 5,
retryConfig: {
"initialDelay": 1000,
"backoffMultiplier": 2
}
});
var job = await await ductape.Api.DispatchAsync(new Dictionary<string, object?>
{
["app"] = "api-client",
["event"] = "fetch_data",
input: { body: {} },
["retries"] = 5,
retryConfig: {
["initialDelay"] = 1000,
["backoffMultiplier"] = 2
}
});
Linear Backoff
Use a fixed delay between retries by setting backoffMultiplier: 1:
- TypeScript
- Java
- Go
- .NET
const job = await ductape.api.dispatch({
app: 'notification-service',
event: 'send_push',
input: { body: {} },
retries: 3,
retryConfig: {
initialDelay: 5000,
backoffMultiplier: 1 // Fixed 5 second delay between retries
}
});
Map<String, Object> job = ductape.api().dispatch(Map<String, Object>.of(
"app", "notification-service",
"event", "send_push",
input: Map.of( body: Map.of() ),
"retries", 3,
retryConfig: Map.of(
"initialDelay", 5000,
"backoffMultiplier", 1 // Fixed 5 second delay between retries
)
));
import "context"
job := client.Api.Dispatch(ctx, map[string]any{
"app": "notification-service",
"event": "send_push",
input: { body: {} },
"retries": 3,
retryConfig: {
"initialDelay": 5000,
"backoffMultiplier": 1 // Fixed 5 second delay between retries
}
});
var job = await await ductape.Api.DispatchAsync(new Dictionary<string, object?>
{
["app"] = "notification-service",
["event"] = "send_push",
input: { body: {} },
["retries"] = 3,
retryConfig: {
["initialDelay"] = 5000,
["backoffMultiplier"] = 1 // Fixed 5 second delay between retries
}
});
Custom Backoff with Jitter
Add randomness to prevent thundering herd:
- TypeScript
- Java
- Go
- .NET
const job = await ductape.api.dispatch({
app: 'sync-service',
event: 'sync_data',
input: { body: {} },
retries: 5,
retryConfig: {
initialDelay: 1000,
backoffMultiplier: 2,
jitter: true, // Add random jitter (0-50% of delay)
jitterPercent: 0.3 // Custom jitter percentage (30%)
}
});
Map<String, Object> job = ductape.api().dispatch(Map<String, Object>.of(
"app", "sync-service",
"event", "sync_data",
input: Map.of( body: Map.of() ),
"retries", 5,
retryConfig: Map.of(
"initialDelay", 1000,
"backoffMultiplier", 2,
"jitter", true, // Add random jitter (0-50% of delay)
"jitterPercent", 0.3 // Custom jitter percentage (30%)
)
));
import "context"
job := client.Api.Dispatch(ctx, map[string]any{
"app": "sync-service",
"event": "sync_data",
input: { body: {} },
"retries": 5,
retryConfig: {
"initialDelay": 1000,
"backoffMultiplier": 2,
"jitter": true, // Add random jitter (0-50% of delay)
"jitterPercent": 0.3 // Custom jitter percentage (30%)
}
});
var job = await await ductape.Api.DispatchAsync(new Dictionary<string, object?>
{
["app"] = "sync-service",
["event"] = "sync_data",
input: { body: {} },
["retries"] = 5,
retryConfig: {
["initialDelay"] = 1000,
["backoffMultiplier"] = 2,
["jitter"] = true, // Add random jitter (0-50% of delay)
["jitterPercent"] = 0.3 // Custom jitter percentage (30%)
}
});
Error-Based Retry Logic
Retry Only Specific Errors
- TypeScript
- Java
- Go
- .NET
const job = await ductape.api.dispatch({
app: 'payment-service',
event: 'charge_card',
input: { body: { amount: 99.99 } },
retries: 3,
retryConfig: {
retryableErrors: [
'TIMEOUT',
'RATE_LIMITED',
'SERVICE_UNAVAILABLE',
'CONNECTION_ERROR'
]
}
});
Map<String, Object> job = ductape.api().dispatch(Map<String, Object>.of(
"app", "payment-service",
"event", "charge_card",
input: Map.of( body: Map.of( "amount", 99.99 ) ),
"retries", 3,
retryConfig: Map.of(
retryableErrors: [
'TIMEOUT',
'RATE_LIMITED',
'SERVICE_UNAVAILABLE',
'CONNECTION_ERROR'
]
)
));
import "context"
job := client.Api.Dispatch(ctx, map[string]any{
"app": "payment-service",
"event": "charge_card",
input: { body: { "amount": 99.99 } },
"retries": 3,
retryConfig: {
retryableErrors: [
'TIMEOUT',
'RATE_LIMITED',
'SERVICE_UNAVAILABLE',
'CONNECTION_ERROR'
]
}
});
var job = await await ductape.Api.DispatchAsync(new Dictionary<string, object?>
{
["app"] = "payment-service",
["event"] = "charge_card",
input: { body: { ["amount"] = 99.99 } },
["retries"] = 3,
retryConfig: {
retryableErrors: [
'TIMEOUT',
'RATE_LIMITED',
'SERVICE_UNAVAILABLE',
'CONNECTION_ERROR'
]
}
});
Never Retry Certain Errors
- TypeScript
- Java
- Go
- .NET
const job = await ductape.api.dispatch({
app: 'payment-service',
event: 'charge_card',
input: { body: { amount: 99.99 } },
retries: 3,
retryConfig: {
nonRetryableErrors: [
'INVALID_CARD',
'INSUFFICIENT_FUNDS',
'CARD_DECLINED',
'FRAUD_DETECTED',
'VALIDATION_ERROR'
]
}
});
Map<String, Object> job = ductape.api().dispatch(Map<String, Object>.of(
"app", "payment-service",
"event", "charge_card",
input: Map.of( body: Map.of( "amount", 99.99 ) ),
"retries", 3,
retryConfig: Map.of(
nonRetryableErrors: [
'INVALID_CARD',
'INSUFFICIENT_FUNDS',
'CARD_DECLINED',
'FRAUD_DETECTED',
'VALIDATION_ERROR'
]
)
));
import "context"
job := client.Api.Dispatch(ctx, map[string]any{
"app": "payment-service",
"event": "charge_card",
input: { body: { "amount": 99.99 } },
"retries": 3,
retryConfig: {
nonRetryableErrors: [
'INVALID_CARD',
'INSUFFICIENT_FUNDS',
'CARD_DECLINED',
'FRAUD_DETECTED',
'VALIDATION_ERROR'
]
}
});
var job = await await ductape.Api.DispatchAsync(new Dictionary<string, object?>
{
["app"] = "payment-service",
["event"] = "charge_card",
input: { body: { ["amount"] = 99.99 } },
["retries"] = 3,
retryConfig: {
nonRetryableErrors: [
'INVALID_CARD',
'INSUFFICIENT_FUNDS',
'CARD_DECLINED',
'FRAUD_DETECTED',
'VALIDATION_ERROR'
]
}
});
Common Error Types
| Error Type | Description | Should Retry? |
|---|---|---|
TIMEOUT | Request timed out | Yes |
RATE_LIMITED | Rate limit exceeded | Yes (with delay) |
SERVICE_UNAVAILABLE | Service temporarily down | Yes |
CONNECTION_ERROR | Network connectivity issue | Yes |
VALIDATION_ERROR | Invalid input data | No |
NOT_FOUND | Resource doesn't exist | No |
UNAUTHORIZED | Authentication failed | No |
FORBIDDEN | Permission denied | No |
Retry Patterns by Use Case
External API Calls
- TypeScript
- Java
- Go
- .NET
// API calls often fail due to rate limits or temporary issues
const job = await ductape.api.dispatch({
app: 'stripe-service',
event: 'create_subscription',
input: {
body: { customer_id: 'cus_123', plan: 'premium' }
},
retries: 5,
retryConfig: {
initialDelay: 2000,
maxDelay: 60000,
backoffMultiplier: 2,
retryableErrors: ['TIMEOUT', 'RATE_LIMITED', 'SERVICE_UNAVAILABLE']
}
});
// API calls often fail due to rate limits or temporary issues
Map<String, Object> job = ductape.api().dispatch(Map<String, Object>.of(
"app", "stripe-service",
"event", "create_subscription",
input: Map.of(
body: Map.of( "customer_id", "cus_123", "plan", "premium" )
),
"retries", 5,
retryConfig: Map.of(
"initialDelay", 2000,
"maxDelay", 60000,
"backoffMultiplier", 2,
retryableErrors: ['TIMEOUT', 'RATE_LIMITED', 'SERVICE_UNAVAILABLE']
)
));
import "context"
// API calls often fail due to rate limits or temporary issues
job := client.Api.Dispatch(ctx, map[string]any{
"app": "stripe-service",
"event": "create_subscription",
input: {
body: { "customer_id": "cus_123", "plan": "premium" }
},
"retries": 5,
retryConfig: {
"initialDelay": 2000,
"maxDelay": 60000,
"backoffMultiplier": 2,
retryableErrors: ['TIMEOUT', 'RATE_LIMITED', 'SERVICE_UNAVAILABLE']
}
});
// API calls often fail due to rate limits or temporary issues
var job = await await ductape.Api.DispatchAsync(new Dictionary<string, object?>
{
["app"] = "stripe-service",
["event"] = "create_subscription",
input: {
body: { ["customer_id"] = "cus_123", ["plan"] = "premium" }
},
["retries"] = 5,
retryConfig: {
["initialDelay"] = 2000,
["maxDelay"] = 60000,
["backoffMultiplier"] = 2,
retryableErrors: ['TIMEOUT', 'RATE_LIMITED', 'SERVICE_UNAVAILABLE']
}
});
Email Delivery
- TypeScript
- Java
- Go
- .NET
// Email services may have temporary issues
const job = await ductape.notifications.dispatch({
notification: 'user-emails',
event: 'send_welcome',
input: {
email: { recipients: ['user@example.com'] }
},
retries: 3,
retryConfig: {
initialDelay: 5000,
backoffMultiplier: 2,
nonRetryableErrors: ['INVALID_EMAIL', 'UNSUBSCRIBED']
}
});
// Email services may have temporary issues
Map<String, Object> job = ductape.notifications.dispatch(Map.of(
"notification", "user-emails",
"event", "send_welcome",
input: Map.of(
email: Map.of( recipients: ['user@example.com'] )
),
"retries", 3,
retryConfig: Map.of(
"initialDelay", 5000,
"backoffMultiplier", 2,
nonRetryableErrors: ['INVALID_EMAIL', 'UNSUBSCRIBED']
)
));
// Email services may have temporary issues
job := client.notifications.dispatch({
"notification": "user-emails",
"event": "send_welcome",
input: {
email: { recipients: ['user@example.com'] }
},
"retries": 3,
retryConfig: {
"initialDelay": 5000,
"backoffMultiplier": 2,
nonRetryableErrors: ['INVALID_EMAIL', 'UNSUBSCRIBED']
}
});
// Email services may have temporary issues
var job = await ductape.notifications.dispatch({
["notification"] = "user-emails",
["event"] = "send_welcome",
input: {
email: { recipients: ['user@example.com'] }
},
["retries"] = 3,
retryConfig: {
["initialDelay"] = 5000,
["backoffMultiplier"] = 2,
nonRetryableErrors: ['INVALID_EMAIL', 'UNSUBSCRIBED']
}
});
Database Operations
- TypeScript
- Java
- Go
- .NET
// Database deadlocks and timeouts can be retried
const job = await ductape.databases.action.dispatch({
database: 'main-db',
event: 'process_batch',
input: { query: {}, data: { batch_id: 'batch_123' } },
retries: 3,
retryConfig: {
initialDelay: 1000,
retryableErrors: ['DEADLOCK', 'TIMEOUT', 'CONNECTION_LOST']
}
});
// Database deadlocks and timeouts can be retried
Map<String, Object> job = ductape.databases.action.dispatch(Map.of(
"database", "main-db",
"event", "process_batch",
input: Map.of( query: Map.of(), data: Map.of( "batch_id", "batch_123" ) ),
"retries", 3,
retryConfig: Map.of(
"initialDelay", 1000,
retryableErrors: ['DEADLOCK', 'TIMEOUT', 'CONNECTION_LOST']
)
));
// Database deadlocks and timeouts can be retried
job := client.databases.action.dispatch({
"database": "main-db",
"event": "process_batch",
input: { query: {}, data: { "batch_id": "batch_123" } },
"retries": 3,
retryConfig: {
"initialDelay": 1000,
retryableErrors: ['DEADLOCK', 'TIMEOUT', 'CONNECTION_LOST']
}
});
// Database deadlocks and timeouts can be retried
var job = await ductape.databases.action.dispatch({
["database"] = "main-db",
["event"] = "process_batch",
input: { query: {}, data: { ["batch_id"] = "batch_123" } },
["retries"] = 3,
retryConfig: {
["initialDelay"] = 1000,
retryableErrors: ['DEADLOCK', 'TIMEOUT', 'CONNECTION_LOST']
}
});
Message Publishing
- TypeScript
- Java
- Go
- .NET
// Message brokers may be temporarily unavailable
const job = await ductape.events.dispatch({
broker: 'kafka-main',
event: 'order_created',
input: {
message: { orderId: 'order_123', status: 'created' }
},
retries: 5,
retryConfig: {
initialDelay: 500,
maxDelay: 30000,
backoffMultiplier: 2
}
});
// Message brokers may be temporarily unavailable
Map<String, Object> job = ductape.events.dispatch(Map.of(
"broker", "kafka-main",
"event", "order_created",
input: Map.of(
message: Map.of( "orderId", "order_123", "status", "created" )
),
"retries", 5,
retryConfig: Map.of(
"initialDelay", 500,
"maxDelay", 30000,
"backoffMultiplier", 2
)
));
// Message brokers may be temporarily unavailable
job := client.events.dispatch({
"broker": "kafka-main",
"event": "order_created",
input: {
message: { "orderId": "order_123", "status": "created" }
},
"retries": 5,
retryConfig: {
"initialDelay": 500,
"maxDelay": 30000,
"backoffMultiplier": 2
}
});
// Message brokers may be temporarily unavailable
var job = await ductape.events.dispatch({
["broker"] = "kafka-main",
["event"] = "order_created",
input: {
message: { ["orderId"] = "order_123", ["status"] = "created" }
},
["retries"] = 5,
retryConfig: {
["initialDelay"] = 500,
["maxDelay"] = 30000,
["backoffMultiplier"] = 2
}
});
Handling Final Failures
When a job exhausts all retries, you can handle the failure:
Using Webhooks
- TypeScript
- Java
- Go
- .NET
// Configure webhook for failed jobs
await ductape.jobs.setWebhook({
url: 'https://api.example.com/webhooks/job-failed',
events: ['job.failed'],
secret: 'your-secret'
});
// Configure webhook for failed jobs
ductape.jobs.setWebhook(Map.of(
"url", "https://api.example.com/webhooks/job-failed",
events: ['job.failed'],
"secret", "your-secret"
));
// Configure webhook for failed jobs
client.jobs.setWebhook({
"url": "https://api.example.com/webhooks/job-failed",
events: ['job.failed'],
"secret": "your-secret"
});
// Configure webhook for failed jobs
await ductape.jobs.setWebhook({
["url"] = "https://api.example.com/webhooks/job-failed",
events: ['job.failed'],
["secret"] = "your-secret"
});
Polling for Failed Jobs
- TypeScript
- Java
- Go
- .NET
// Check for failed jobs and handle them
const failedJobs = await ductape.jobs.list({
status: 'failed',
from: Date.now() - 3600000 // Last hour
});
for (const job of failedJobs.jobs) {
console.log(`Job ${job.id} failed after ${job.retry_count} attempts`);
console.log(`Error: ${job.last_error}`);
// Decide whether to retry manually or escalate
if (job.retry_count < 10 && isRetryable(job.last_error)) {
await ductape.jobs.retry(job.id, { delay: 60000 });
} else {
await escalateToSupport(job);
}
}
// Check for failed jobs and handle them
Map<String, Object> failedJobs = ductape.jobs.list(Map.of(
"status", "failed",
from: Date.now() - 3600000 // Last hour
));
for (Map<String, Object> job of failedJobs.jobs) Map.of(
System.out.println(`Job $Map.of(job.id) failed after $Map.of(job.retry_count) attempts`);
System.out.println(`Error: $Map.of(job.last_error)`);
// Decide whether to retry manually or escalate
if (job.retry_count < 10 && isRetryable(job.last_error)) Map.of(
ductape.jobs.retry(job.id, Map.of( "delay", 60000 ));
) else Map.of(
escalateToSupport(job);
)
)
// Check for failed jobs and handle them
failedJobs := client.jobs.list({
"status": "failed",
from: Date.now() - 3600000 // Last hour
});
for (const job of failedJobs.jobs) {
fmt.Println(`Job ${job.id} failed after ${job.retry_count} attempts`);
fmt.Println(`Error: ${job.last_error}`);
// Decide whether to retry manually or escalate
if (job.retry_count < 10 && isRetryable(job.last_error)) {
client.jobs.retry(job.id, { "delay": 60000 });
} else {
escalateToSupport(job);
}
}
// Check for failed jobs and handle them
var failedJobs = await ductape.jobs.list({
["status"] = "failed",
from: Date.now() - 3600000 // Last hour
});
for (var job of failedJobs.jobs) {
Console.WriteLine(`Job ${job.id} failed after ${job.retry_count} attempts`);
Console.WriteLine(`Error: ${job.last_error}`);
// Decide whether to retry manually or escalate
if (job.retry_count < 10 && isRetryable(job.last_error)) {
await ductape.jobs.retry(job.id, { ["delay"] = 60000 });
} else {
await escalateToSupport(job);
}
}
Dead Letter Queue Pattern
Store failed jobs for later analysis:
- TypeScript
- Java
- Go
- .NET
// When setting up webhooks
await ductape.jobs.setWebhook({
url: 'https://api.example.com/webhooks/dead-letter',
events: ['job.failed'],
secret: 'your-secret'
});
// In your webhook handler
async function handleDeadLetter(payload) {
// Store in dead letter collection
await ductape.database.insert({
table: 'dead_letter_queue',
data: {
job_id: payload.job.id,
namespace: payload.job.namespace,
product: payload.job.product,
error: payload.job.error,
failed_at: payload.timestamp,
payload: JSON.stringify(payload.job)
}
});
// Alert operations team
await alertOps({
message: `Job ${payload.job.id} moved to dead letter queue`,
error: payload.job.error
});
}
// When setting up webhooks
ductape.jobs.setWebhook(Map.of(
"url", "https://api.example.com/webhooks/dead-letter",
events: ['job.failed'],
"secret", "your-secret"
));
// In your webhook handler
async function handleDeadLetter(payload) Map.of(
// Store in dead letter collection
ductape.database.insert(Map.of(
"table", "dead_letter_queue",
data: Map.of(
job_id: payload.job.id,
namespace: payload.job.namespace,
product: payload.job.product,
error: payload.job.error,
failed_at: payload.timestamp,
payload: JSON.stringify(payload.job)
)
));
// Alert operations team
alertOps(Map.of(
message: `Job $Map.of(payload.job.id) moved to dead letter queue`,
error: payload.job.error
));
)
// When setting up webhooks
client.jobs.setWebhook({
"url": "https://api.example.com/webhooks/dead-letter",
events: ['job.failed'],
"secret": "your-secret"
});
// In your webhook handler
async function handleDeadLetter(payload) {
// Store in dead letter collection
client.database.insert({
"table": "dead_letter_queue",
data: {
job_id: payload.job.id,
namespace: payload.job.namespace,
product: payload.job.product,
error: payload.job.error,
failed_at: payload.timestamp,
payload: JSON.stringify(payload.job)
}
});
// Alert operations team
alertOps({
message: `Job ${payload.job.id} moved to dead letter queue`,
error: payload.job.error
});
}
// When setting up webhooks
await ductape.jobs.setWebhook({
["url"] = "https://api.example.com/webhooks/dead-letter",
events: ['job.failed'],
["secret"] = "your-secret"
});
// In your webhook handler
async function handleDeadLetter(payload) {
// Store in dead letter collection
await ductape.database.insert({
["table"] = "dead_letter_queue",
data: {
job_id: payload.job.id,
namespace: payload.job.namespace,
product: payload.job.product,
error: payload.job.error,
failed_at: payload.timestamp,
payload: JSON.stringify(payload.job)
}
});
// Alert operations team
await alertOps({
message: `Job ${payload.job.id} moved to dead letter queue`,
error: payload.job.error
});
}
Retry Monitoring
View Retry Status
- TypeScript
- Java
- Go
- .NET
const job = await ductape.jobs.get('job_abc123');
console.log('Total retries configured:', job.retries);
console.log('Retries attempted:', job.retry_count);
console.log('Status:', job.status);
console.log('Last error:', job.last_error);
Map<String, Object> job = ductape.jobs.get('job_abc123');
System.out.println('Total retries "configured", ", job.retries);
System.out.println("Retries "attempted", ", job.retry_count);
System.out.println(""Status", ", job.status);
System.out.println("Last error:', job.last_error);
job := client.jobs.get('job_abc123');
fmt.Println('Total retries "configured": ", job.retries);
fmt.Println("Retries "attempted": ", job.retry_count);
fmt.Println(""Status": ", job.status);
fmt.Println("Last error:', job.last_error);
var job = await ductape.jobs.get('job_abc123');
Console.WriteLine('Total retries ["configured"] = ", job.retries);
Console.WriteLine("Retries ["attempted"] = ", job.retry_count);
Console.WriteLine("["Status"] = ", job.status);
Console.WriteLine("Last error:', job.last_error);
Get Retry Statistics
- TypeScript
- Java
- Go
- .NET
const stats = await ductape.jobs.getStats({
from: Date.now() - 86400000 // Last 24 hours
});
console.log('Total jobs:', stats.total);
console.log('Completed on first try:', stats.completed_first_try);
console.log('Completed after retry:', stats.completed_after_retry);
console.log('Failed after retries:', stats.failed);
console.log('Average retry count:', stats.avg_retry_count);
Map<String, Object> stats = ductape.jobs.getStats(Map.of(
from: Date.now() - 86400000 // Last 24 hours
));
System.out.println('Total "jobs", ", stats.total);
System.out.println("Completed on first "try", ", stats.completed_first_try);
System.out.println("Completed after "retry", ", stats.completed_after_retry);
System.out.println("Failed after "retries", ", stats.failed);
System.out.println("Average retry count:', stats.avg_retry_count);
stats := client.jobs.getStats({
from: Date.now() - 86400000 // Last 24 hours
});
fmt.Println('Total "jobs": ", stats.total);
fmt.Println("Completed on first "try": ", stats.completed_first_try);
fmt.Println("Completed after "retry": ", stats.completed_after_retry);
fmt.Println("Failed after "retries": ", stats.failed);
fmt.Println("Average retry count:', stats.avg_retry_count);
var stats = await ductape.jobs.getStats({
from: Date.now() - 86400000 // Last 24 hours
});
Console.WriteLine('Total ["jobs"] = ", stats.total);
Console.WriteLine("Completed on first ["try"] = ", stats.completed_first_try);
Console.WriteLine("Completed after ["retry"] = ", stats.completed_after_retry);
Console.WriteLine("Failed after ["retries"] = ", stats.failed);
Console.WriteLine("Average retry count:', stats.avg_retry_count);
Best Practices
1. Set Appropriate Retry Counts
| Job Type | Recommended Retries |
|---|---|
| Critical business logic | 5-10 |
| Email/notifications | 3-5 |
| External API calls | 3-5 |
| Data sync operations | 3-5 |
| Analytics/logging | 1-2 |
2. Use Exponential Backoff
Always use exponential backoff for external services to:
- Avoid overwhelming recovering services
- Respect rate limits
- Allow time for transient issues to resolve
3. Categorize Errors
Properly categorize errors to avoid:
- Retrying unrecoverable errors (wastes resources)
- Not retrying recoverable errors (misses opportunities)
4. Set Maximum Delays
Always set a maxDelay to ensure jobs don't wait too long:
- TypeScript
- Java
- Go
- .NET
retryConfig: {
initialDelay: 1000,
backoffMultiplier: 2,
maxDelay: 300000 // Never wait more than 5 minutes
}
retryConfig: Map.of(
"initialDelay", 1000,
"backoffMultiplier", 2,
"maxDelay", 300000 // Never wait more than 5 minutes
)
retryConfig: {
"initialDelay": 1000,
"backoffMultiplier": 2,
"maxDelay": 300000 // Never wait more than 5 minutes
}
retryConfig: {
["initialDelay"] = 1000,
["backoffMultiplier"] = 2,
["maxDelay"] = 300000 // Never wait more than 5 minutes
}
5. Monitor and Alert
Set up monitoring for:
- Jobs that fail after all retries
- High retry rates (may indicate systemic issues)
- Unusual error patterns
See Also
- Scheduling Jobs - Create and schedule jobs
- Job Management - Monitor and control jobs
- Examples - Real-world retry patterns