import { getRedisClient } from './storage/redis'; const POD_ID = process.env.POD_ID ?? `local-${process.pid}`; export interface LeaderLease { tryAcquireOrRenew(): Promise; release(): Promise; } /** * Vytvoří leader lease pro daný klíč — zajišťuje, že periodickou úlohu * spouští v multi-replica nasazení pouze jedna instance. * Při non-Redis storage vždy vrací true (single-process, leader election není potřeba). */ export function createLeaderLease(leaseKey: string, ttlSeconds = 90): LeaderLease { return { async tryAcquireOrRenew(): Promise { if (process.env.STORAGE?.toLowerCase() !== 'redis') return true; try { const c = getRedisClient(); if (!c) return true; // Zkusíme získat lease atomicky (SET NX EX) const acquired = await c.set(leaseKey, POD_ID, { NX: true, EX: ttlSeconds }); if (acquired !== null) return true; // lease čerstvě získána // Pokud jsme ji nedostali, ověříme zda ji držíme my const currentHolder = await c.get(leaseKey); if (currentHolder === POD_ID) { // Naše lease — obnovíme TTL await c.set(leaseKey, POD_ID, { EX: ttlSeconds }); return true; } return false; // lease drží jiná instance } catch (e) { console.error(`Leader lease (${leaseKey}): chyba při získávání, úloha bude spuštěna`, e); return true; // při chybě raději spustíme, než vynecháme } }, async release(): Promise { if (process.env.STORAGE?.toLowerCase() !== 'redis') return; try { const c = getRedisClient(); if (!c) return; const currentHolder = await c.get(leaseKey); if (currentHolder === POD_ID) { await c.del(leaseKey); console.log(`Leader lease (${leaseKey}): uvolněna`); } } catch (e) { console.error(`Leader lease (${leaseKey}): chyba při uvolňování`, e); } }, }; }