Skip to content

Instantly share code, notes, and snippets.

@adeisbright
Last active May 9, 2026 13:03
Show Gist options
  • Select an option

  • Save adeisbright/3a3471835e7234870412e3aab8ce29ec to your computer and use it in GitHub Desktop.

Select an option

Save adeisbright/3a3471835e7234870412e3aab8ce29ec to your computer and use it in GitHub Desktop.
An example on how to handle thunder herd problem
import Redis from "ioredis";
interface TokenResponse {
token: string;
expires_in: string;
}
interface AuthClientConfig {
url: string;
authPayload: Record<string, unknown>;
cacheKey?: string;
retryAttempts?: number;
retryDelayMs?: number;
}
export class AuthTokenService {
private inFlightPromise: Promise<string> | null = null;
constructor(
private readonly redis: Redis,
private readonly config: AuthClientConfig
) {}
async getToken(): Promise<string> {
const {
cacheKey
} = this.config;
// 1. Local memory lock
if (this.inFlightPromise) {
return this.inFlightPromise;
}
// 2. Shared cache lookup
const cachedToken = await this.redis.get(cacheKey);
if (cachedToken) {
return cachedToken;
}
// 3. Acquire token
this.inFlightPromise = this.acquireToken();
try {
return await this.inFlightPromise;
} finally {
this.inFlightPromise = null;
}
}
private async acquireToken(): Promise<string> {
const {
url,
authPayload,
retryAttempts = 3,
retryDelayMs = 500,
cacheKey
} = this.config;
for (let attempt = 0; attempt <= retryAttempts; attempt++) {
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(authPayload),
});
if (!response.ok) {
throw new Error(`Auth server failed: ${response.status}`);
}
const data = (await response.json()) as TokenResponse;
const expirationSeconds = Math.max(
60,
Math.floor(
(new Date(data.expires_in).getTime() - Date.now()) / 1000
) - 30
);
await this.redis.setex(
cacheKey,
expirationSeconds,
data.token
);
return data.token;
} catch (error) {
if (attempt === retryAttempts) {
throw error;
}
await this.sleep(
retryDelayMs * Math.pow(2, attempt)
);
}
}
throw new Error("Failed to acquire token");
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment