@jetit/publisher 5.4.1 → 5.5.1
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/package.json +1 -1
- package/src/lib/monitoring/collector.js +20 -4
- package/src/lib/redis/dlq.js +25 -20
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MetricsCollector = void 0;
|
|
4
|
+
const logger_1 = require("../redis/logger");
|
|
4
5
|
class MetricsCollector {
|
|
5
6
|
constructor(config, dlq) {
|
|
6
7
|
this.metrics = [];
|
|
@@ -116,19 +117,34 @@ class MetricsCollector {
|
|
|
116
117
|
if (keys.length > 0) {
|
|
117
118
|
for (const streamKey of keys) {
|
|
118
119
|
try {
|
|
120
|
+
// Verify if the key is a stream before proceeding
|
|
121
|
+
const keyType = await this.redisClient.type(streamKey);
|
|
122
|
+
if (keyType !== 'stream') {
|
|
123
|
+
continue; // Skip non-stream keys
|
|
124
|
+
}
|
|
119
125
|
// Get stream length and pending info
|
|
120
126
|
const streamLength = await this.redisClient.xlen(streamKey);
|
|
121
127
|
// Extract consumer group name from stream key (format: eventName:cg-serviceName)
|
|
122
|
-
const consumerGroup = streamKey.split(':')
|
|
128
|
+
const [eventName, consumerGroup] = streamKey.split(':');
|
|
129
|
+
if (!consumerGroup?.startsWith('cg-')) {
|
|
130
|
+
continue; // Skip if key format doesn't match expected pattern
|
|
131
|
+
}
|
|
123
132
|
// XPENDING returns [count, min-id, max-id, consumer-list]
|
|
124
|
-
|
|
125
|
-
|
|
133
|
+
let totalPending = 0;
|
|
134
|
+
try {
|
|
135
|
+
const pendingInfo = await this.redisClient.xpending(streamKey, consumerGroup);
|
|
136
|
+
totalPending = pendingInfo ? Number(pendingInfo[0]) : 0;
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
logger_1.PUBLISHER_LOGGER.error(`Error getting pending info for stream ${streamKey}:`, error);
|
|
140
|
+
// Continue with totalPending as 0 if XPENDING fails
|
|
141
|
+
}
|
|
126
142
|
const queueDepth = Math.max(0, streamLength - totalPending);
|
|
127
143
|
totalDepth += queueDepth;
|
|
128
144
|
individualDepths[streamKey] = queueDepth;
|
|
129
145
|
}
|
|
130
146
|
catch (error) {
|
|
131
|
-
|
|
147
|
+
logger_1.PUBLISHER_LOGGER.error(`Error processing key ${streamKey}:`, error);
|
|
132
148
|
continue;
|
|
133
149
|
}
|
|
134
150
|
}
|
package/src/lib/redis/dlq.js
CHANGED
|
@@ -18,7 +18,11 @@ class DeadLetterQueue {
|
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
const eventJson = JSON.stringify(event);
|
|
21
|
-
|
|
21
|
+
// Execute commands separately to avoid CROSSSLOT error in cluster mode
|
|
22
|
+
await Promise.all([
|
|
23
|
+
this.redisClient.hset(DLQ_HASH_KEY, event.eventId, eventJson),
|
|
24
|
+
this.redisClient.zadd(DLQ_ZSET_KEY, event.timestamp, event.eventId),
|
|
25
|
+
]);
|
|
22
26
|
await this.incrementRateLimit();
|
|
23
27
|
logger_1.PUBLISHER_LOGGER.log(`DLQ: Added event ${event.eventId} to Dead Letter Queue`);
|
|
24
28
|
}
|
|
@@ -69,8 +73,12 @@ class DeadLetterQueue {
|
|
|
69
73
|
}
|
|
70
74
|
async removeFromDLQ(eventId) {
|
|
71
75
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
// Execute commands separately to avoid CROSSSLOT error in cluster mode
|
|
77
|
+
const [hdelResult, zremResult] = await Promise.all([
|
|
78
|
+
this.redisClient.hdel(DLQ_HASH_KEY, eventId),
|
|
79
|
+
this.redisClient.zrem(DLQ_ZSET_KEY, eventId),
|
|
80
|
+
]);
|
|
81
|
+
if (hdelResult === 1 && zremResult === 1) {
|
|
74
82
|
logger_1.PUBLISHER_LOGGER.log(`DLQ: Successfully removed event ${eventId} from Dead Letter Queue`);
|
|
75
83
|
return true;
|
|
76
84
|
}
|
|
@@ -90,11 +98,11 @@ class DeadLetterQueue {
|
|
|
90
98
|
try {
|
|
91
99
|
const expiredEventIds = await this.redisClient.zrangebyscore(DLQ_ZSET_KEY, 0, cutoffTime);
|
|
92
100
|
if (expiredEventIds.length > 0) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
.hdel(DLQ_HASH_KEY, ...expiredEventIds)
|
|
96
|
-
.zremrangebyscore(DLQ_ZSET_KEY, 0, cutoffTime)
|
|
97
|
-
|
|
101
|
+
// Execute commands separately to avoid CROSSSLOT error in cluster mode
|
|
102
|
+
await Promise.all([
|
|
103
|
+
this.redisClient.hdel(DLQ_HASH_KEY, ...expiredEventIds),
|
|
104
|
+
this.redisClient.zremrangebyscore(DLQ_ZSET_KEY, 0, cutoffTime),
|
|
105
|
+
]);
|
|
98
106
|
}
|
|
99
107
|
logger_1.PUBLISHER_LOGGER.log(`DLQ: Cleaned up ${expiredEventIds.length} expired events from Dead Letter Queue`);
|
|
100
108
|
}
|
|
@@ -104,16 +112,9 @@ class DeadLetterQueue {
|
|
|
104
112
|
}
|
|
105
113
|
async getDLQStats() {
|
|
106
114
|
try {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
const [sizeResult, additionRateResult] = results;
|
|
112
|
-
if (sizeResult[0] || additionRateResult[0]) {
|
|
113
|
-
throw new Error('Error executing Redis commands');
|
|
114
|
-
}
|
|
115
|
-
const size = sizeResult[1];
|
|
116
|
-
const additionRate = parseInt(additionRateResult[1] || '0', 10);
|
|
115
|
+
// Execute commands separately to avoid CROSSSLOT error in cluster mode
|
|
116
|
+
const [size, additionRateStr] = await Promise.all([this.redisClient.zcard(DLQ_ZSET_KEY), this.redisClient.get(DLQ_RATE_LIMIT_KEY)]);
|
|
117
|
+
const additionRate = parseInt(additionRateStr || '0', 10);
|
|
117
118
|
return { size, additionRate };
|
|
118
119
|
}
|
|
119
120
|
catch (error) {
|
|
@@ -126,8 +127,12 @@ class DeadLetterQueue {
|
|
|
126
127
|
return parseInt(currentRate, 10) >= this.maxEventsThreshold;
|
|
127
128
|
}
|
|
128
129
|
async incrementRateLimit() {
|
|
129
|
-
|
|
130
|
-
await this.redisClient
|
|
130
|
+
// These commands operate on the same key so they can be combined in cluster mode
|
|
131
|
+
await this.redisClient
|
|
132
|
+
.multi()
|
|
133
|
+
.incr(DLQ_RATE_LIMIT_KEY)
|
|
134
|
+
.expire(DLQ_RATE_LIMIT_KEY, 60) // Reset rate limit after 1 minute
|
|
135
|
+
.exec();
|
|
131
136
|
}
|
|
132
137
|
}
|
|
133
138
|
exports.DeadLetterQueue = DeadLetterQueue;
|