/** * apps/worker — ioredis factory. * * `createRedis()` builds the shared connection. BullMQ requirements: * `maxRetriesPerRequest: null` (long-running workers must not let ioredis * abort in-flight commands during a Redis failover). `lazyConnect: false` * so failures surface immediately at startup. Connection errors are logged * (warn) instead of crashing — ioredis retries with backoff on its own. */ import { Redis } from "ioredis"; import { getLogger } from "../logger.js"; const log = getLogger("redis"); let shared: Redis | null = null; export function createRedis(url: string): Redis { const redis = new Redis(url, { maxRetriesPerRequest: null, // REQUIRED by BullMQ lazyConnect: false, enableReadyCheck: true, retryStrategy: (times) => Math.min(times * 200, 5000) }); redis.on("error", (err: Error) => { log.warn({ err: err.message }, "redis connection error (auto-retrying)"); }); redis.on("connect", () => log.debug("redis connected")); redis.on("ready", () => log.debug("redis ready")); redis.on("reconnecting", (ms: number) => log.warn(`redis reconnecting in ${ms}ms`)); return redis; } /** Shared instance for the whole process (workers + consumers + heartbeat). */ export function getRedis(url: string): Redis { if (!shared) shared = createRedis(url); return shared; } export async function closeRedis(): Promise { if (shared) { await shared.quit().catch(() => shared!.disconnect()); shared = null; } }