Quotas
Distribute load across multiple providers based on weights for better reliability and cost optimization.
Overview
Quotas allow you to split traffic between multiple providers. For example, send 70% of SMS through Twilio and 30% through Nexmo to:
- Reduce dependency on a single provider
- Optimize costs across providers
- Test new providers with limited traffic
Defining a Quota
- TypeScript
- Java
- Go
- .NET
const quota = await resilience.quota.define({
tag: 'sms-quota',
name: 'SMS Provider Quota',
description: 'Distribute SMS across providers',
input: {
phone: { type: 'string' },
message: { type: 'string' },
},
handler: async (ctx) => {
ctx.provider('twilio')
.weight(70)
.healthcheck('twilio-health')
.app('twilio-app')
.action('send-sms')
.mapInput((input) => ({
body: { to: input.phone, body: input.message }
}))
.mapOutput((res) => ({ messageId: res.sid }))
.retries(2);
ctx.provider('nexmo')
.weight(30)
.healthcheck('nexmo-health')
.app('nexmo-app')
.action('send-sms')
.mapInput((input) => ({
body: { to: input.phone, text: input.message }
}))
.mapOutput((res) => ({ messageId: res['message-id'] }))
.retries(2);
},
});
Map<String, Object> quota = resilience.quota.define(Map.of(
"tag", "sms-quota",
"name", "SMS Provider Quota",
"description", "Distribute SMS across providers",
input: Map.of(
phone: Map.of( "type", "string" ),
message: Map.of( "type", "string" )
),
handler: async (ctx) => Map.of(
ctx.provider('twilio')
.weight(70)
.healthcheck('twilio-health')
.app('twilio-app')
.action('send-sms')
.mapInput((input) => (Map.of(
body: Map.of( to: input.phone, body: input.message )
)))
.mapOutput((res) => (Map.of( messageId: res.sid )))
.retries(2);
ctx.provider('nexmo')
.weight(30)
.healthcheck('nexmo-health')
.app('nexmo-app')
.action('send-sms')
.mapInput((input) => (Map.of(
body: Map.of( to: input.phone, text: input.message )
)))
.mapOutput((res) => (Map.of( messageId: res['message-id'] )))
.retries(2);
)
));
quota := resilience.quota.define({
"tag": "sms-quota",
"name": "SMS Provider Quota",
"description": "Distribute SMS across providers",
input: {
phone: { "type": "string" },
message: { "type": "string" },
},
handler: async (ctx) => {
ctx.provider('twilio')
.weight(70)
.healthcheck('twilio-health')
.app('twilio-app')
.action('send-sms')
.mapInput((input) => ({
body: { to: input.phone, body: input.message }
}))
.mapOutput((res) => ({ messageId: res.sid }))
.retries(2);
ctx.provider('nexmo')
.weight(30)
.healthcheck('nexmo-health')
.app('nexmo-app')
.action('send-sms')
.mapInput((input) => ({
body: { to: input.phone, text: input.message }
}))
.mapOutput((res) => ({ messageId: res['message-id'] }))
.retries(2);
},
});
var quota = await resilience.quota.define({
["tag"] = "sms-quota",
["name"] = "SMS Provider Quota",
["description"] = "Distribute SMS across providers",
input: {
phone: { ["type"] = "string" },
message: { ["type"] = "string" },
},
handler: async (ctx) => {
ctx.provider('twilio')
.weight(70)
.healthcheck('twilio-health')
.app('twilio-app')
.action('send-sms')
.mapInput((input) => ({
body: { to: input.phone, body: input.message }
}))
.mapOutput((res) => ({ messageId: res.sid }))
.retries(2);
ctx.provider('nexmo')
.weight(30)
.healthcheck('nexmo-health')
.app('nexmo-app')
.action('send-sms')
.mapInput((input) => ({
body: { to: input.phone, text: input.message }
}))
.mapOutput((res) => ({ messageId: res['message-id'] }))
.retries(2);
},
});
Provider Configuration
Weight
Define the percentage of traffic for each provider:
- TypeScript
- Java
- Go
- .NET
ctx.provider('twilio').weight(70); // 70% of traffic
ctx.provider('nexmo').weight(30); // 30% of traffic
ctx.provider('twilio').weight(70); // 70% of traffic
ctx.provider('nexmo').weight(30); // 30% of traffic
ctx.provider('twilio').weight(70); // 70% of traffic
ctx.provider('nexmo').weight(30); // 30% of traffic
ctx.provider('twilio').weight(70); // 70% of traffic
ctx.provider('nexmo').weight(30); // 30% of traffic
Weights are relative - if you have weights of 70 and 30, that's 70% and 30%. If you have 7 and 3, it's still 70% and 30%.
Healthcheck
Link a healthcheck to automatically skip unhealthy providers:
- TypeScript
- Java
- Go
- .NET
ctx.provider('twilio')
.healthcheck('twilio-health') // Skip if unhealthy
.app('twilio-app')
.action('send-sms');
ctx.provider('twilio')
.healthcheck('twilio-health') // Skip if unhealthy
.app('twilio-app')
.action('send-sms');
ctx.provider('twilio')
.healthcheck('twilio-health') // Skip if unhealthy
.app('twilio-app')
.action('send-sms');
ctx.provider('twilio')
.healthcheck('twilio-health') // Skip if unhealthy
.app('twilio-app')
.action('send-sms');
Target Types
App Action
Route to an app action:
- TypeScript
- Java
- Go
- .NET
ctx.provider('twilio')
.weight(70)
.app('twilio-app')
.action('send-sms');
ctx.provider('twilio')
.weight(70)
.app('twilio-app')
.action('send-sms');
ctx.provider('twilio')
.weight(70)
.app('twilio-app')
.action('send-sms');
ctx.provider('twilio')
.weight(70)
.app('twilio-app')
.action('send-sms');
Database Action
Route to a database:
- TypeScript
- Java
- Go
- .NET
ctx.provider('primary-db')
.weight(80)
.database('main-db')
.action('write');
ctx.provider('primary-db')
.weight(80)
.database('main-db')
.action('write');
ctx.provider('primary-db')
.weight(80)
.database('main-db')
.action('write');
ctx.provider('primary-db')
.weight(80)
.database('main-db')
.action('write');
Feature
Route to a feature:
- TypeScript
- Java
- Go
- .NET
ctx.provider('standard-flow')
.weight(90)
.feature('order-processing')
.input({ priority: 'normal' });
ctx.provider('standard-flow')
.weight(90)
.feature('order-processing')
.input(Map.of( "priority", "normal" ));
ctx.provider('standard-flow')
.weight(90)
.feature('order-processing')
.input({ "priority": "normal" });
ctx.provider('standard-flow')
.weight(90)
.feature('order-processing')
.input({ ["priority"] = "normal" });
Notification
Route to a notification:
- TypeScript
- Java
- Go
- .NET
ctx.provider('email-provider')
.weight(100)
.notification('transactional-email')
.event('send');
ctx.provider('email-provider')
.weight(100)
.notification('transactional-email')
.event('send');
ctx.provider('email-provider')
.weight(100)
.notification('transactional-email')
.event('send');
ctx.provider('email-provider')
.weight(100)
.notification('transactional-email')
.event('send');
Input/Output Mapping
Transform input for each provider:
- TypeScript
- Java
- Go
- .NET
ctx.provider('twilio')
.weight(70)
.app('twilio-app')
.action('send-sms')
.mapInput((input) => ({
body: {
To: input.phone,
Body: input.message,
From: '+1234567890'
}
}))
.mapOutput((response) => ({
messageId: response.sid,
status: response.status
}));
ctx.provider('twilio')
.weight(70)
.app('twilio-app')
.action('send-sms')
.mapInput((input) => (Map.of(
body: Map.of(
To: input.phone,
Body: input.message,
"From", "+1234567890"
)
)))
.mapOutput((response) => (Map.of(
messageId: response.sid,
status: response.status
)));
ctx.provider('twilio')
.weight(70)
.app('twilio-app')
.action('send-sms')
.mapInput((input) => ({
body: {
To: input.phone,
Body: input.message,
"From": "+1234567890"
}
}))
.mapOutput((response) => ({
messageId: response.sid,
status: response.status
}));
ctx.provider('twilio')
.weight(70)
.app('twilio-app')
.action('send-sms')
.mapInput((input) => ({
body: {
To: input.phone,
Body: input.message,
["From"] = "+1234567890"
}
}))
.mapOutput((response) => ({
messageId: response.sid,
status: response.status
}));
Retries and Check Interval
Configure retry behavior:
- TypeScript
- Java
- Go
- .NET
ctx.provider('twilio')
.weight(70)
.app('twilio-app')
.action('send-sms')
.retries(3) // Retry up to 3 times
.checkInterval(5000); // Wait 5s between health checks
ctx.provider('twilio')
.weight(70)
.app('twilio-app')
.action('send-sms')
.retries(3) // Retry up to 3 times
.checkInterval(5000); // Wait 5s between health checks
ctx.provider('twilio')
.weight(70)
.app('twilio-app')
.action('send-sms')
.retries(3) // Retry up to 3 times
.checkInterval(5000); // Wait 5s between health checks
ctx.provider('twilio')
.weight(70)
.app('twilio-app')
.action('send-sms')
.retries(3) // Retry up to 3 times
.checkInterval(5000); // Wait 5s between health checks
Running Quotas
Synchronous Run
- TypeScript
- Java
- Go
- .NET
const result = await resilience.quota.run({
tag: 'sms-quota',
input: {
phone: '+1234567890',
message: 'Hello, World!'
},
});
Map<String, Object> result = resilience.quota.run(Map.of(
"tag", "sms-quota",
input: Map.of(
"phone", "+1234567890",
"message", "Hello, World!"
)
));
result := resilience.quota.run({
"tag": "sms-quota",
input: {
"phone": "+1234567890",
"message": "Hello, World!"
},
});
var result = await resilience.quota.run({
["tag"] = "sms-quota",
input: {
["phone"] = "+1234567890",
["message"] = "Hello, World!"
},
});
With Session
- TypeScript
- Java
- Go
- .NET
const result = await resilience.quota.run({
tag: 'sms-quota',
input: { phone: '+1234567890', message: 'Hello!' },
session: {
tag: 'user-session',
token: 'session-token-123'
},
});
Map<String, Object> result = resilience.quota.run(Map.of(
"tag", "sms-quota",
input: Map.of( "phone", "+1234567890", "message", "Hello!" ),
session: Map.of(
"tag", "user-session",
"token", "session-token-123"
)
));
result := resilience.quota.run({
"tag": "sms-quota",
input: { "phone": "+1234567890", "message": "Hello!" },
session: {
"tag": "user-session",
"token": "session-token-123"
},
});
var result = await resilience.quota.run({
["tag"] = "sms-quota",
input: { ["phone"] = "+1234567890", ["message"] = "Hello!" },
session: {
["tag"] = "user-session",
["token"] = "session-token-123"
},
});
With Caching
- TypeScript
- Java
- Go
- .NET
const result = await resilience.quota.run({
tag: 'sms-quota',
input: { phone: '+1234567890', message: 'Hello!' },
cache: 'sms-cache',
});
Map<String, Object> result = resilience.quota.run(Map.of(
"tag", "sms-quota",
input: Map.of( "phone", "+1234567890", "message", "Hello!" ),
"cache", "sms-cache"
));
result := resilience.quota.run({
"tag": "sms-quota",
input: { "phone": "+1234567890", "message": "Hello!" },
"cache": "sms-cache",
});
var result = await resilience.quota.run({
["tag"] = "sms-quota",
input: { ["phone"] = "+1234567890", ["message"] = "Hello!" },
["cache"] = "sms-cache",
});
Dispatching Quotas
Schedule quota execution for later:
- TypeScript
- Java
- Go
- .NET
// Dispatch with delay
const job = await resilience.quota.dispatch({
tag: 'sms-quota',
input: { phone: '+1234567890', message: 'Hello!' },
schedule: {
delay: 60000, // Run after 60 seconds
},
});
// Dispatch at specific time
const job = await resilience.quota.dispatch({
tag: 'sms-quota',
input: { phone: '+1234567890', message: 'Happy Birthday!' },
schedule: {
at: new Date('2024-12-25T00:00:00Z'),
},
});
// Dispatch with cron schedule
const job = await resilience.quota.dispatch({
tag: 'daily-report',
input: { type: 'daily' },
schedule: {
cron: '0 9 * * *', // Every day at 9 AM
},
});
// Dispatch with delay
Map<String, Object> job = resilience.quota.dispatch(Map.of(
"tag", "sms-quota",
input: Map.of( "phone", "+1234567890", "message", "Hello!" ),
schedule: Map.of(
"delay", 60000, // Run after 60 seconds
)
));
// Dispatch at specific time
Map<String, Object> job = resilience.quota.dispatch(Map.of(
"tag", "sms-quota",
input: Map.of( "phone", "+1234567890", "message", "Happy Birthday!" ),
schedule: Map.of(
at: new Date('2024-12-"25T00", 00:00Z')
)
));
// Dispatch with cron schedule
Map<String, Object> job = resilience.quota.dispatch(Map.of(
"tag", "daily-report",
input: Map.of( "type", "daily" ),
schedule: Map.of(
"cron", "0 9 * * *", // Every day at 9 AM
)
));
// Dispatch with delay
job := resilience.quota.dispatch({
"tag": "sms-quota",
input: { "phone": "+1234567890", "message": "Hello!" },
schedule: {
"delay": 60000, // Run after 60 seconds
},
});
// Dispatch at specific time
job := resilience.quota.dispatch({
"tag": "sms-quota",
input: { "phone": "+1234567890", "message": "Happy Birthday!" },
schedule: {
at: new Date('2024-12-"25T00": 00:00Z'),
},
});
// Dispatch with cron schedule
job := resilience.quota.dispatch({
"tag": "daily-report",
input: { "type": "daily" },
schedule: {
"cron": "0 9 * * *", // Every day at 9 AM
},
});
// Dispatch with delay
var job = await resilience.quota.dispatch({
["tag"] = "sms-quota",
input: { ["phone"] = "+1234567890", ["message"] = "Hello!" },
schedule: {
["delay"] = 60000, // Run after 60 seconds
},
});
// Dispatch at specific time
var job = await resilience.quota.dispatch({
["tag"] = "sms-quota",
input: { ["phone"] = "+1234567890", ["message"] = "Happy Birthday!" },
schedule: {
at: new Date('2024-12-["25T00"] = 00:00Z'),
},
});
// Dispatch with cron schedule
var job = await resilience.quota.dispatch({
["tag"] = "daily-report",
input: { ["type"] = "daily" },
schedule: {
["cron"] = "0 9 * * *", // Every day at 9 AM
},
});
Managing Quotas
Create
- TypeScript
- Java
- Go
- .NET
await resilience.quota.create(productId, quota);
resilience.quota.create(productId, quota);
resilience.quota.create(productId, quota);
await resilience.quota.create(productId, quota);
Fetch
- TypeScript
- Java
- Go
- .NET
const q = await resilience.quota.fetch(productId, 'sms-quota');
Map<String, Object> q = resilience.quota.fetch(productId, 'sms-quota');
q := resilience.quota.fetch(productId, 'sms-quota');
var q = await resilience.quota.fetch(productId, 'sms-quota');
Fetch All
- TypeScript
- Java
- Go
- .NET
const quotas = await resilience.quota.fetchAll(productId);
Map<String, Object> quotas = resilience.quota.fetchAll(productId);
quotas := resilience.quota.fetchAll(productId);
var quotas = await resilience.quota.fetchAll(productId);
Update
- TypeScript
- Java
- Go
- .NET
await resilience.quota.update(productId, 'sms-quota', {
input: { phone: { type: 'string' }, message: { type: 'string' } },
handler: async (ctx) => {
// Update weights
ctx.provider('twilio').weight(50).app('twilio-app').action('send-sms');
ctx.provider('nexmo').weight(50).app('nexmo-app').action('send-sms');
},
});
resilience.quota.update(productId, 'sms-quota', Map.of(
input: Map.of( phone: Map.of( "type", "string" ), message: Map.of( "type", "string" ) ),
handler: async (ctx) => Map.of(
// Update weights
ctx.provider('twilio').weight(50).app('twilio-app').action('send-sms');
ctx.provider('nexmo').weight(50).app('nexmo-app').action('send-sms');
)
));
resilience.quota.update(productId, 'sms-quota', {
input: { phone: { "type": "string" }, message: { "type": "string" } },
handler: async (ctx) => {
// Update weights
ctx.provider('twilio').weight(50).app('twilio-app').action('send-sms');
ctx.provider('nexmo').weight(50).app('nexmo-app').action('send-sms');
},
});
await resilience.quota.update(productId, 'sms-quota', {
input: { phone: { ["type"] = "string" }, message: { ["type"] = "string" } },
handler: async (ctx) => {
// Update weights
ctx.provider('twilio').weight(50).app('twilio-app').action('send-sms');
ctx.provider('nexmo').weight(50).app('nexmo-app').action('send-sms');
},
});
Delete
- TypeScript
- Java
- Go
- .NET
await resilience.quota.delete(productId, 'sms-quota');
resilience.quota.delete(productId, 'sms-quota');
resilience.quota.delete(productId, 'sms-quota');
await resilience.quota.delete(productId, 'sms-quota');
How Quota Selection Works
- Filter healthy providers: Skip providers whose healthcheck is failing
- Calculate effective weights: Redistribute weights among healthy providers
- Random selection: Use weighted random selection to pick a provider
- Execute: Run the selected provider's action
- Retry if needed: On failure, retry or try another provider
Best Practices
Start with Uneven Weights
Begin with most traffic to your primary provider:
- TypeScript
- Java
- Go
- .NET
ctx.provider('primary').weight(90);
ctx.provider('backup').weight(10);
ctx.provider('primary').weight(90);
ctx.provider('backup').weight(10);
ctx.provider('primary').weight(90);
ctx.provider('backup').weight(10);
ctx.provider('primary').weight(90);
ctx.provider('backup').weight(10);
Always Use Healthchecks
Link healthchecks to automatically avoid unhealthy providers:
- TypeScript
- Java
- Go
- .NET
ctx.provider('twilio')
.healthcheck('twilio-health') // Required for resilience
.app('twilio-app')
.action('send-sms');
ctx.provider('twilio')
.healthcheck('twilio-health') // Required for resilience
.app('twilio-app')
.action('send-sms');
ctx.provider('twilio')
.healthcheck('twilio-health') // Required for resilience
.app('twilio-app')
.action('send-sms');
ctx.provider('twilio')
.healthcheck('twilio-health') // Required for resilience
.app('twilio-app')
.action('send-sms');
Normalize Output
Use mapOutput to ensure consistent responses regardless of provider:
- TypeScript
- Java
- Go
- .NET
// Both providers return { messageId, status }
ctx.provider('twilio')
.mapOutput((res) => ({ messageId: res.sid, status: res.status }));
ctx.provider('nexmo')
.mapOutput((res) => ({ messageId: res['message-id'], status: res.status }));
// Both providers return Map.of( messageId, status )
ctx.provider('twilio')
.mapOutput((res) => (Map.of( messageId: res.sid, status: res.status )));
ctx.provider('nexmo')
.mapOutput((res) => (Map.of( messageId: res['message-id'], status: res.status )));
// Both providers return { messageId, status }
ctx.provider('twilio')
.mapOutput((res) => ({ messageId: res.sid, status: res.status }));
ctx.provider('nexmo')
.mapOutput((res) => ({ messageId: res['message-id'], status: res.status }));
// Both providers return { messageId, status }
ctx.provider('twilio')
.mapOutput((res) => ({ messageId: res.sid, status: res.status }));
ctx.provider('nexmo')
.mapOutput((res) => ({ messageId: res['message-id'], status: res.status }));