atomic-queues 1.2.8 → 1.5.2
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.
- package/README.md +363 -636
- package/dist/decorators/decorators.d.ts +123 -4
- package/dist/decorators/decorators.d.ts.map +1 -1
- package/dist/decorators/decorators.js +218 -10
- package/dist/decorators/decorators.js.map +1 -1
- package/dist/domain/interfaces.d.ts +127 -1
- package/dist/domain/interfaces.d.ts.map +1 -1
- package/dist/module/atomic-queues.module.d.ts.map +1 -1
- package/dist/module/atomic-queues.module.js +2 -0
- package/dist/module/atomic-queues.module.js.map +1 -1
- package/dist/services/cron-manager/cron-manager.service.d.ts +23 -1
- package/dist/services/cron-manager/cron-manager.service.d.ts.map +1 -1
- package/dist/services/cron-manager/cron-manager.service.js +150 -41
- package/dist/services/cron-manager/cron-manager.service.js.map +1 -1
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/processor-discovery/processor-discovery.service.d.ts +49 -4
- package/dist/services/processor-discovery/processor-discovery.service.d.ts.map +1 -1
- package/dist/services/processor-discovery/processor-discovery.service.js +262 -14
- package/dist/services/processor-discovery/processor-discovery.service.js.map +1 -1
- package/dist/services/queue-bus/queue-bus.service.d.ts +126 -12
- package/dist/services/queue-bus/queue-bus.service.d.ts.map +1 -1
- package/dist/services/queue-bus/queue-bus.service.js +274 -22
- package/dist/services/queue-bus/queue-bus.service.js.map +1 -1
- package/dist/services/queue-events-manager/index.d.ts +2 -0
- package/dist/services/queue-events-manager/index.d.ts.map +1 -0
- package/dist/services/queue-events-manager/index.js +18 -0
- package/dist/services/queue-events-manager/index.js.map +1 -0
- package/dist/services/queue-events-manager/queue-events-manager.service.d.ts +124 -0
- package/dist/services/queue-events-manager/queue-events-manager.service.d.ts.map +1 -0
- package/dist/services/queue-events-manager/queue-events-manager.service.js +355 -0
- package/dist/services/queue-events-manager/queue-events-manager.service.js.map +1 -0
- package/dist/services/queue-manager/queue-manager.service.d.ts +15 -1
- package/dist/services/queue-manager/queue-manager.service.d.ts.map +1 -1
- package/dist/services/queue-manager/queue-manager.service.js +17 -3
- package/dist/services/queue-manager/queue-manager.service.js.map +1 -1
- package/dist/services/spawn-queue/index.d.ts +2 -0
- package/dist/services/spawn-queue/index.d.ts.map +1 -0
- package/dist/services/spawn-queue/index.js +18 -0
- package/dist/services/spawn-queue/index.js.map +1 -0
- package/dist/services/spawn-queue/spawn-queue.service.d.ts +119 -0
- package/dist/services/spawn-queue/spawn-queue.service.d.ts.map +1 -0
- package/dist/services/spawn-queue/spawn-queue.service.js +273 -0
- package/dist/services/spawn-queue/spawn-queue.service.js.map +1 -0
- package/dist/services/worker-manager/worker-manager.service.d.ts +59 -1
- package/dist/services/worker-manager/worker-manager.service.d.ts.map +1 -1
- package/dist/services/worker-manager/worker-manager.service.js +142 -12
- package/dist/services/worker-manager/worker-manager.service.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import Redis from 'ioredis';
|
|
3
|
+
import { IAtomicQueuesModuleConfig } from '../../domain';
|
|
4
|
+
import { WorkerManagerService } from '../worker-manager';
|
|
5
|
+
import { QueueEventsManagerService } from '../queue-events-manager';
|
|
6
|
+
/**
|
|
7
|
+
* Spawn job payload
|
|
8
|
+
*/
|
|
9
|
+
export interface ISpawnJobData {
|
|
10
|
+
entityType: string;
|
|
11
|
+
entityId: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Handler callback that the ProcessorDiscoveryService registers
|
|
15
|
+
* to actually create the entity worker on this pod.
|
|
16
|
+
*/
|
|
17
|
+
export type SpawnWorkerHandler = (entityType: string, entityId: string) => Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* SpawnQueueService
|
|
20
|
+
*
|
|
21
|
+
* Replaces the cron-based / service-queue-based worker creation model
|
|
22
|
+
* with a distributed spawn queue.
|
|
23
|
+
*
|
|
24
|
+
* Architecture:
|
|
25
|
+
* - One shared BullMQ queue: `{prefix}-spawn-queue`
|
|
26
|
+
* - EVERY pod runs a BullMQ Worker on this queue (concurrency: 1)
|
|
27
|
+
* - When QueueEventsManager detects a job for an entity with no worker,
|
|
28
|
+
* it enqueues a spawn-worker job here
|
|
29
|
+
* - BullMQ distributes spawn jobs round-robin across pods
|
|
30
|
+
* - Whichever pod picks up the job creates the entity worker locally
|
|
31
|
+
* - Duplicate protection: before creating, check if worker already exists
|
|
32
|
+
* (heartbeat key in Redis). If yes, skip. Race-safe because BullMQ
|
|
33
|
+
* guarantees only ONE worker processes each job.
|
|
34
|
+
*
|
|
35
|
+
* Benefits over the old cron approach:
|
|
36
|
+
* - Workers naturally distribute across all pods
|
|
37
|
+
* - No single leader / no single point of bottleneck
|
|
38
|
+
* - No distributed lock / leader election needed
|
|
39
|
+
* - Reactive (spawn on demand) rather than polling
|
|
40
|
+
*
|
|
41
|
+
* Idle cleanup:
|
|
42
|
+
* - Each pod runs a local interval that checks its OWN workers' idle time
|
|
43
|
+
* - Idle workers are closed directly (no cross-pod signaling needed)
|
|
44
|
+
*/
|
|
45
|
+
export declare class SpawnQueueService implements OnModuleInit, OnModuleDestroy {
|
|
46
|
+
private readonly redis;
|
|
47
|
+
private readonly config;
|
|
48
|
+
private readonly workerManager;
|
|
49
|
+
private readonly logger;
|
|
50
|
+
private readonly keyPrefix;
|
|
51
|
+
private readonly spawnQueueName;
|
|
52
|
+
private spawnQueue;
|
|
53
|
+
private spawnWorker;
|
|
54
|
+
private idleSweepInterval;
|
|
55
|
+
/** Handler registered by ProcessorDiscoveryService */
|
|
56
|
+
private spawnHandler;
|
|
57
|
+
/** Idle timeout per entity type (set by ProcessorDiscovery) */
|
|
58
|
+
private readonly idleTimeouts;
|
|
59
|
+
/** Default idle timeout in seconds */
|
|
60
|
+
private readonly defaultIdleTimeoutSeconds;
|
|
61
|
+
/** Idle sweep interval in ms */
|
|
62
|
+
private readonly idleSweepIntervalMs;
|
|
63
|
+
/** Reference to QueueEventsManager for hot-cache eviction on idle close */
|
|
64
|
+
private queueEventsManager;
|
|
65
|
+
constructor(redis: Redis, config: IAtomicQueuesModuleConfig, workerManager: WorkerManagerService);
|
|
66
|
+
/**
|
|
67
|
+
* Set the QueueEventsManagerService reference for hot-cache eviction.
|
|
68
|
+
* Called by ProcessorDiscoveryService to avoid circular dependency.
|
|
69
|
+
*/
|
|
70
|
+
setQueueEventsManager(manager: QueueEventsManagerService): void;
|
|
71
|
+
/**
|
|
72
|
+
* Register the handler that creates entity workers.
|
|
73
|
+
* Called by ProcessorDiscoveryService during init.
|
|
74
|
+
*/
|
|
75
|
+
registerSpawnHandler(handler: SpawnWorkerHandler): void;
|
|
76
|
+
/**
|
|
77
|
+
* Register idle timeout for an entity type.
|
|
78
|
+
*/
|
|
79
|
+
registerIdleTimeout(entityType: string, timeoutSeconds: number): void;
|
|
80
|
+
/**
|
|
81
|
+
* Initialize: create the spawn queue and start the spawn worker + idle sweep.
|
|
82
|
+
*/
|
|
83
|
+
onModuleInit(): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Enqueue a spawn-worker request.
|
|
86
|
+
* Called by QueueEventsManager when a job arrives for an entity with no worker.
|
|
87
|
+
*
|
|
88
|
+
* Uses deduplication: jobId = `spawn-{entityType}-{entityId}` so BullMQ
|
|
89
|
+
* will not create a duplicate if one is already queued/active.
|
|
90
|
+
*/
|
|
91
|
+
requestSpawn(entityType: string, entityId: string): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Handle a spawn job picked up by this pod's worker.
|
|
94
|
+
*/
|
|
95
|
+
private handleSpawnJob;
|
|
96
|
+
/**
|
|
97
|
+
* Direct spawn — bypasses the BullMQ queue entirely.
|
|
98
|
+
* Called by QueueEventsManagerService when it wins the atomic claim
|
|
99
|
+
* and wants to create the worker on this pod immediately.
|
|
100
|
+
*/
|
|
101
|
+
handleSpawnJobDirect(entityType: string, entityId: string): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Start the local idle sweep interval.
|
|
104
|
+
* Periodically checks all workers on THIS pod and closes idle ones.
|
|
105
|
+
*/
|
|
106
|
+
private startIdleSweep;
|
|
107
|
+
/**
|
|
108
|
+
* Sweep all workers on this pod, close any that are idle.
|
|
109
|
+
*/
|
|
110
|
+
private sweepIdleWorkers;
|
|
111
|
+
/**
|
|
112
|
+
* Extract entity type from worker name.
|
|
113
|
+
* Worker names follow pattern: {entityType}-{entityId}-worker
|
|
114
|
+
* e.g., "candy-abc123-worker" → "candy"
|
|
115
|
+
*/
|
|
116
|
+
private extractEntityTypeFromWorkerName;
|
|
117
|
+
onModuleDestroy(): Promise<void>;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=spawn-queue.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-queue.service.d.ts","sourceRoot":"","sources":["../../../src/services/spawn-queue/spawn-queue.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,YAAY,EACZ,eAAe,EAChB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,MAAM,SAAS,CAAC;AAC5B,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBACa,iBAAkB,YAAW,YAAY,EAAE,eAAe;IAyBtC,OAAO,CAAC,QAAQ,CAAC,KAAK;IAEnD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IA3BhC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsC;IAC7D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IAExC,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,iBAAiB,CAA+B;IAExD,sDAAsD;IACtD,OAAO,CAAC,YAAY,CAAmC;IAEvD,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;IAE/D,sCAAsC;IACtC,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAS;IAEnD,gCAAgC;IAChC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAE7C,2EAA2E;IAC3E,OAAO,CAAC,kBAAkB,CAA0C;gBAGpB,KAAK,EAAE,KAAK,EAEzC,MAAM,EAAE,yBAAyB,EACjC,aAAa,EAAE,oBAAoB;IAQtD;;;OAGG;IACH,qBAAqB,CAAC,OAAO,EAAE,yBAAyB,GAAG,IAAI;IAI/D;;;OAGG;IACH,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAKvD;;OAEG;IACH,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI;IAIrE;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IA0CnC;;;;;;OAMG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BvE;;OAEG;YACW,cAAc;IAwB5B;;;;OAIG;IACG,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc/E;;;OAGG;IACH,OAAO,CAAC,cAAc;IAgBtB;;OAEG;YACW,gBAAgB;IA+B9B;;;;OAIG;IACH,OAAO,CAAC,+BAA+B;IAejC,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;CAqBvC"}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
var SpawnQueueService_1;
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.SpawnQueueService = void 0;
|
|
20
|
+
const common_1 = require("@nestjs/common");
|
|
21
|
+
const bullmq_1 = require("bullmq");
|
|
22
|
+
const ioredis_1 = __importDefault(require("ioredis"));
|
|
23
|
+
const constants_1 = require("../constants");
|
|
24
|
+
const worker_manager_1 = require("../worker-manager");
|
|
25
|
+
/**
|
|
26
|
+
* SpawnQueueService
|
|
27
|
+
*
|
|
28
|
+
* Replaces the cron-based / service-queue-based worker creation model
|
|
29
|
+
* with a distributed spawn queue.
|
|
30
|
+
*
|
|
31
|
+
* Architecture:
|
|
32
|
+
* - One shared BullMQ queue: `{prefix}-spawn-queue`
|
|
33
|
+
* - EVERY pod runs a BullMQ Worker on this queue (concurrency: 1)
|
|
34
|
+
* - When QueueEventsManager detects a job for an entity with no worker,
|
|
35
|
+
* it enqueues a spawn-worker job here
|
|
36
|
+
* - BullMQ distributes spawn jobs round-robin across pods
|
|
37
|
+
* - Whichever pod picks up the job creates the entity worker locally
|
|
38
|
+
* - Duplicate protection: before creating, check if worker already exists
|
|
39
|
+
* (heartbeat key in Redis). If yes, skip. Race-safe because BullMQ
|
|
40
|
+
* guarantees only ONE worker processes each job.
|
|
41
|
+
*
|
|
42
|
+
* Benefits over the old cron approach:
|
|
43
|
+
* - Workers naturally distribute across all pods
|
|
44
|
+
* - No single leader / no single point of bottleneck
|
|
45
|
+
* - No distributed lock / leader election needed
|
|
46
|
+
* - Reactive (spawn on demand) rather than polling
|
|
47
|
+
*
|
|
48
|
+
* Idle cleanup:
|
|
49
|
+
* - Each pod runs a local interval that checks its OWN workers' idle time
|
|
50
|
+
* - Idle workers are closed directly (no cross-pod signaling needed)
|
|
51
|
+
*/
|
|
52
|
+
let SpawnQueueService = SpawnQueueService_1 = class SpawnQueueService {
|
|
53
|
+
constructor(redis, config, workerManager) {
|
|
54
|
+
this.redis = redis;
|
|
55
|
+
this.config = config;
|
|
56
|
+
this.workerManager = workerManager;
|
|
57
|
+
this.logger = new common_1.Logger(SpawnQueueService_1.name);
|
|
58
|
+
this.spawnQueue = null;
|
|
59
|
+
this.spawnWorker = null;
|
|
60
|
+
this.idleSweepInterval = null;
|
|
61
|
+
/** Handler registered by ProcessorDiscoveryService */
|
|
62
|
+
this.spawnHandler = null;
|
|
63
|
+
/** Idle timeout per entity type (set by ProcessorDiscovery) */
|
|
64
|
+
this.idleTimeouts = new Map();
|
|
65
|
+
/** Reference to QueueEventsManager for hot-cache eviction on idle close */
|
|
66
|
+
this.queueEventsManager = null;
|
|
67
|
+
this.keyPrefix = config.keyPrefix || 'aq';
|
|
68
|
+
this.spawnQueueName = `${this.keyPrefix}-spawn-queue`;
|
|
69
|
+
this.defaultIdleTimeoutSeconds = 15;
|
|
70
|
+
this.idleSweepIntervalMs = config.cronInterval ?? 5000;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Set the QueueEventsManagerService reference for hot-cache eviction.
|
|
74
|
+
* Called by ProcessorDiscoveryService to avoid circular dependency.
|
|
75
|
+
*/
|
|
76
|
+
setQueueEventsManager(manager) {
|
|
77
|
+
this.queueEventsManager = manager;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Register the handler that creates entity workers.
|
|
81
|
+
* Called by ProcessorDiscoveryService during init.
|
|
82
|
+
*/
|
|
83
|
+
registerSpawnHandler(handler) {
|
|
84
|
+
this.spawnHandler = handler;
|
|
85
|
+
this.logger.debug('Spawn handler registered');
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Register idle timeout for an entity type.
|
|
89
|
+
*/
|
|
90
|
+
registerIdleTimeout(entityType, timeoutSeconds) {
|
|
91
|
+
this.idleTimeouts.set(entityType, timeoutSeconds);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Initialize: create the spawn queue and start the spawn worker + idle sweep.
|
|
95
|
+
*/
|
|
96
|
+
async onModuleInit() {
|
|
97
|
+
// Create the spawn queue
|
|
98
|
+
this.spawnQueue = new bullmq_1.Queue(this.spawnQueueName, {
|
|
99
|
+
connection: this.redis.duplicate(),
|
|
100
|
+
defaultJobOptions: {
|
|
101
|
+
removeOnComplete: true,
|
|
102
|
+
removeOnFail: 100,
|
|
103
|
+
attempts: 2,
|
|
104
|
+
backoff: { type: 'fixed', delay: 500 },
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
// Create a BullMQ worker on this pod to consume spawn jobs
|
|
108
|
+
this.spawnWorker = new bullmq_1.Worker(this.spawnQueueName, async (job) => {
|
|
109
|
+
await this.handleSpawnJob(job);
|
|
110
|
+
}, {
|
|
111
|
+
connection: this.redis.duplicate(),
|
|
112
|
+
concurrency: 3, // Allow a few concurrent spawns
|
|
113
|
+
});
|
|
114
|
+
this.spawnWorker.on('ready', () => {
|
|
115
|
+
this.logger.log(`Spawn worker ready on this pod — listening to ${this.spawnQueueName}`);
|
|
116
|
+
});
|
|
117
|
+
this.spawnWorker.on('failed', (job, error) => {
|
|
118
|
+
this.logger.error(`Spawn job ${job?.id} failed: ${error.message}`);
|
|
119
|
+
});
|
|
120
|
+
// Start the local idle sweep
|
|
121
|
+
this.startIdleSweep();
|
|
122
|
+
this.logger.log('SpawnQueueService initialized');
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Enqueue a spawn-worker request.
|
|
126
|
+
* Called by QueueEventsManager when a job arrives for an entity with no worker.
|
|
127
|
+
*
|
|
128
|
+
* Uses deduplication: jobId = `spawn-{entityType}-{entityId}` so BullMQ
|
|
129
|
+
* will not create a duplicate if one is already queued/active.
|
|
130
|
+
*/
|
|
131
|
+
async requestSpawn(entityType, entityId) {
|
|
132
|
+
if (!this.spawnQueue) {
|
|
133
|
+
this.logger.warn('Spawn queue not initialized yet');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const jobId = `spawn-${entityType}-${entityId}`;
|
|
137
|
+
try {
|
|
138
|
+
await this.spawnQueue.add('spawn-worker', { entityType, entityId }, { jobId });
|
|
139
|
+
this.logger.debug(`Enqueued spawn request: ${entityType}/${entityId}`);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
// Duplicate job ID → already queued, that's fine
|
|
143
|
+
const msg = error.message;
|
|
144
|
+
if (msg.includes('duplicate')) {
|
|
145
|
+
this.logger.debug(`Spawn already queued for ${entityType}/${entityId}`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
this.logger.error(`Failed to enqueue spawn: ${msg}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Handle a spawn job picked up by this pod's worker.
|
|
154
|
+
*/
|
|
155
|
+
async handleSpawnJob(job) {
|
|
156
|
+
const { entityType, entityId } = job.data;
|
|
157
|
+
this.logger.log(`Processing spawn job: ${entityType}/${entityId} (job ${job.id})`);
|
|
158
|
+
if (!this.spawnHandler) {
|
|
159
|
+
this.logger.warn('No spawn handler registered — cannot create worker');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
await this.spawnHandler(entityType, entityId);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
this.logger.error(`Spawn handler failed for ${entityType}/${entityId}: ${error.message}`);
|
|
167
|
+
throw error; // Let BullMQ retry
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Direct spawn — bypasses the BullMQ queue entirely.
|
|
172
|
+
* Called by QueueEventsManagerService when it wins the atomic claim
|
|
173
|
+
* and wants to create the worker on this pod immediately.
|
|
174
|
+
*/
|
|
175
|
+
async handleSpawnJobDirect(entityType, entityId) {
|
|
176
|
+
if (!this.spawnHandler) {
|
|
177
|
+
this.logger.warn('No spawn handler registered — cannot create worker (direct)');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.logger.log(`Direct spawn: ${entityType}/${entityId} (no queue round-trip)`);
|
|
181
|
+
await this.spawnHandler(entityType, entityId);
|
|
182
|
+
}
|
|
183
|
+
// =========================================================================
|
|
184
|
+
// IDLE SWEEP — runs locally on each pod
|
|
185
|
+
// =========================================================================
|
|
186
|
+
/**
|
|
187
|
+
* Start the local idle sweep interval.
|
|
188
|
+
* Periodically checks all workers on THIS pod and closes idle ones.
|
|
189
|
+
*/
|
|
190
|
+
startIdleSweep() {
|
|
191
|
+
this.idleSweepInterval = setInterval(async () => {
|
|
192
|
+
try {
|
|
193
|
+
await this.sweepIdleWorkers();
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
this.logger.error(`Idle sweep error: ${error.message}`);
|
|
197
|
+
}
|
|
198
|
+
}, this.idleSweepIntervalMs);
|
|
199
|
+
this.logger.debug(`Idle sweep started (interval: ${this.idleSweepIntervalMs}ms)`);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Sweep all workers on this pod, close any that are idle.
|
|
203
|
+
*/
|
|
204
|
+
async sweepIdleWorkers() {
|
|
205
|
+
const localWorkers = await this.workerManager.getNodeWorkers();
|
|
206
|
+
if (localWorkers.length === 0)
|
|
207
|
+
return;
|
|
208
|
+
for (const workerName of localWorkers) {
|
|
209
|
+
// Skip the spawn worker itself
|
|
210
|
+
if (workerName.includes('spawn'))
|
|
211
|
+
continue;
|
|
212
|
+
// Determine idle timeout for this worker's entity type
|
|
213
|
+
const entityType = this.extractEntityTypeFromWorkerName(workerName);
|
|
214
|
+
const idleTimeout = entityType
|
|
215
|
+
? (this.idleTimeouts.get(entityType) ?? this.defaultIdleTimeoutSeconds)
|
|
216
|
+
: this.defaultIdleTimeoutSeconds;
|
|
217
|
+
const isIdle = await this.workerManager.isWorkerIdle(workerName, idleTimeout);
|
|
218
|
+
if (isIdle) {
|
|
219
|
+
const idleSeconds = await this.workerManager.getWorkerIdleSeconds(workerName);
|
|
220
|
+
this.logger.log(`[IdleSweep] Closing idle worker: ${workerName} (idle ${idleSeconds}s >= ${idleTimeout}s threshold)`);
|
|
221
|
+
// Evict from hot cache BEFORE closing so the next job triggers a fresh spawn
|
|
222
|
+
if (this.queueEventsManager) {
|
|
223
|
+
this.queueEventsManager.evictFromHotCache(workerName);
|
|
224
|
+
}
|
|
225
|
+
await this.workerManager.signalWorkerClose(workerName);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Extract entity type from worker name.
|
|
231
|
+
* Worker names follow pattern: {entityType}-{entityId}-worker
|
|
232
|
+
* e.g., "candy-abc123-worker" → "candy"
|
|
233
|
+
*/
|
|
234
|
+
extractEntityTypeFromWorkerName(workerName) {
|
|
235
|
+
// Worker names from config: e.g., "candy-{uuid}-worker"
|
|
236
|
+
// We need to match against registered entity types
|
|
237
|
+
for (const entityType of this.idleTimeouts.keys()) {
|
|
238
|
+
if (workerName.startsWith(`${entityType}-`)) {
|
|
239
|
+
return entityType;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
// =========================================================================
|
|
245
|
+
// CLEANUP
|
|
246
|
+
// =========================================================================
|
|
247
|
+
async onModuleDestroy() {
|
|
248
|
+
// Stop idle sweep
|
|
249
|
+
if (this.idleSweepInterval) {
|
|
250
|
+
clearInterval(this.idleSweepInterval);
|
|
251
|
+
this.idleSweepInterval = null;
|
|
252
|
+
}
|
|
253
|
+
// Close spawn worker
|
|
254
|
+
if (this.spawnWorker) {
|
|
255
|
+
await this.spawnWorker.close();
|
|
256
|
+
this.spawnWorker = null;
|
|
257
|
+
}
|
|
258
|
+
// Close spawn queue
|
|
259
|
+
if (this.spawnQueue) {
|
|
260
|
+
await this.spawnQueue.close();
|
|
261
|
+
this.spawnQueue = null;
|
|
262
|
+
}
|
|
263
|
+
this.logger.log('SpawnQueueService destroyed');
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
exports.SpawnQueueService = SpawnQueueService;
|
|
267
|
+
exports.SpawnQueueService = SpawnQueueService = SpawnQueueService_1 = __decorate([
|
|
268
|
+
(0, common_1.Injectable)(),
|
|
269
|
+
__param(0, (0, common_1.Inject)(constants_1.ATOMIC_QUEUES_REDIS)),
|
|
270
|
+
__param(1, (0, common_1.Inject)(constants_1.ATOMIC_QUEUES_CONFIG)),
|
|
271
|
+
__metadata("design:paramtypes", [ioredis_1.default, Object, worker_manager_1.WorkerManagerService])
|
|
272
|
+
], SpawnQueueService);
|
|
273
|
+
//# sourceMappingURL=spawn-queue.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-queue.service.js","sourceRoot":"","sources":["../../../src/services/spawn-queue/spawn-queue.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,6 +39,8 @@ export declare class WorkerManagerService implements IWorkerManager, OnModuleIni
|
|
|
39
39
|
private readonly workerStates;
|
|
40
40
|
private readonly heartbeatIntervals;
|
|
41
41
|
private readonly shutdownSubscriptions;
|
|
42
|
+
/** Tracks the last job completion time per worker (for idle detection) */
|
|
43
|
+
private readonly lastJobCompletedAt;
|
|
42
44
|
private subscriberClient;
|
|
43
45
|
private readonly keyPrefix;
|
|
44
46
|
constructor(redis: Redis, config: IAtomicQueuesModuleConfig);
|
|
@@ -58,8 +60,23 @@ export declare class WorkerManagerService implements IWorkerManager, OnModuleIni
|
|
|
58
60
|
createWorker(options: IWorkerCreationOptions): Promise<Worker>;
|
|
59
61
|
/**
|
|
60
62
|
* Check if a worker exists and is alive (has valid heartbeat).
|
|
63
|
+
* Uses a global alive key for O(1) lookup instead of KEYS scan.
|
|
64
|
+
* The alive key is maintained alongside the per-node heartbeat key.
|
|
61
65
|
*/
|
|
62
66
|
workerExists(workerName: string): Promise<boolean>;
|
|
67
|
+
/**
|
|
68
|
+
* Atomically claim a worker slot using SET NX.
|
|
69
|
+
* Returns true if this pod claimed the slot (and should create the worker).
|
|
70
|
+
* Returns false if another pod already claimed it.
|
|
71
|
+
* Uses a separate claim key (not the alive key) so that workerExists()
|
|
72
|
+
* doesn't see the claim as an existing worker before creation finishes.
|
|
73
|
+
*/
|
|
74
|
+
claimWorkerSlot(workerName: string, ttlSeconds?: number): Promise<boolean>;
|
|
75
|
+
/**
|
|
76
|
+
* Check if a worker exists on THIS node specifically.
|
|
77
|
+
* Use this when you need node-local checks (e.g., for cleanup).
|
|
78
|
+
*/
|
|
79
|
+
workerExistsOnThisNode(workerName: string): Promise<boolean>;
|
|
63
80
|
/**
|
|
64
81
|
* Get all running workers for the current node.
|
|
65
82
|
*/
|
|
@@ -99,6 +116,39 @@ export declare class WorkerManagerService implements IWorkerManager, OnModuleIni
|
|
|
99
116
|
* Remove worker heartbeat (mark as dead).
|
|
100
117
|
*/
|
|
101
118
|
removeWorkerHeartbeat(workerName: string): Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Mark that a worker has completed a job (resets idle counter).
|
|
121
|
+
* Called internally when job completes.
|
|
122
|
+
*/
|
|
123
|
+
markWorkerActive(workerName: string): void;
|
|
124
|
+
/**
|
|
125
|
+
* Get the idle seconds counter for a worker from Redis.
|
|
126
|
+
* This is incremented by the heartbeat and reset when a job completes.
|
|
127
|
+
*/
|
|
128
|
+
getWorkerIdleSeconds(workerName: string): Promise<number>;
|
|
129
|
+
/**
|
|
130
|
+
* Reset the idle counter for a worker (called when job completes).
|
|
131
|
+
*/
|
|
132
|
+
resetWorkerIdleCounter(workerName: string): Promise<void>;
|
|
133
|
+
/**
|
|
134
|
+
* Increment the idle counter for a worker (called by heartbeat).
|
|
135
|
+
* Returns the new idle seconds value.
|
|
136
|
+
*/
|
|
137
|
+
incrementWorkerIdleCounter(workerName: string, incrementBy?: number): Promise<number>;
|
|
138
|
+
/**
|
|
139
|
+
* Remove the idle counter for a worker (cleanup).
|
|
140
|
+
*/
|
|
141
|
+
removeWorkerIdleCounter(workerName: string): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* Check if a worker is idle based on threshold.
|
|
144
|
+
* @param workerName - Worker name
|
|
145
|
+
* @param thresholdSeconds - Idle threshold in seconds (default: 15)
|
|
146
|
+
*/
|
|
147
|
+
isWorkerIdle(workerName: string, thresholdSeconds?: number): Promise<boolean>;
|
|
148
|
+
/**
|
|
149
|
+
* Get the Redis key for a worker's idle counter.
|
|
150
|
+
*/
|
|
151
|
+
private getWorkerIdleKey;
|
|
102
152
|
/**
|
|
103
153
|
* Get the node ID for this instance.
|
|
104
154
|
*/
|
|
@@ -128,9 +178,16 @@ export declare class WorkerManagerService implements IWorkerManager, OnModuleIni
|
|
|
128
178
|
*/
|
|
129
179
|
private createSubscriberClient;
|
|
130
180
|
/**
|
|
131
|
-
* Get the Redis key for a worker's heartbeat.
|
|
181
|
+
* Get the Redis key for a worker's heartbeat (per-node).
|
|
132
182
|
*/
|
|
133
183
|
private getWorkerKey;
|
|
184
|
+
/**
|
|
185
|
+
* Get the global alive key for O(1) worker existence check.
|
|
186
|
+
* This key has the same TTL as the per-node heartbeat and is
|
|
187
|
+
* refreshed in the same pipeline. It replaces the O(N) KEYS scan
|
|
188
|
+
* with a single O(1) EXISTS call.
|
|
189
|
+
*/
|
|
190
|
+
private getGlobalAliveKey;
|
|
134
191
|
/**
|
|
135
192
|
* Get the shutdown channel for a worker.
|
|
136
193
|
*/
|
|
@@ -141,6 +198,7 @@ export declare class WorkerManagerService implements IWorkerManager, OnModuleIni
|
|
|
141
198
|
private mergeWorkerConfig;
|
|
142
199
|
/**
|
|
143
200
|
* Set up heartbeat interval for a worker.
|
|
201
|
+
* Also increments idle counter on each heartbeat tick.
|
|
144
202
|
*/
|
|
145
203
|
private setupHeartbeat;
|
|
146
204
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-manager.service.d.ts","sourceRoot":"","sources":["../../../src/services/worker-manager/worker-manager.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,EACZ,qBAAqB,EAEtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,EAAO,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,MAAM,SAAS,CAAC;AAE5B,OAAO,EACL,cAAc,EAEd,sBAAsB,EAEtB,yBAAyB,EAC1B,MAAM,cAAc,CAAC;AAGtB;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBACa,oBACX,YAAW,cAAc,EAAE,YAAY,EAAE,qBAAqB;
|
|
1
|
+
{"version":3,"file":"worker-manager.service.d.ts","sourceRoot":"","sources":["../../../src/services/worker-manager/worker-manager.service.ts"],"names":[],"mappings":"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"}
|