()
| 105 | } |
| 106 | |
| 107 | export function bootWorkers() { |
| 108 | const enabledQueues = getEnabledQueues(); |
| 109 | |
| 110 | const workers: (Worker | GroupWorker<any>)[] = []; |
| 111 | const extraStops: Array<() => Promise<unknown>> = []; |
| 112 | |
| 113 | // Start event workers based on enabled queues. |
| 114 | // When Kafka is configured the producer routes every event to Kafka, so the |
| 115 | // GroupMQ event shards would only poll an empty queue — skip them entirely |
| 116 | // and let the Kafka consumer handle ingestion. |
| 117 | const eventQueuesToStart: number[] = []; |
| 118 | |
| 119 | if (isKafkaConfigured()) { |
| 120 | logger.info('Kafka is configured, skipping GroupMQ event workers'); |
| 121 | } else if (enabledQueues.includes('events')) { |
| 122 | // Start all event shards |
| 123 | for (let i = 0; i < EVENTS_GROUP_QUEUES_SHARDS; i++) { |
| 124 | eventQueuesToStart.push(i); |
| 125 | } |
| 126 | } else { |
| 127 | // Start specific event shards (events_0, events_1, etc.) |
| 128 | for (let i = 0; i < EVENTS_GROUP_QUEUES_SHARDS; i++) { |
| 129 | if (enabledQueues.includes(`events_${i}`)) { |
| 130 | eventQueuesToStart.push(i); |
| 131 | } |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | if (eventQueuesToStart.length > 0) { |
| 136 | enableEventsHeartbeat(); |
| 137 | } |
| 138 | |
| 139 | for (const index of eventQueuesToStart) { |
| 140 | const queue = eventsGroupQueues[index]; |
| 141 | if (!queue) { |
| 142 | continue; |
| 143 | } |
| 144 | |
| 145 | const queueName = `events_${index}`; |
| 146 | const concurrency = getConcurrencyFor( |
| 147 | queueName, |
| 148 | Number.parseInt(process.env.EVENT_JOB_CONCURRENCY || '10', 10) |
| 149 | ); |
| 150 | |
| 151 | const worker = new GroupWorker<EventsQueuePayloadIncomingEvent['payload']>({ |
| 152 | queue, |
| 153 | concurrency, |
| 154 | logger: process.env.NODE_ENV === 'production' ? queueLogger : undefined, |
| 155 | blockingTimeoutSec: Number.parseFloat( |
| 156 | process.env.EVENT_BLOCKING_TIMEOUT_SEC || '1' |
| 157 | ), |
| 158 | handler: async (job) => { |
| 159 | return await incomingEvent(job.data); |
| 160 | }, |
| 161 | }); |
| 162 | |
| 163 | // Consumer-loop heartbeat for the readiness probe. `completed` fires after |
| 164 | // each processed job; `drained` fires on each poll cycle that finds the |
no test coverage detected