Description
The background sweep workers — GigExpiryWorkerService (src/gig/gig-expiry-worker.service.ts) and RepinWorkerService (src/ipfs-pinning/repin-worker.service.ts) — each register their own process-local setInterval on onModuleInit. In any deployment running more than one instance behind a load balancer, every instance runs its own copy of each sweep independently, so a single expiring gig or degraded IPFS pin gets processed N times, dispatching N duplicate webhooks per event.
This is independent of where the underlying gig/pin data eventually lives (see the persistence spike, #181) — it's specifically about making sure only one instance's timer is ever "active" at a time. The backend already has a Redis client wired up (REDIS_CLIENT in src/common/redis/redis.module.ts) via the rate-limiting module, so the primitive needed (a distributed lock) is already available without adding a new infra dependency.
Component
Backend
Difficulty
🔴 Hard
Tasks
Acceptance Criteria
Estimated Time
2-3 days
Description
The background sweep workers —
GigExpiryWorkerService(src/gig/gig-expiry-worker.service.ts) andRepinWorkerService(src/ipfs-pinning/repin-worker.service.ts) — each register their own process-localsetIntervalononModuleInit. In any deployment running more than one instance behind a load balancer, every instance runs its own copy of each sweep independently, so a single expiring gig or degraded IPFS pin gets processed N times, dispatching N duplicate webhooks per event.This is independent of where the underlying gig/pin data eventually lives (see the persistence spike, #181) — it's specifically about making sure only one instance's timer is ever "active" at a time. The backend already has a Redis client wired up (
REDIS_CLIENTinsrc/common/redis/redis.module.ts) via the rate-limiting module, so the primitive needed (a distributed lock) is already available without adding a new infra dependency.Component
Backend
Difficulty
🔴 Hard
Tasks
SET key value NX PX <ttl>+ periodic renewal) shared by both worker servicesrunOnce(); other instances should skip their tickonModuleDestroy) and on lock-holder crash (TTL expiry)REDIS_URL-dependency behavior when Redis is unavailable (currentlyREDIS_CLIENTcan benull— decide whether workers should fail closed or fall back to per-instance execution with a warning)Acceptance Criteria
runOnce()sweep per interval tick, verified by a test or integration harnessgig-expiry-worker.service.spec.ts/repin-worker.service.spec.tstests still passREDIS_URLis unset is explicitly defined and documentedEstimated Time
2-3 days