You can read about the algorithm here.
An exponential backoff algorithm retries requests exponentially, increasing the waiting time between retries up to a maximum backoff time. An example is:
- Make a request to Cloud Storage.
- If the request fails, wait 1 + random_number_milliseconds seconds and retry the request.
- If the request fails, wait 2 + random_number_milliseconds seconds and retry the request.
- If the request fails, wait 4 + random_number_milliseconds seconds and retry the request.
- And so on, up to a maximum_backoff time.
- Continue waiting and retrying up to a maximum amount of time (deadline), but do not increase the maximum_backoff wait period between retries.
where:
- The wait time is min((2n +random_number_milliseconds), maximum_backoff), with n incremented by 1 for each iteration (request).
- random_number_milliseconds is a random number of milliseconds less than or equal to 1000. This helps to avoid cases where many clients become synchronized and all retry at once, sending requests in synchronized waves. The value of random_number_milliseconds is recalculated after each retry request.
- maximum_backoff is typically 32 or 64 seconds. The appropriate value depends on the use case. You can continue retrying once you reach the maximum_backoff time, but we recommend your request fail out after an amount of time to prevent your application from becoming unresponsive. For example, if a client uses a maximum_backoff time of 64 seconds, then after reaching this value, the client can retry every 64 seconds. The client then stops retrying after a deadline of 600 seconds.
EBARetrier.js
const utils = require("./utils");
async function EBARetrier(callback, argsList) {
const context = getEBAContext(callback, argsList);
while (isProcessingEntities(context)) {
try {
await processEntity(context);
} catch (error) {
handleFailedEntity(context, error);
}
}
}
function getEBAContext(callback, argsList) {
return {
maximumBackoff: 64,
deadline: 600,
startTime: Date.now(),
totalEntities: argsList.length,
failedEntities: [],
retryAttempt: 0,
processedEntities: 0,
lastProcessFailed: false,
args: null,
callback,
argsList,
};
}
function isProcessingEntities(context) {
return (
utils.getTimeElapsed(context.startTime) < context.deadline &&
context.processedEntities < context.totalEntities
);
}
async function processEntity(context) {
const args = context.failedEntities.shift() || context.argsList.shift();
context.args = args;
if (context.lastProcessFailed) {
const ms = getWaitTime(context);
await utils.wait(ms);
}
await context.callback(...args);
context.processedEntities++;
context.lastProcessFailed = false;
context.retryAttempt = 0;
}
function handleFailedEntity(context, error) {
console.error(error);
context.failedEntities.push(context.args);
context.lastProcessFailed = true;
context.retryAttempt++;
}
function getWaitTime(context) {
const randomMS = utils.getRandomInt(1001);
const attempt = context.retryAttempt - 1;
const ms = Math.pow(2, attempt) * 1000 + randomMS;
const maxBackoffMs = context.maximumBackoff * 1000;
if (ms > maxBackoffMs) {
return maxBackoffMs;
}
return ms;
}
module.exports = {
EBARetrier,
};
utils.js
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
function getTimeElapsed(startTime) {
return (Date.now() - startTime) / 1000;
}
function wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
module.exports = {
getRandomInt,
getTimeElapsed,
wait,
};
Any help will be appreciated! Thanks!