vidspotai-shared 1.0.72 → 1.0.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,11 +10,36 @@ declare class BullMQService {
10
10
  private queues;
11
11
  private workers;
12
12
  private redisOptions;
13
+ private connection?;
13
14
  private concurrency;
14
15
  private initialized;
15
16
  private shuttingDown;
16
17
  private constructor();
17
18
  static getInstance(options: BullMQServiceOptions): BullMQService;
19
+ /**
20
+ * Lazily create and return the single shared ioredis connection for this
21
+ * process. ALL Queues and Workers must use this instance rather than a plain
22
+ * options object.
23
+ *
24
+ * WHY THIS EXISTS — Redis client exhaustion:
25
+ * Passing a plain RedisOptions object to `new Queue`/`new Worker` makes
26
+ * BullMQ open a BRAND-NEW connection for every component, so total
27
+ * connections scale as (instances × queues). With 7 queues across N warm
28
+ * Cloud Run instances + the worker fleet, this blew past the Redis
29
+ * `maxclients` cap → "ERR max number of clients reached".
30
+ *
31
+ * Passing a shared IORedis instance instead:
32
+ * - Queues reuse this one connection (0 extra per queue).
33
+ * - Workers reuse it for non-blocking commands and BullMQ internally
34
+ * `.duplicate()`s it for each worker's MANDATORY blocking client
35
+ * (one blocking conn per worker is unavoidable by BullMQ design).
36
+ * Net: a producer (Functions) holds exactly 1 connection regardless of how
37
+ * many queues it enqueues to; a worker host holds 1 + (1 per worker).
38
+ *
39
+ * `maxRetriesPerRequest: null` (in sharedRedisOptions) is REQUIRED by BullMQ
40
+ * for the connection it blocks on.
41
+ */
42
+ private getConnection;
18
43
  /** Initialize BullMQ service (like your initRedis) */
19
44
  init(): Promise<void>;
20
45
  /** Add a job */
@@ -1 +1 @@
1
- {"version":3,"file":"bullmq.service.d.ts","sourceRoot":"","sources":["../../src/services/bullmq.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,GAAG,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAIvC,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE1C,UAAU,oBAAoB;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,cAAM,aAAa;IACjB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAgB;IACvC,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAS;IAE7B,OAAO;WAQO,WAAW,CAAC,OAAO,EAAE,oBAAoB;IAOvD,sDAAsD;IACzC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBlC,gBAAgB;IACH,MAAM,CACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAUpC,YAAY,CACjB,UAAU,EAAE,MAAM,EAAE,EACpB,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,EACtC,eAAe,CAAC,EAAE,MAAM,CACtB,MAAM,EACN;QACE,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CACF;CA+FJ;AASD,eAAO,MAAM,MAAM,eAGjB,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,GAAG,CAAC"}
1
+ {"version":3,"file":"bullmq.service.d.ts","sourceRoot":"","sources":["../../src/services/bullmq.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,GAAG,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAgB,EAAwB,YAAY,EAAE,MAAM,SAAS,CAAC;AAItE,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE1C,UAAU,oBAAoB;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,cAAM,aAAa;IACjB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAgB;IACvC,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,YAAY,CAAgB;IAIpC,OAAO,CAAC,UAAU,CAAC,CAAc;IACjC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAS;IAE7B,OAAO;WAQO,WAAW,CAAC,OAAO,EAAE,oBAAoB;IAOvD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,CAAC,aAAa;IAOrB,sDAAsD;IACzC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBlC,gBAAgB;IACH,MAAM,CACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAUpC,YAAY,CACjB,UAAU,EAAE,MAAM,EAAE,EACpB,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,EACtC,eAAe,CAAC,EAAE,MAAM,CACtB,MAAM,EACN;QACE,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CACF;CAwGJ;AASD,eAAO,MAAM,MAAM,eAGjB,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,GAAG,CAAC"}
@@ -1,8 +1,12 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.bullmq = void 0;
4
7
  // libs/bullmqService.ts
5
8
  const bullmq_1 = require("bullmq");
9
+ const ioredis_1 = __importDefault(require("ioredis"));
6
10
  const logger_1 = require("../utils/logger");
7
11
  const redisOptions_1 = require("./redisOptions");
8
12
  class BullMQService {
@@ -20,19 +24,44 @@ class BullMQService {
20
24
  }
21
25
  return BullMQService.instance;
22
26
  }
27
+ /**
28
+ * Lazily create and return the single shared ioredis connection for this
29
+ * process. ALL Queues and Workers must use this instance rather than a plain
30
+ * options object.
31
+ *
32
+ * WHY THIS EXISTS — Redis client exhaustion:
33
+ * Passing a plain RedisOptions object to `new Queue`/`new Worker` makes
34
+ * BullMQ open a BRAND-NEW connection for every component, so total
35
+ * connections scale as (instances × queues). With 7 queues across N warm
36
+ * Cloud Run instances + the worker fleet, this blew past the Redis
37
+ * `maxclients` cap → "ERR max number of clients reached".
38
+ *
39
+ * Passing a shared IORedis instance instead:
40
+ * - Queues reuse this one connection (0 extra per queue).
41
+ * - Workers reuse it for non-blocking commands and BullMQ internally
42
+ * `.duplicate()`s it for each worker's MANDATORY blocking client
43
+ * (one blocking conn per worker is unavoidable by BullMQ design).
44
+ * Net: a producer (Functions) holds exactly 1 connection regardless of how
45
+ * many queues it enqueues to; a worker host holds 1 + (1 per worker).
46
+ *
47
+ * `maxRetriesPerRequest: null` (in sharedRedisOptions) is REQUIRED by BullMQ
48
+ * for the connection it blocks on.
49
+ */
50
+ getConnection() {
51
+ if (this.connection)
52
+ return this.connection;
53
+ this.connection = new ioredis_1.default(this.redisOptions);
54
+ (0, redisOptions_1.attachRedisListeners)(this.connection, "BullMQ");
55
+ return this.connection;
56
+ }
23
57
  /** Initialize BullMQ service (like your initRedis) */
24
58
  async init() {
25
59
  if (this.initialized)
26
60
  return;
27
61
  try {
28
- // Create a temporary test queue
29
- const testQueue = new bullmq_1.Queue("__init__", {
30
- connection: this.redisOptions,
31
- });
32
- // Ping Redis via ioredis client
33
- const testQueueClient = await testQueue.client;
34
- testQueueClient.ping();
35
- await testQueue.close();
62
+ // Ping the shared connection directly — no throwaway Queue (which would
63
+ // open, then close, an extra connection on every cold start).
64
+ await this.getConnection().ping();
36
65
  this.initialized = true;
37
66
  logger_1.logger.info("BullMQService initialized and connected to Redis");
38
67
  }
@@ -48,7 +77,7 @@ class BullMQService {
48
77
  if (!this.initialized)
49
78
  await this.init();
50
79
  const queue = this.queues.get(queueName) ??
51
- new bullmq_1.Queue(queueName, { connection: this.redisOptions });
80
+ new bullmq_1.Queue(queueName, { connection: this.getConnection() });
52
81
  this.queues.set(queueName, queue);
53
82
  await queue.add(opts?.jobId || "default", data, { delay: opts?.delay });
54
83
  }
@@ -58,11 +87,11 @@ class BullMQService {
58
87
  await this.init();
59
88
  for (const queueName of queueNames) {
60
89
  const queue = this.queues.get(queueName) ??
61
- new bullmq_1.Queue(queueName, { connection: this.redisOptions });
90
+ new bullmq_1.Queue(queueName, { connection: this.getConnection() });
62
91
  this.queues.set(queueName, queue);
63
92
  const overrides = perQueueOptions?.[queueName] ?? {};
64
93
  const worker = new bullmq_1.Worker(queueName, async (job) => processor(job), {
65
- connection: this.redisOptions,
94
+ connection: this.getConnection(),
66
95
  concurrency: overrides.concurrency ?? this.concurrency,
67
96
  // N5 (agent refactor Stage 2). Explicit lock semantics:
68
97
  // - lockDuration: 60s default. BullMQ auto-renews while the
@@ -122,6 +151,15 @@ class BullMQService {
122
151
  await worker.close();
123
152
  logger_1.logger.info(`Worker for queue closed`, { queueName: worker.name });
124
153
  }
154
+ // Close queues and the shared connection so blocking-client dups are
155
+ // released cleanly (prevents lingering clients on the Redis side after
156
+ // a rolling deploy / SIGTERM on Railway).
157
+ for (const queue of this.queues.values()) {
158
+ await queue.close();
159
+ }
160
+ if (this.connection) {
161
+ await this.connection.quit();
162
+ }
125
163
  logger_1.logger.info("All workers closed. Exiting process.");
126
164
  process.exit(0);
127
165
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vidspotai-shared",
3
- "version": "1.0.72",
3
+ "version": "1.0.73",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "exports": {