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,
|
|
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
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
};
|