@koala42/redis-highway 0.1.10 → 0.2.0

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/dist/worker.js DELETED
@@ -1,170 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Worker = void 0;
4
- const events_1 = require("events");
5
- const lua_1 = require("./lua");
6
- const keys_1 = require("./keys");
7
- const stream_message_entity_1 = require("./stream-message-entity");
8
- const uuid_1 = require("uuid");
9
- class Worker {
10
- constructor(redis, groupName, streamName, concurrency = 1, MAX_RETRIES = 3, blockTimeMs = 2000, claimIntervalMs = 60000, minIdleTimeMs = 300000) {
11
- this.redis = redis;
12
- this.groupName = groupName;
13
- this.streamName = streamName;
14
- this.concurrency = concurrency;
15
- this.MAX_RETRIES = MAX_RETRIES;
16
- this.blockTimeMs = blockTimeMs;
17
- this.claimIntervalMs = claimIntervalMs;
18
- this.minIdleTimeMs = minIdleTimeMs;
19
- this.isRunning = false;
20
- this.activeCount = 0;
21
- this.events = new events_1.EventEmitter();
22
- this.consumerId = (0, uuid_1.v7)();
23
- this.events.setMaxListeners(100);
24
- this.keys = new keys_1.KeyManager(streamName);
25
- this.blockingRedis = this.redis.duplicate();
26
- }
27
- /**
28
- * Start worker
29
- * @returns
30
- */
31
- async start() {
32
- if (this.isRunning) {
33
- return;
34
- }
35
- this.isRunning = true;
36
- try {
37
- await this.redis.xgroup('CREATE', this.streamName, this.groupName, '0', 'MKSTREAM');
38
- }
39
- catch (e) {
40
- if (!e.message.includes('BUSYGROUP')) {
41
- throw e;
42
- }
43
- }
44
- this.fetchLoop();
45
- this.autoClaimLoop();
46
- }
47
- async stop() {
48
- this.isRunning = false;
49
- this.events.emit('job_finished');
50
- if (this.blockingRedis) {
51
- try {
52
- await this.blockingRedis.quit();
53
- }
54
- catch (e) { }
55
- }
56
- while (this.activeCount > 0) {
57
- await new Promise(resolve => setTimeout(resolve, 50));
58
- }
59
- }
60
- async autoClaimLoop() {
61
- while (this.isRunning) {
62
- try {
63
- await new Promise(resolve => setTimeout(resolve, this.claimIntervalMs));
64
- if (!this.isRunning) {
65
- break;
66
- }
67
- let cursor = '0-0';
68
- let continueClaiming = true;
69
- while (continueClaiming && this.isRunning) {
70
- const result = await this.redis.xautoclaim(this.streamName, this.groupName, this.consumerName(), this.minIdleTimeMs, cursor, 'COUNT', this.concurrency);
71
- if (!result) {
72
- continueClaiming = false;
73
- break;
74
- }
75
- const [nextCursor, messages] = result;
76
- cursor = nextCursor;
77
- if (messages && messages.length > 0) {
78
- console.log(`[${this.groupName}] Recovered ${messages.length} stuck messages`);
79
- for (const msg of messages) {
80
- this.spawnWorker(msg);
81
- }
82
- }
83
- else {
84
- continueClaiming = false;
85
- }
86
- if (nextCursor === '0-0') {
87
- continueClaiming = false;
88
- }
89
- }
90
- }
91
- catch (e) {
92
- if (this.isRunning) {
93
- console.error(`[${this.groupName}] auto claim err:`, e.message);
94
- }
95
- }
96
- }
97
- }
98
- async fetchLoop() {
99
- while (this.isRunning) {
100
- const freeSlots = this.concurrency - this.activeCount;
101
- if (freeSlots <= 0) {
102
- await new Promise((resolve) => this.events.once('job_finished', resolve));
103
- continue;
104
- }
105
- try {
106
- const results = await this.blockingRedis.xreadgroup('GROUP', this.groupName, this.consumerName(), 'COUNT', freeSlots, 'BLOCK', this.blockTimeMs, 'STREAMS', this.streamName, '>');
107
- if (results) {
108
- const messages = results[0][1];
109
- for (const msg of messages) {
110
- this.spawnWorker(msg);
111
- }
112
- }
113
- }
114
- catch (err) {
115
- console.error(`[${this.groupName}] Fetch Error:`, err);
116
- await new Promise((resolve) => setTimeout(resolve, 1000));
117
- }
118
- }
119
- }
120
- spawnWorker(msg) {
121
- this.activeCount++;
122
- this.processInternal(msg).finally(() => {
123
- this.activeCount--;
124
- this.events.emit('job_finished');
125
- });
126
- }
127
- async processInternal(msg) {
128
- const streamMessage = new stream_message_entity_1.StreamMessageEntity(msg);
129
- if (!streamMessage.routes.includes(this.groupName)) {
130
- await this.redis.xack(this.streamName, this.groupName, streamMessage.streamMessageId);
131
- return;
132
- }
133
- try {
134
- await this.process(streamMessage.data);
135
- await this.finalize(streamMessage.messageUuid, streamMessage.streamMessageId);
136
- }
137
- catch (err) {
138
- console.error(`[${this.groupName}] Job failed ${streamMessage.messageUuid}`, err);
139
- await this.handleFailure(streamMessage.messageUuid, streamMessage.streamMessageId, streamMessage.retryCount, err.message, streamMessage.data);
140
- }
141
- }
142
- async handleFailure(uuid, msgId, currentRetries, errorMsg, payloadData) {
143
- // Ack
144
- await this.redis.xack(this.streamName, this.groupName, msgId);
145
- const payloadString = payloadData ? JSON.stringify(payloadData) : '';
146
- if (currentRetries < this.MAX_RETRIES && payloadData) {
147
- console.log(`[${this.groupName}] Retrying job ${uuid} (Attempt ${currentRetries + 1}/${this.MAX_RETRIES})`);
148
- const pipeline = this.redis.pipeline();
149
- pipeline.xadd(this.streamName, '*', 'id', uuid, 'target', this.groupName, 'retryCount', currentRetries + 1, 'data', payloadString);
150
- await pipeline.exec();
151
- }
152
- else {
153
- console.error(`[${this.groupName}] Job ${uuid} run outof retries. Moving to DLQ`);
154
- await this.redis.xadd(this.keys.getDlqStreamKey(), '*', 'id', uuid, 'group', this.groupName, 'error', errorMsg, 'payload', payloadString, 'failedAt', Date.now());
155
- await this.finalize(uuid, msgId);
156
- }
157
- }
158
- async finalize(messageUuid, msgId) {
159
- const timestamp = Date.now();
160
- const statusKey = this.keys.getJobStatusKey(messageUuid);
161
- const dataKey = this.keys.getJobDataKey(messageUuid);
162
- const throughputKey = this.keys.getThroughputKey(this.groupName, timestamp);
163
- const totalKey = this.keys.getTotalKey(this.groupName);
164
- await this.redis.eval(lua_1.LUA_MARK_DONE, 5, statusKey, this.streamName, this.groupName, throughputKey, totalKey, this.groupName, timestamp, msgId);
165
- }
166
- consumerName() {
167
- return `${this.groupName}-${process.pid}-${this.consumerId}`;
168
- }
169
- }
170
- exports.Worker = Worker;
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes