@jetit/publisher 6.0.1 → 6.0.3
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
CHANGED
|
@@ -126,6 +126,8 @@ const config: Partial<IStreamsConfig> = {
|
|
|
126
126
|
maxStoredEvents: 5000,
|
|
127
127
|
},
|
|
128
128
|
maxPendingTasks: 1000, // Max concurrent pending retry tasks per event per consumer (default: 1000, 0 = unbounded)
|
|
129
|
+
enableMetrics: true, // Set to false to disable MetricsCollector and its periodic SCAN (default: true)
|
|
130
|
+
recurringCleanupInterval: 10000, // Interval (ms) for the recurring pending-retry timer (default: 10000)
|
|
129
131
|
};
|
|
130
132
|
|
|
131
133
|
const publisher = new Publisher('MyService', config);
|
|
@@ -359,7 +361,8 @@ The Circuit Breaker has three states:
|
|
|
359
361
|
- Retry logic with exponential backoff for failed operations
|
|
360
362
|
- Circuit Breaker to prevent overwhelming failed services
|
|
361
363
|
- Dead Letter Queue (DLQ) for handling subscription failures
|
|
362
|
-
- **
|
|
364
|
+
- **Disable Metrics Collection**: Set `enableMetrics: false` to skip `MetricsCollector` initialization entirely. The collector runs `SCAN *:cg-* COUNT 100` every 60 seconds for queue depth — disabling it eliminates this periodic Redis overhead. Applies to both `Publisher` and `PublisherLite`. When disabled, `getMetrics()` returns `[]` and `getLatestMetrics()` returns `null`.
|
|
365
|
+
- **Pending Retry Task Cap**: Limits the number of concurrent in-flight pending message retry tasks per event per consumer instance. Prevents unbounded memory growth and Redis saturation under sustained load. Configurable via `maxPendingTasks` (default: 1000). Set to `0` to disable the cap (unbounded, pre-6.0.1 behavior). When the cap is reached, a rate-limited warning is logged and skipped messages are retried on the next trigger (Pub/Sub notification or the 10-second cleanup timer).
|
|
363
366
|
- **Adaptive Redis Stream ID Generation (Publisher only)**: The default `Publisher` automatically switches to an optimized ID generation strategy using a Lua script when publishing to many consumer groups (>10 by default) or when ID conflicts are detected. This prevents `XADD` errors related to non-monotonic IDs in high-throughput scenarios. Configurable via `optimizationThreshold` and `optimizationDurationMs`. (`PublisherLite` does not include this complex logic).
|
|
364
367
|
|
|
365
368
|
## Cleanup and Graceful Shutdown
|
package/package.json
CHANGED
|
@@ -75,11 +75,13 @@ class StreamsLite {
|
|
|
75
75
|
}, cleanUpInterval);
|
|
76
76
|
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Clean Up process setup for ${cleanUpInterval} ms`);
|
|
77
77
|
this.dlq = new dlq_1.DeadLetterQueue(this.redisPublisher, config.dlqEventThreshold);
|
|
78
|
-
this.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
if (this.config.enableMetrics !== false) {
|
|
79
|
+
this.metricsCollector = new collector_1.MetricsCollector({
|
|
80
|
+
redisClient: this.redisPublisher,
|
|
81
|
+
collectionInterval: 60000,
|
|
82
|
+
retentionPeriod: 6 * 60 * 60 * 1000,
|
|
83
|
+
}, this.dlq);
|
|
84
|
+
}
|
|
83
85
|
this.duplicateChecker = new duplication_1.ContentBasedDeduplication(this.redisPublisher, this.config.duplicationCheckWindow);
|
|
84
86
|
this.circuitBreaker = new circuit_breaker_1.CircuitBreaker(this.config.circuitBreaker, this.redisPublisher);
|
|
85
87
|
if (this.config.circuitBreaker.enabled)
|
|
@@ -667,6 +669,8 @@ class StreamsLite {
|
|
|
667
669
|
* This will return you the stats of the publisher for the last 6 hours after cleaning
|
|
668
670
|
*/
|
|
669
671
|
async getMetrics(startTime, endTime) {
|
|
672
|
+
if (!this.metricsCollector)
|
|
673
|
+
return [];
|
|
670
674
|
return this.metricsCollector.getMetrics(startTime, endTime);
|
|
671
675
|
}
|
|
672
676
|
/**
|
|
@@ -674,6 +678,8 @@ class StreamsLite {
|
|
|
674
678
|
* This will return you the latest stats of the publisher
|
|
675
679
|
*/
|
|
676
680
|
async getLatestMetrics() {
|
|
681
|
+
if (!this.metricsCollector)
|
|
682
|
+
return null;
|
|
677
683
|
return this.metricsCollector.getLatestMetrics();
|
|
678
684
|
}
|
|
679
685
|
/**
|
package/src/lib/redis/streams.js
CHANGED
|
@@ -129,6 +129,8 @@ class Streams {
|
|
|
129
129
|
optimizationDurationMs: 2 * 60 * 1000, // 2 minutes
|
|
130
130
|
optimizationThreshold: 20, // Enable optimization for >20 consumer groups
|
|
131
131
|
maxPendingTasks: 1000,
|
|
132
|
+
enableMetrics: true,
|
|
133
|
+
recurringCleanupInterval: 10000, // 10 seconds
|
|
132
134
|
};
|
|
133
135
|
/** Initialise Config properties */
|
|
134
136
|
this.config = { ...this.config, ...this.DEFAULT_STREAMS_CONFIG, ...config };
|
|
@@ -144,11 +146,13 @@ class Streams {
|
|
|
144
146
|
}, cleanUpInterval);
|
|
145
147
|
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Clean Up process setup for ${cleanUpInterval} ms`);
|
|
146
148
|
this.dlq = new dlq_1.DeadLetterQueue(this.redisPublisher, config.dlqEventThreshold);
|
|
147
|
-
this.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
if (this.config.enableMetrics !== false) {
|
|
150
|
+
this.metricsCollector = new collector_1.MetricsCollector({
|
|
151
|
+
redisClient: this.redisPublisher,
|
|
152
|
+
collectionInterval: 60000,
|
|
153
|
+
retentionPeriod: 6 * 60 * 60 * 1000,
|
|
154
|
+
}, this.dlq);
|
|
155
|
+
}
|
|
152
156
|
this.duplicateChecker = new duplication_1.ContentBasedDeduplication(this.redisPublisher, this.config.duplicationCheckWindow);
|
|
153
157
|
this.circuitBreaker = new circuit_breaker_1.CircuitBreaker(this.config.circuitBreaker, this.redisPublisher);
|
|
154
158
|
if (this.config.circuitBreaker.enabled)
|
|
@@ -574,10 +578,20 @@ class Streams {
|
|
|
574
578
|
if (!isNewSubscription) {
|
|
575
579
|
return bs.asObservable().pipe((0, rxjs_1.skip)(1));
|
|
576
580
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
+
const cleanupInterval = this.config.recurringCleanupInterval ?? 10000; // 10 seconds
|
|
582
|
+
const timer = (0, rxjs_1.interval)(cleanupInterval).subscribe({
|
|
583
|
+
next: async () => {
|
|
584
|
+
try {
|
|
585
|
+
await processMessage(this.redisGroups, '0', new tracker_1.MetricsTracker(), false);
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
logger_1.PUBLISHER_LOGGER.error('PUBLISHER: Error in running recurring cleanup task:', error);
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
error: (error) => {
|
|
592
|
+
logger_1.PUBLISHER_LOGGER.error('PUBLISHER: Fatal error in cleanup timer:', error);
|
|
593
|
+
},
|
|
594
|
+
});
|
|
581
595
|
// Create observable with proper cleanup
|
|
582
596
|
const observable = bs.asObservable().pipe((0, rxjs_1.skip)(1), (0, rxjs_1.finalize)(() => {
|
|
583
597
|
timer.unsubscribe();
|
|
@@ -1032,6 +1046,8 @@ class Streams {
|
|
|
1032
1046
|
* This will return you the stats of the publisher for the last 6 hours after cleaning
|
|
1033
1047
|
*/
|
|
1034
1048
|
async getMetrics(startTime, endTime) {
|
|
1049
|
+
if (!this.metricsCollector)
|
|
1050
|
+
return [];
|
|
1035
1051
|
return this.metricsCollector.getMetrics(startTime, endTime);
|
|
1036
1052
|
}
|
|
1037
1053
|
/**
|
|
@@ -1039,6 +1055,8 @@ class Streams {
|
|
|
1039
1055
|
* This will return you the latest stats of the publisher
|
|
1040
1056
|
*/
|
|
1041
1057
|
async getLatestMetrics() {
|
|
1058
|
+
if (!this.metricsCollector)
|
|
1059
|
+
return null;
|
|
1042
1060
|
return this.metricsCollector.getLatestMetrics();
|
|
1043
1061
|
}
|
|
1044
1062
|
/**
|
package/src/lib/redis/types.d.ts
CHANGED
|
@@ -57,6 +57,8 @@ export interface IStreamsConfig {
|
|
|
57
57
|
optimizationDurationMs?: number;
|
|
58
58
|
optimizationThreshold?: number;
|
|
59
59
|
maxPendingTasks?: number;
|
|
60
|
+
enableMetrics?: boolean;
|
|
61
|
+
recurringCleanupInterval?: number;
|
|
60
62
|
}
|
|
61
63
|
export type TEventFilter<T> = (event: EventData<T, string>) => boolean;
|
|
62
64
|
export interface ISubscription<T, TName extends string = string> {
|