From 6570c25617a541d93a8878741ca91c6130e0727b Mon Sep 17 00:00:00 2001 From: Rushil Perera Date: Wed, 17 Dec 2025 09:25:07 -0500 Subject: [PATCH] feat: configure queue retry delays with min/max bounds and update exponential backoff defaults --- src/index.ts | 13 +++-- src/libs/calculateExponentialBackoff.ts | 64 +++++++++++++------------ 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7f6bd47..7a4082e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -123,9 +123,13 @@ export default { }, } satisfies ExportedHandler; -const retryDelayConfig: Record = { - ANILIST_UPDATES: Duration.fromObject({ minutes: 1 }), - NEW_EPISODE: Duration.fromObject({ hours: 1 }), +const retryDelayConfig: Partial< + Record +> = { + NEW_EPISODE: { + min: Duration.fromObject({ hours: 1 }), + max: Duration.fromObject({ hours: 12 }), + }, }; function onMessageQueue( @@ -144,7 +148,8 @@ function onMessageQueue( message.retry({ delaySeconds: calculateExponentialBackoff({ attempt: message.attempts, - baseMin: retryDelayConfig[messageBatch.queue as QN], + baseMin: retryDelayConfig[messageBatch.queue as QN]?.min, + absCap: retryDelayConfig[messageBatch.queue as QN]?.max, }), }); } diff --git a/src/libs/calculateExponentialBackoff.ts b/src/libs/calculateExponentialBackoff.ts index 7c47dfd..2a71d63 100644 --- a/src/libs/calculateExponentialBackoff.ts +++ b/src/libs/calculateExponentialBackoff.ts @@ -1,49 +1,53 @@ import { Duration, type DurationLike } from "luxon"; interface CalculateExponentialBackoffOptions { - attempt: number; - baseMin?: DurationLike; - absCap?: DurationLike; - fuzzFactor?: number; + attempt: number; + baseMin?: DurationLike; + absCap?: DurationLike; + fuzzFactor?: number; } /** - * Generates a backoff time where both the Minimum floor and Maximum ceiling + * Generates a backoff time where both the Minimum floor and Maximum ceiling * are "fuzzed" with jitter to prevent clustering at the edges. * * @param attempt - The current retry attempt (0-indexed). * @param baseMin - The nominal minimum wait time (default: 1s). * @param absCap - The absolute maximum wait time (default: 60s). * @param fuzzFactor - How much to wobble the edges (0.1 = +/- 10%). - * + * * @returns A random duration between the nominal minimum and maximum, in seconds. */ -export function calculateExponentialBackoff( - { attempt, baseMin: baseMinDuration = Duration.fromObject({ seconds: 1 }), absCap: absCapDuration = Duration.fromObject({ seconds: 60 }), fuzzFactor = 0.2 }: CalculateExponentialBackoffOptions -): number { - const baseMin = Duration.fromDurationLike(baseMinDuration).as('seconds'); - const absCap = Duration.fromDurationLike(absCapDuration).as('seconds'); +export function calculateExponentialBackoff({ + attempt, + baseMin: baseMinDuration = Duration.fromObject({ minutes: 1 }), + absCap: absCapDuration = Duration.fromObject({ hours: 1 }), + fuzzFactor = 0.2, +}: CalculateExponentialBackoffOptions): number { + const baseMin = Duration.fromDurationLike(baseMinDuration).as("seconds"); + const absCap = Duration.fromDurationLike(absCapDuration).as("seconds"); - // 1. Calculate nominal boundaries - // Example: If baseMin is 1s, the nominal boundaries are 1s, 2s, 4s, 8s... (The 'ceiling' grows exponentially) - const nominalMin = baseMin; - const nominalCeiling = Math.min(baseMin * Math.pow(2, attempt), absCap); + // 1. Calculate nominal boundaries + // Example: If baseMin is 1s, the nominal boundaries are 1s, 2s, 4s, 8s... (The 'ceiling' grows exponentially) + const nominalMin = baseMin; + const nominalCeiling = Math.min(baseMin * Math.pow(2, attempt), absCap); - // 2. Fuzz the Min (The Floor) - // Example: If min is 1s and fuzz is 0.2, the floor becomes random between 0.8s and 1.2s - const minFuzz = nominalMin * fuzzFactor; - const fuzzedMin = nominalMin + (Math.random() * 2 * minFuzz - minFuzz); + // 2. Fuzz the Min (The Floor) + // Example: If min is 1s and fuzz is 0.2, the floor becomes random between 0.8s and 1.2s + const minFuzz = nominalMin * fuzzFactor; + const fuzzedMin = nominalMin + (Math.random() * 2 * minFuzz - minFuzz); - // 3. Fuzz the Max (The Ceiling) - // Example: If ceiling is 4s (and fuzz is 0.2), it becomes random between 3.2s and 4.8s - const maxFuzz = nominalCeiling * fuzzFactor; - const fuzzedCeiling = nominalCeiling + (Math.random() * 2 * maxFuzz - maxFuzz); + // 3. Fuzz the Max (The Ceiling) + // Example: If ceiling is 4s (and fuzz is 0.2), it becomes random between 3.2s and 4.8s + const maxFuzz = nominalCeiling * fuzzFactor; + const fuzzedCeiling = + nominalCeiling + (Math.random() * 2 * maxFuzz - maxFuzz); - // Safety: Ensure we don't return a negative number or cross boundaries weirdly - // (e.g. if fuzz makes min > max, we swap or clamp) - const safeMin = Math.max(0, fuzzedMin); - const safeMax = Math.max(safeMin, fuzzedCeiling); + // Safety: Ensure we don't return a negative number or cross boundaries weirdly + // (e.g. if fuzz makes min > max, we swap or clamp) + const safeMin = Math.max(0, fuzzedMin); + const safeMax = Math.max(safeMin, fuzzedCeiling); - // 4. Return random value in the new fuzzy range - return safeMin + Math.random() * (safeMax - safeMin); -} \ No newline at end of file + // 4. Return random value in the new fuzzy range + return safeMin + Math.random() * (safeMax - safeMin); +}