import { Queue } from "bullmq";
import { createHash } from "crypto";
export const connection = { host: "127.0.0.1", port: 6379 };
export interface ChargePayload {
paymentIntentId: string;
amountCents: number;
currency: string;
}
export const chargeQueue = new Queue<ChargePayload>("charges", { connection });
function dedupeId(intentId: string): string {
return "charge:" + createHash("sha256").update(intentId).digest("hex").slice(0, 24);
}
export async function enqueueChargeCustomer(payload: ChargePayload) {
const jobId = dedupeId(payload.paymentIntentId);
return chargeQueue.add("charge-customer", payload, {
jobId,
attempts: 5,
backoff: { type: "exponential", delay: 2000 },
// Keep completed jobs so a re-add with the same jobId is ignored.
removeOnComplete: { count: 5000 },
removeOnFail: { age: 24 * 3600 },
});
}
import IORedis from "ioredis";
export interface StoredResult {
chargeId: string;
}
type BeginOutcome =
| { status: "cached"; result: StoredResult }
| { status: "locked"; token: string }
| { status: "busy" };
export class IdempotencyStore {
constructor(private redis: IORedis, private ttlMs = 60_000) {}
private lockKey(key: string) { return `lock:${key}`; }
private resultKey(key: string) { return `result:${key}`; }
async begin(key: string, token: string): Promise<BeginOutcome> {
const existing = await this.redis.get(this.resultKey(key));
if (existing) {
return { status: "cached", result: JSON.parse(existing) as StoredResult };
}
const acquired = await this.redis.set(this.lockKey(key), token, "PX", this.ttlMs, "NX");
return acquired ? { status: "locked", token } : { status: "busy" };
}
async complete(key: string, token: string, result: StoredResult): Promise<void> {
await this.redis.set(this.resultKey(key), JSON.stringify(result));
await this.release(key, token);
}
private static RELEASE = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end`;
async release(key: string, token: string): Promise<void> {
await this.redis.eval(IdempotencyStore.RELEASE, 1, this.lockKey(key), token);
}
}
import { Worker, DelayedError, Job } from "bullmq";
import IORedis from "ioredis";
import { randomUUID } from "crypto";
import { connection, ChargePayload } from "./queue";
import { IdempotencyStore } from "./idempotency";
import { chargeCustomer } from "./payments";
const redis = new IORedis(connection);
const store = new IdempotencyStore(redis);
async function process(job: Job<ChargePayload>) {
const key = `pi:${job.data.paymentIntentId}`;
const token = randomUUID();
const begin = await store.begin(key, token);
if (begin.status === "cached") {
return begin.result; // already charged on a prior attempt
}
if (begin.status === "busy") {
await job.moveToDelayed(Date.now() + 5000, job.token);
throw new DelayedError();
}
try {
const charge = await chargeCustomer(job.data.paymentIntentId, job.data.amountCents, job.data.currency);
const result = { chargeId: charge.id };
await store.complete(key, begin.token, result);
return result;
} catch (err) {
await store.release(key, begin.token);
throw err;
}
}
export const chargeWorker = new Worker<ChargePayload>("charges", process, {
connection,
concurrency: 8,
});
chargeWorker.on("failed", (job, err) => {
console.error(`charge job ${job?.id} failed: ${err.message}`);
});
This snippet shows how to make BullMQ jobs safely retryable and deduplicated so that the same logical event never produces two side effects, even when a producer enqueues it multiple times or a worker crashes mid-run. The core idea is two layers of protection: BullMQ's own jobId for enqueue-time dedupe, and a Redis-backed result marker for processing-time idempotency.
In queue.ts, the Queue is created and enqueueChargeCustomer derives a deterministic jobId from a stable business key (here a payment intent id) via dedupeId. BullMQ guarantees that adding a job with an existing jobId is a no-op while that job still exists, so two rapid webhook deliveries for the same intent collapse into one job. The removeOnComplete window matters: as long as the completed job is retained, re-adds are ignored, so the retention count effectively defines the dedupe horizon. That trade-off is important — a very short retention window reopens the door to duplicate enqueues, while a long one costs Redis memory.
Because jobId dedupe only helps at enqueue time, worker.ts adds a second guard for the case where a job is retried after it already succeeded partway. The IdempotencyStore in idempotency.ts uses a Redis SET ... NX lock plus a persisted result key. Before doing real work the worker calls begin, which either returns a cached result (short-circuiting duplicate processing) or acquires a lock. After the side effect runs, complete stores the result and releases the lock; abort releases the lock on failure so a retry can proceed.
The SET key value NX PX pattern gives an atomic "claim this key if unclaimed" with an expiry, preventing a dead worker from holding the lock forever. The persisted result: key is what makes a genuine retry idempotent: chargeCustomer runs once, and any later attempt reads the stored charge id instead of calling the payment provider again. Note the ordering in the worker — the external charge happens inside the lock, and only then is the result recorded, so a crash between the two leaves the lock to expire and the job to retry cleanly. This layering is the standard approach when exactly-once delivery is impossible but effectively-once processing is required.
Related snips
timestamp = request.headers.fetch('X-Signature-Timestamp')
signature = request.headers.fetch('X-Signature')
payload = request.raw_post
data = "#{timestamp}.#{payload}"
expected = OpenSSL::HMAC.hexdigest('SHA256', ENV.fetch('WEBHOOK_SECRET'), data)
HMAC signed API requests for webhook and partner integrity
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
use crossbeam::channel::unbounded;
use std::thread;
fn main() {
let (tx, rx) = unbounded();
Crossbeam for advanced concurrent data structures
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
Share this code
Here's the card — post it anywhere.