queen-mq 0.4.0 → 0.6.4
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 +520 -273
- package/{client-js/client-v2 → client-v2}/Queen.js +38 -0
- package/{client-js/client-v2 → client-v2}/README.md +61 -8
- package/package.json +5 -8
- package/{client-js/test-v2 → test-v2}/GETTING_STARTED.md +27 -0
- package/test-v2/README_SUBSCRIPTION_TESTS.md +201 -0
- package/{client-js/test-v2 → test-v2}/consume.js +10 -0
- package/{client-js/test-v2 → test-v2}/dlq.js +1 -1
- package/{client-js/test-v2 → test-v2}/load.js +2 -0
- package/{client-js/test-v2 → test-v2}/subscription.js +198 -18
- package/{client-js/test-v2 → test-v2}/transaction.js +66 -4
- package/LICENSE.md +0 -202
- package/client-js/benchmark/consumer.js +0 -209
- package/client-js/benchmark/consumer_multi.js +0 -216
- package/client-js/benchmark/producer.js +0 -80
- package/client-js/benchmark/producer_multi.js +0 -115
- /package/{client-js/client-v2 → client-v2}/LOGGING.md +0 -0
- /package/{client-js/client-v2 → client-v2}/buffer/BufferManager.js +0 -0
- /package/{client-js/client-v2 → client-v2}/buffer/MessageBuffer.js +0 -0
- /package/{client-js/client-v2 → client-v2}/builders/QueueBuilder.js +0 -0
- /package/{client-js/client-v2 → client-v2}/builders/TransactionBuilder.js +0 -0
- /package/{client-js/client-v2 → client-v2}/consumer/ConsumerManager.js +0 -0
- /package/{client-js/client-v2 → client-v2}/http/HttpClient.js +0 -0
- /package/{client-js/client-v2 → client-v2}/http/LoadBalancer.js +0 -0
- /package/{client-js/client-v2 → client-v2}/index.js +0 -0
- /package/{client-js/client-v2 → client-v2}/stream/StreamBuilder.js +0 -0
- /package/{client-js/client-v2 → client-v2}/stream/StreamConsumer.js +0 -0
- /package/{client-js/client-v2 → client-v2}/stream/Window.js +0 -0
- /package/{client-js/client-v2 → client-v2}/utils/defaults.js +0 -0
- /package/{client-js/client-v2 → client-v2}/utils/logger.js +0 -0
- /package/{client-js/client-v2 → client-v2}/utils/validation.js +0 -0
- /package/{client-js/test-v2 → test-v2}/AI_TEST_SUMMARY.md +0 -0
- /package/{client-js/test-v2 → test-v2}/MAINTENANCE_TEST.md +0 -0
- /package/{client-js/test-v2 → test-v2}/ai_buffering.js +0 -0
- /package/{client-js/test-v2 → test-v2}/ai_error_handling.js +0 -0
- /package/{client-js/test-v2 → test-v2}/ai_lease_renewal.js +0 -0
- /package/{client-js/test-v2 → test-v2}/ai_mixed_scenarios.js +0 -0
- /package/{client-js/test-v2 → test-v2}/ai_priority.js +0 -0
- /package/{client-js/test-v2 → test-v2}/ai_resources.js +0 -0
- /package/{client-js/test-v2 → test-v2}/ai_ttl_retention.js +0 -0
- /package/{client-js/test-v2 → test-v2}/complete.js +0 -0
- /package/{client-js/test-v2 → test-v2}/maintenance.js +0 -0
- /package/{client-js/test-v2 → test-v2}/pop.js +0 -0
- /package/{client-js/test-v2 → test-v2}/push.js +0 -0
- /package/{client-js/test-v2 → test-v2}/queue.js +0 -0
- /package/{client-js/test-v2 → test-v2}/retention.js +0 -0
- /package/{client-js/test-v2 → test-v2}/run.js +0 -0
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import { Queen } from '../client/client.js';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
|
|
4
|
-
const QUEUE_NAME = 'benchmark-queue-001';
|
|
5
|
-
const NUMBER_OF_CONSUMERS = 10;
|
|
6
|
-
const STARTING_PARTITION = parseInt(process.argv[2]) || 0;
|
|
7
|
-
const BATCH_SIZE = 10000;
|
|
8
|
-
const CONSUME_MODE = null//'partition';
|
|
9
|
-
|
|
10
|
-
// Global metrics tracking
|
|
11
|
-
const metrics = {
|
|
12
|
-
startTime: null,
|
|
13
|
-
endTime: null,
|
|
14
|
-
totalProcessed: 0,
|
|
15
|
-
consumers: [],
|
|
16
|
-
batchTimes: [],
|
|
17
|
-
ackTimes: [],
|
|
18
|
-
popTimes: [],
|
|
19
|
-
firstMessageTime: null,
|
|
20
|
-
lastMessageTime: null
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
async function consumer(consumerId, partition) {
|
|
24
|
-
const q = new Queen({
|
|
25
|
-
baseUrls: [ 'http://localhost:6632'],
|
|
26
|
-
timeout: 30000
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const consumerMetrics = {
|
|
30
|
-
consumerId,
|
|
31
|
-
partition,
|
|
32
|
-
messagesProcessed: 0,
|
|
33
|
-
batchesProcessed: 0,
|
|
34
|
-
batchTimes: [],
|
|
35
|
-
ackTimes: [],
|
|
36
|
-
popTimes: [],
|
|
37
|
-
startTime: Date.now(),
|
|
38
|
-
endTime: null
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const target = CONSUME_MODE === 'partition' ? `${QUEUE_NAME}/${partition}` : `${QUEUE_NAME}`;
|
|
42
|
-
|
|
43
|
-
metrics.consumers.push(consumerMetrics);
|
|
44
|
-
if (target === `${QUEUE_NAME}`) {
|
|
45
|
-
console.log(`[Consumer ${consumerId}] Started, consuming all partitions`);
|
|
46
|
-
} else {
|
|
47
|
-
console.log(`[Consumer ${consumerId}] Started, consuming partition ${partition}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
for await (const messages of q.takeBatch(target, { //`namespace:benchmark`,
|
|
52
|
-
wait: true,
|
|
53
|
-
timeout: 30000,
|
|
54
|
-
batch: BATCH_SIZE,
|
|
55
|
-
idleTimeout: 5000
|
|
56
|
-
})) {
|
|
57
|
-
if (!metrics.firstMessageTime) {
|
|
58
|
-
metrics.firstMessageTime = Date.now();
|
|
59
|
-
}
|
|
60
|
-
metrics.lastMessageTime = Date.now();
|
|
61
|
-
const batchStartTime = Date.now();
|
|
62
|
-
const popTime = batchStartTime - (consumerMetrics.endTime || consumerMetrics.startTime);
|
|
63
|
-
consumerMetrics.popTimes.push(popTime);
|
|
64
|
-
metrics.popTimes.push(popTime);
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
// Validate message ordering
|
|
68
|
-
const partitionLastIds = new Map();
|
|
69
|
-
|
|
70
|
-
for (const message of messages) {
|
|
71
|
-
if (!message.data || typeof message.data.id !== 'number') {
|
|
72
|
-
throw new Error(`Message missing or invalid data.id: ${JSON.stringify(message)}`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const partition = message.partition || 'default';
|
|
76
|
-
const messageId = message.data.id;
|
|
77
|
-
|
|
78
|
-
if (partitionLastIds.has(partition)) {
|
|
79
|
-
const lastId = partitionLastIds.get(partition);
|
|
80
|
-
if (messageId !== lastId + 1) {
|
|
81
|
-
console.log(message.partition, message);
|
|
82
|
-
throw new Error(`Message ordering violation in partition '${partition}': expected id ${lastId + 1}, got ${messageId}`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
partitionLastIds.set(partition, messageId);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Acknowledge all messages
|
|
90
|
-
const ackStartTime = Date.now();
|
|
91
|
-
await q.ack(messages);
|
|
92
|
-
const ackTime = Date.now() - ackStartTime;
|
|
93
|
-
consumerMetrics.ackTimes.push(ackTime);
|
|
94
|
-
metrics.ackTimes.push(ackTime);
|
|
95
|
-
|
|
96
|
-
const batchEndTime = Date.now();
|
|
97
|
-
const batchDuration = batchEndTime - batchStartTime;
|
|
98
|
-
|
|
99
|
-
consumerMetrics.messagesProcessed += messages.length;
|
|
100
|
-
consumerMetrics.batchesProcessed++;
|
|
101
|
-
consumerMetrics.batchTimes.push(batchDuration);
|
|
102
|
-
consumerMetrics.endTime = batchEndTime;
|
|
103
|
-
|
|
104
|
-
metrics.totalProcessed += messages.length;
|
|
105
|
-
metrics.batchTimes.push(batchDuration);
|
|
106
|
-
|
|
107
|
-
const overallDuration = (batchEndTime - metrics.startTime) / 1000;
|
|
108
|
-
const currentRate = (metrics.totalProcessed / overallDuration).toFixed(0);
|
|
109
|
-
|
|
110
|
-
console.log(new Date().toISOString(), `[C${consumerId}] Batch: ${messages.length} msgs | Time: ${(batchDuration/1000).toFixed(3)}s (pop:${(popTime/1000).toFixed(3)}s ack:${(ackTime/1000).toFixed(3)}s) | Total: ${metrics.totalProcessed} | Rate: ${currentRate} msg/s`);
|
|
111
|
-
metrics.lastMessageTime = Date.now();
|
|
112
|
-
} catch (error) {
|
|
113
|
-
console.error(new Date().toISOString(), `[Consumer ${consumerId}] Error processing batch:`, error);
|
|
114
|
-
await q.ack(messages, false, { error: error.message });
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
} catch (error) {
|
|
118
|
-
console.error(`[Consumer ${consumerId}] Fatal error:`, error);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
consumerMetrics.endTime = Date.now();
|
|
122
|
-
console.log(`[Consumer ${consumerId}] Finished. Processed ${consumerMetrics.messagesProcessed} messages in ${consumerMetrics.batchesProcessed} batches`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Start metrics
|
|
126
|
-
metrics.startTime = Date.now();
|
|
127
|
-
|
|
128
|
-
// Launch all consumers
|
|
129
|
-
const consumerPromises = [];
|
|
130
|
-
//
|
|
131
|
-
for (let i = 0; i < NUMBER_OF_CONSUMERS; i++) {
|
|
132
|
-
consumerPromises.push(consumer(i, i ));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Wait for all consumers to complete
|
|
136
|
-
await Promise.all(consumerPromises);
|
|
137
|
-
metrics.endTime = Date.now();
|
|
138
|
-
|
|
139
|
-
// Calculate actual processing time (excluding idle timeout)
|
|
140
|
-
// Find the time when the last batch was actually completed
|
|
141
|
-
let lastBatchCompletedTime = metrics.startTime;
|
|
142
|
-
metrics.consumers.forEach(c => {
|
|
143
|
-
if (c.endTime && c.batchesProcessed > 0) {
|
|
144
|
-
// c.endTime is when consumer exited (after idle timeout)
|
|
145
|
-
// Actual last batch time = c.endTime - (idle timeout we didn't track)
|
|
146
|
-
// Better: use the sum of all processing times
|
|
147
|
-
const totalBatchTime = c.batchTimes.reduce((a,b)=>a+b,0);
|
|
148
|
-
const totalPopTime = c.popTimes.reduce((a,b)=>a+b,0);
|
|
149
|
-
const actualEndTime = c.startTime + totalBatchTime + totalPopTime;
|
|
150
|
-
if (actualEndTime > lastBatchCompletedTime) {
|
|
151
|
-
lastBatchCompletedTime = actualEndTime;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
const totalDuration = (metrics.endTime - metrics.startTime) / 1000;
|
|
157
|
-
const actualProcessingDuration = (lastBatchCompletedTime - metrics.startTime) / 1000;
|
|
158
|
-
const avgThroughput = (metrics.totalProcessed / actualProcessingDuration).toFixed(0);
|
|
159
|
-
|
|
160
|
-
console.log('\n' + '='.repeat(80));
|
|
161
|
-
console.log('📊 BENCHMARK RESULTS');
|
|
162
|
-
console.log('='.repeat(80));
|
|
163
|
-
console.log(`Total Messages Processed: ${metrics.totalProcessed.toLocaleString()}`);
|
|
164
|
-
console.log(`Total Wall Clock Time: ${totalDuration.toFixed(2)}s (includes idle timeout)`);
|
|
165
|
-
console.log(`Actual Processing Time: ${actualProcessingDuration.toFixed(2)}s`);
|
|
166
|
-
console.log(`Idle/Wait Time: ${(totalDuration - actualProcessingDuration).toFixed(2)}s`);
|
|
167
|
-
console.log(`Average Throughput: ${avgThroughput} msg/s (based on processing time)`);
|
|
168
|
-
console.log(`Number of Consumers: ${NUMBER_OF_CONSUMERS}`);
|
|
169
|
-
console.log(`Total Batches: ${metrics.batchTimes.length}`);
|
|
170
|
-
console.log(`Total Time: ${metrics.lastMessageTime - metrics.firstMessageTime}ms`);
|
|
171
|
-
console.log(`Total msg/s: ${metrics.totalProcessed / (metrics.lastMessageTime - metrics.firstMessageTime) * 1000} msg/s`);
|
|
172
|
-
|
|
173
|
-
if (metrics.batchTimes.length > 0) {
|
|
174
|
-
const avgBatchTime = (metrics.batchTimes.reduce((a, b) => a + b, 0) / metrics.batchTimes.length / 1000).toFixed(3);
|
|
175
|
-
const minBatchTime = (Math.min(...metrics.batchTimes) / 1000).toFixed(3);
|
|
176
|
-
const maxBatchTime = (Math.max(...metrics.batchTimes) / 1000).toFixed(3);
|
|
177
|
-
|
|
178
|
-
console.log(`\nBatch Times:`);
|
|
179
|
-
console.log(` Average: ${avgBatchTime}s`);
|
|
180
|
-
console.log(` Min: ${minBatchTime}s`);
|
|
181
|
-
console.log(` Max: ${maxBatchTime}s`);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (metrics.popTimes.length > 0) {
|
|
185
|
-
const avgPopTime = (metrics.popTimes.reduce((a, b) => a + b, 0) / metrics.popTimes.length / 1000).toFixed(3);
|
|
186
|
-
console.log(`\nAverage POP Time: ${avgPopTime}s`);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (metrics.ackTimes.length > 0) {
|
|
190
|
-
const avgAckTime = (metrics.ackTimes.reduce((a, b) => a + b, 0) / metrics.ackTimes.length / 1000).toFixed(3);
|
|
191
|
-
console.log(`Average ACK Time: ${avgAckTime}s`);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
console.log(`\nPer-Consumer Stats:`);
|
|
195
|
-
metrics.consumers.forEach(c => {
|
|
196
|
-
// Calculate actual processing time (sum of all batch times + pop times)
|
|
197
|
-
const totalBatchTime = c.batchTimes.reduce((a,b)=>a+b,0);
|
|
198
|
-
const totalPopTime = c.popTimes.reduce((a,b)=>a+b,0);
|
|
199
|
-
const actualProcessingTime = (totalBatchTime + totalPopTime) / 1000;
|
|
200
|
-
|
|
201
|
-
const wallClockTime = ((c.endTime - c.startTime) / 1000).toFixed(2);
|
|
202
|
-
const rate = (c.messagesProcessed / actualProcessingTime).toFixed(0);
|
|
203
|
-
const avgBatch = c.batchTimes.length > 0 ? (totalBatchTime / c.batchTimes.length / 1000).toFixed(3) : 'N/A';
|
|
204
|
-
const idleTime = (wallClockTime - actualProcessingTime).toFixed(2);
|
|
205
|
-
|
|
206
|
-
console.log(` [C${c.consumerId}] ${c.messagesProcessed.toLocaleString()} msgs | Active: ${actualProcessingTime.toFixed(2)}s (${rate} msg/s) | Idle: ${idleTime}s | ${c.batchesProcessed} batches | avg: ${avgBatch}s`);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
console.log('='.repeat(80));
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import { Queen } from '../client/client.js';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
|
|
4
|
-
const NAMESPACE = 'benchmark';
|
|
5
|
-
const NUMBER_OF_CONSUMERS = 10;
|
|
6
|
-
const BATCH_SIZE = 1;
|
|
7
|
-
|
|
8
|
-
// Global metrics tracking
|
|
9
|
-
const metrics = {
|
|
10
|
-
startTime: null,
|
|
11
|
-
endTime: null,
|
|
12
|
-
totalProcessed: 0,
|
|
13
|
-
consumers: [],
|
|
14
|
-
batchTimes: [],
|
|
15
|
-
ackTimes: [],
|
|
16
|
-
popTimes: [],
|
|
17
|
-
firstMessageTime: null,
|
|
18
|
-
lastMessageTime: null
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
async function consumer(consumerId) {
|
|
22
|
-
const q = new Queen({
|
|
23
|
-
baseUrls: ['http://localhost:6632'],
|
|
24
|
-
timeout: 30000
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const consumerMetrics = {
|
|
28
|
-
consumerId,
|
|
29
|
-
messagesProcessed: 0,
|
|
30
|
-
batchesProcessed: 0,
|
|
31
|
-
batchTimes: [],
|
|
32
|
-
ackTimes: [],
|
|
33
|
-
popTimes: [],
|
|
34
|
-
startTime: Date.now(),
|
|
35
|
-
endTime: null,
|
|
36
|
-
queuesConsumed: new Set(),
|
|
37
|
-
partitionsConsumed: new Set()
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const target = `namespace:${NAMESPACE}`;
|
|
41
|
-
|
|
42
|
-
metrics.consumers.push(consumerMetrics);
|
|
43
|
-
console.log(`[Consumer ${consumerId}] Started, consuming from namespace '${NAMESPACE}'`);
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
for await (const messages of q.takeBatch(target, {
|
|
47
|
-
wait: true,
|
|
48
|
-
timeout: 15000,
|
|
49
|
-
batch: BATCH_SIZE,
|
|
50
|
-
idleTimeout: 5000
|
|
51
|
-
})) {
|
|
52
|
-
if (!metrics.firstMessageTime) {
|
|
53
|
-
metrics.firstMessageTime = Date.now();
|
|
54
|
-
}
|
|
55
|
-
metrics.lastMessageTime = Date.now();
|
|
56
|
-
const batchStartTime = Date.now();
|
|
57
|
-
const popTime = batchStartTime - (consumerMetrics.endTime || consumerMetrics.startTime);
|
|
58
|
-
consumerMetrics.popTimes.push(popTime);
|
|
59
|
-
metrics.popTimes.push(popTime);
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
// Track which queues and partitions this consumer is consuming from
|
|
63
|
-
for (const message of messages) {
|
|
64
|
-
if (message.queue) consumerMetrics.queuesConsumed.add(message.queue);
|
|
65
|
-
if (message.partition !== undefined) consumerMetrics.partitionsConsumed.add(`${message.queue}:${message.partition}`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Validate message ordering per queue/partition
|
|
69
|
-
const queuePartitionLastIds = new Map();
|
|
70
|
-
|
|
71
|
-
for (const message of messages) {
|
|
72
|
-
if (!message.data || typeof message.data.id !== 'number') {
|
|
73
|
-
throw new Error(`Message missing or invalid data.id: ${JSON.stringify(message)}`);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Use queue and partition to ensure proper ordering within each queue/partition
|
|
77
|
-
const queuePartitionKey = `${message.queue || 'default'}:${message.partition || 'default'}`;
|
|
78
|
-
const messageId = message.data.id;
|
|
79
|
-
|
|
80
|
-
if (queuePartitionLastIds.has(queuePartitionKey)) {
|
|
81
|
-
const lastId = queuePartitionLastIds.get(queuePartitionKey);
|
|
82
|
-
if (messageId !== lastId + 1) {
|
|
83
|
-
console.log(`Queue: ${message.queue}, Partition: ${message.partition}, Message:`, message);
|
|
84
|
-
throw new Error(`Message ordering violation in '${queuePartitionKey}': expected id ${lastId + 1}, got ${messageId}`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
queuePartitionLastIds.set(queuePartitionKey, messageId);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Acknowledge all messages
|
|
92
|
-
const ackStartTime = Date.now();
|
|
93
|
-
await q.ack(messages);
|
|
94
|
-
const ackTime = Date.now() - ackStartTime;
|
|
95
|
-
consumerMetrics.ackTimes.push(ackTime);
|
|
96
|
-
metrics.ackTimes.push(ackTime);
|
|
97
|
-
|
|
98
|
-
const batchEndTime = Date.now();
|
|
99
|
-
const batchDuration = batchEndTime - batchStartTime;
|
|
100
|
-
|
|
101
|
-
consumerMetrics.messagesProcessed += messages.length;
|
|
102
|
-
consumerMetrics.batchesProcessed++;
|
|
103
|
-
consumerMetrics.batchTimes.push(batchDuration);
|
|
104
|
-
consumerMetrics.endTime = batchEndTime;
|
|
105
|
-
|
|
106
|
-
metrics.totalProcessed += messages.length;
|
|
107
|
-
metrics.batchTimes.push(batchDuration);
|
|
108
|
-
|
|
109
|
-
const overallDuration = (batchEndTime - metrics.startTime) / 1000;
|
|
110
|
-
const currentRate = (metrics.totalProcessed / overallDuration).toFixed(0);
|
|
111
|
-
|
|
112
|
-
console.log(new Date().toISOString(), `[C${consumerId}] Batch: ${messages.length} msgs | Time: ${(batchDuration/1000).toFixed(3)}s (pop:${(popTime/1000).toFixed(3)}s ack:${(ackTime/1000).toFixed(3)}s) | Total: ${metrics.totalProcessed} | Rate: ${currentRate} msg/s`);
|
|
113
|
-
metrics.lastMessageTime = Date.now();
|
|
114
|
-
} catch (error) {
|
|
115
|
-
console.error(new Date().toISOString(), `[Consumer ${consumerId}] Error processing batch:`, error);
|
|
116
|
-
await q.ack(messages, false, { error: error.message });
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
} catch (error) {
|
|
120
|
-
console.error(`[Consumer ${consumerId}] Fatal error:`, error);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
consumerMetrics.endTime = Date.now();
|
|
124
|
-
console.log(`[Consumer ${consumerId}] Finished. Processed ${consumerMetrics.messagesProcessed} messages in ${consumerMetrics.batchesProcessed} batches`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Start metrics
|
|
128
|
-
metrics.startTime = Date.now();
|
|
129
|
-
|
|
130
|
-
// Launch all consumers (they will all consume from the namespace)
|
|
131
|
-
console.log(`Launching ${NUMBER_OF_CONSUMERS} consumers for namespace '${NAMESPACE}'...\n`);
|
|
132
|
-
const consumerPromises = [];
|
|
133
|
-
for (let i = 0; i < NUMBER_OF_CONSUMERS; i++) {
|
|
134
|
-
consumerPromises.push(consumer(i));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Wait for all consumers to complete
|
|
138
|
-
await Promise.all(consumerPromises);
|
|
139
|
-
metrics.endTime = Date.now();
|
|
140
|
-
|
|
141
|
-
// Calculate actual processing time (excluding idle timeout)
|
|
142
|
-
// Find the time when the last batch was actually completed
|
|
143
|
-
let lastBatchCompletedTime = metrics.startTime;
|
|
144
|
-
metrics.consumers.forEach(c => {
|
|
145
|
-
if (c.endTime && c.batchesProcessed > 0) {
|
|
146
|
-
// c.endTime is when consumer exited (after idle timeout)
|
|
147
|
-
// Actual last batch time = c.endTime - (idle timeout we didn't track)
|
|
148
|
-
// Better: use the sum of all processing times
|
|
149
|
-
const totalBatchTime = c.batchTimes.reduce((a,b)=>a+b,0);
|
|
150
|
-
const totalPopTime = c.popTimes.reduce((a,b)=>a+b,0);
|
|
151
|
-
const actualEndTime = c.startTime + totalBatchTime + totalPopTime;
|
|
152
|
-
if (actualEndTime > lastBatchCompletedTime) {
|
|
153
|
-
lastBatchCompletedTime = actualEndTime;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const totalDuration = (metrics.endTime - metrics.startTime) / 1000;
|
|
159
|
-
const actualProcessingDuration = (lastBatchCompletedTime - metrics.startTime) / 1000;
|
|
160
|
-
const avgThroughput = (metrics.totalProcessed / actualProcessingDuration).toFixed(0);
|
|
161
|
-
|
|
162
|
-
console.log('\n' + '='.repeat(80));
|
|
163
|
-
console.log('📊 BENCHMARK RESULTS');
|
|
164
|
-
console.log('='.repeat(80));
|
|
165
|
-
console.log(`Total Messages Processed: ${metrics.totalProcessed.toLocaleString()}`);
|
|
166
|
-
console.log(`Total Wall Clock Time: ${totalDuration.toFixed(2)}s (includes idle timeout)`);
|
|
167
|
-
console.log(`Actual Processing Time: ${actualProcessingDuration.toFixed(2)}s`);
|
|
168
|
-
console.log(`Idle/Wait Time: ${(totalDuration - actualProcessingDuration).toFixed(2)}s`);
|
|
169
|
-
console.log(`Average Throughput: ${avgThroughput} msg/s (based on processing time)`);
|
|
170
|
-
console.log(`Number of Consumers: ${NUMBER_OF_CONSUMERS}`);
|
|
171
|
-
console.log(`Total Batches: ${metrics.batchTimes.length}`);
|
|
172
|
-
console.log(`Total Time: ${metrics.lastMessageTime - metrics.firstMessageTime}ms`);
|
|
173
|
-
console.log(`Total msg/s: ${metrics.totalProcessed / (metrics.lastMessageTime - metrics.firstMessageTime) * 1000} msg/s`);
|
|
174
|
-
|
|
175
|
-
if (metrics.batchTimes.length > 0) {
|
|
176
|
-
const avgBatchTime = (metrics.batchTimes.reduce((a, b) => a + b, 0) / metrics.batchTimes.length / 1000).toFixed(3);
|
|
177
|
-
const minBatchTime = (Math.min(...metrics.batchTimes) / 1000).toFixed(3);
|
|
178
|
-
const maxBatchTime = (Math.max(...metrics.batchTimes) / 1000).toFixed(3);
|
|
179
|
-
|
|
180
|
-
console.log(`\nBatch Times:`);
|
|
181
|
-
console.log(` Average: ${avgBatchTime}s`);
|
|
182
|
-
console.log(` Min: ${minBatchTime}s`);
|
|
183
|
-
console.log(` Max: ${maxBatchTime}s`);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (metrics.popTimes.length > 0) {
|
|
187
|
-
const avgPopTime = (metrics.popTimes.reduce((a, b) => a + b, 0) / metrics.popTimes.length / 1000).toFixed(3);
|
|
188
|
-
console.log(`\nAverage POP Time: ${avgPopTime}s`);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (metrics.ackTimes.length > 0) {
|
|
192
|
-
const avgAckTime = (metrics.ackTimes.reduce((a, b) => a + b, 0) / metrics.ackTimes.length / 1000).toFixed(3);
|
|
193
|
-
console.log(`Average ACK Time: ${avgAckTime}s`);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
console.log(`\nPer-Consumer Stats:`);
|
|
197
|
-
metrics.consumers.forEach(c => {
|
|
198
|
-
// Calculate actual processing time (sum of all batch times + pop times)
|
|
199
|
-
const totalBatchTime = c.batchTimes.reduce((a,b)=>a+b,0);
|
|
200
|
-
const totalPopTime = c.popTimes.reduce((a,b)=>a+b,0);
|
|
201
|
-
const actualProcessingTime = (totalBatchTime + totalPopTime) / 1000;
|
|
202
|
-
|
|
203
|
-
const wallClockTime = ((c.endTime - c.startTime) / 1000).toFixed(2);
|
|
204
|
-
const rate = (c.messagesProcessed / actualProcessingTime).toFixed(0);
|
|
205
|
-
const avgBatch = c.batchTimes.length > 0 ? (totalBatchTime / c.batchTimes.length / 1000).toFixed(3) : 'N/A';
|
|
206
|
-
const idleTime = (wallClockTime - actualProcessingTime).toFixed(2);
|
|
207
|
-
|
|
208
|
-
const queuesStr = c.queuesConsumed.size > 0 ? `queues:[${Array.from(c.queuesConsumed).sort().join(',')}]` : '';
|
|
209
|
-
const partitionsStr = c.partitionsConsumed.size > 0 ? `partitions:${c.partitionsConsumed.size}` : '';
|
|
210
|
-
const consumedFrom = [queuesStr, partitionsStr].filter(Boolean).join(' ');
|
|
211
|
-
|
|
212
|
-
console.log(` [C${c.consumerId}] ${c.messagesProcessed.toLocaleString()} msgs | Active: ${actualProcessingTime.toFixed(2)}s (${rate} msg/s) | Idle: ${idleTime}s | ${c.batchesProcessed} batches | avg: ${avgBatch}s | ${consumedFrom}`);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
console.log('='.repeat(80));
|
|
216
|
-
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { Queen } from '../client/client.js';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
|
|
4
|
-
const TOTAL_MESSAGES = 1000000;
|
|
5
|
-
const PARTITIONS = 10;
|
|
6
|
-
const MAX_BATCH_SIZE = 100; // Reduced from 10000
|
|
7
|
-
const PUSH_PARALLEL = 10; // Reduced from 10 to avoid lock contention
|
|
8
|
-
const QUEUE_NAME = 'benchmark-queue-001';
|
|
9
|
-
|
|
10
|
-
const q = new Queen({
|
|
11
|
-
baseUrls: ['http://localhost:6632'],
|
|
12
|
-
timeout: 120000
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
await q.queueDelete(QUEUE_NAME);
|
|
18
|
-
await q.queue(QUEUE_NAME, {
|
|
19
|
-
leaseTime: 10,
|
|
20
|
-
retryLimit: 3
|
|
21
|
-
}, { namespace: 'benchmark' });
|
|
22
|
-
} catch (e) {
|
|
23
|
-
console.error(e);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
let batches = []
|
|
29
|
-
const messagesPerPartition = Math.floor(TOTAL_MESSAGES / PARTITIONS);
|
|
30
|
-
|
|
31
|
-
// Create batches for each partition
|
|
32
|
-
const partitionBatches = [];
|
|
33
|
-
for (let i = 0; i < PARTITIONS; i++) {
|
|
34
|
-
partitionBatches[i] = [];
|
|
35
|
-
let messages = [];
|
|
36
|
-
for (let j = 0; j < messagesPerPartition; j++) {
|
|
37
|
-
messages.push({
|
|
38
|
-
id: j,
|
|
39
|
-
//message: `Message ${globalMessageId}`,
|
|
40
|
-
//timestamp: new Date().toISOString()
|
|
41
|
-
});
|
|
42
|
-
if (messages.length >= MAX_BATCH_SIZE) {
|
|
43
|
-
console.log(`Pushing ${messages.length} messages for partition ${i}`);
|
|
44
|
-
partitionBatches[i].push({
|
|
45
|
-
partition: i,
|
|
46
|
-
messages: messages
|
|
47
|
-
});
|
|
48
|
-
messages = [];
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
// Only add the remaining batch if it has messages
|
|
52
|
-
if (messages.length > 0) {
|
|
53
|
-
console.log(`Pushing ${messages.length} messages for partition ${i}`);
|
|
54
|
-
partitionBatches[i].push({
|
|
55
|
-
partition: i,
|
|
56
|
-
messages: messages
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Interleave batches from different partitions to avoid lock contention
|
|
62
|
-
// This ensures parallel pushes go to different partitions
|
|
63
|
-
const maxBatchesPerPartition = Math.max(...partitionBatches.map(pb => pb.length));
|
|
64
|
-
for (let batchIndex = 0; batchIndex < maxBatchesPerPartition; batchIndex++) {
|
|
65
|
-
for (let partition = 0; partition < PARTITIONS; partition++) {
|
|
66
|
-
if (partitionBatches[partition][batchIndex]) {
|
|
67
|
-
batches.push(partitionBatches[partition][batchIndex]);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const start = Date.now();
|
|
73
|
-
console.log(`Total batches: ${batches.length}, pushing ${PUSH_PARALLEL} at a time`);
|
|
74
|
-
for (let i = 0; i < batches.length; i += PUSH_PARALLEL) {
|
|
75
|
-
const batchGroup = batches.slice(i, i + PUSH_PARALLEL);
|
|
76
|
-
console.log(`Pushing batch group ${Math.floor(i/PUSH_PARALLEL) + 1}/${Math.ceil(batches.length/PUSH_PARALLEL)}: ${batchGroup.map(b => `partition ${b.partition} (${b.messages.length} msgs)`).join(', ')}`);
|
|
77
|
-
await Promise.all(batchGroup.map(batch => q.push(`${QUEUE_NAME}/${batch.partition}`, batch.messages)));
|
|
78
|
-
}
|
|
79
|
-
const end = Date.now();
|
|
80
|
-
console.log(`Pushed ${TOTAL_MESSAGES} messages in ${(end - start) / 1000}s, ${(TOTAL_MESSAGES / ((end - start) / 1000)).toFixed(2)} msg/s`);
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { Queen } from '../client/client.js';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
|
|
4
|
-
const TOTAL_MESSAGES = 100000;
|
|
5
|
-
const NUM_QUEUES = 50; // X: Number of queues
|
|
6
|
-
const PARTITIONS_PER_QUEUE = 10; // Y: Partitions per queue
|
|
7
|
-
const MAX_BATCH_SIZE = 10;
|
|
8
|
-
const PUSH_PARALLEL = 10;
|
|
9
|
-
|
|
10
|
-
const q = new Queen({
|
|
11
|
-
baseUrls: ['http://localhost:6632'],
|
|
12
|
-
timeout: 120000
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
// Create X queues, each with Y partitions, all in 'benchmark' namespace
|
|
16
|
-
console.log(`Creating ${NUM_QUEUES} queues with ${PARTITIONS_PER_QUEUE} partitions each...`);
|
|
17
|
-
for (let queueIdx = 0; queueIdx < NUM_QUEUES; queueIdx++) {
|
|
18
|
-
const queueName = `benchmark-queue-${String(queueIdx + 1).padStart(2, '0')}`;
|
|
19
|
-
try {
|
|
20
|
-
//await q.queueDelete(queueName);
|
|
21
|
-
} catch (e) {
|
|
22
|
-
// Queue might not exist
|
|
23
|
-
}
|
|
24
|
-
await q.queue(queueName, {
|
|
25
|
-
leaseTime: 10,
|
|
26
|
-
retryLimit: 3
|
|
27
|
-
}, { namespace: 'benchmark' });
|
|
28
|
-
console.log(` Created queue: ${queueName}`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// Calculate total partitions across all queues
|
|
33
|
-
const TOTAL_PARTITIONS = NUM_QUEUES * PARTITIONS_PER_QUEUE;
|
|
34
|
-
const messagesPerPartition = Math.floor(TOTAL_MESSAGES / TOTAL_PARTITIONS);
|
|
35
|
-
|
|
36
|
-
console.log(`\nDistributing ${TOTAL_MESSAGES} messages across ${NUM_QUEUES} queues × ${PARTITIONS_PER_QUEUE} partitions = ${TOTAL_PARTITIONS} total partitions`);
|
|
37
|
-
console.log(`Messages per partition: ${messagesPerPartition}\n`);
|
|
38
|
-
|
|
39
|
-
// Create batches for each queue/partition combination
|
|
40
|
-
const queuePartitionBatches = [];
|
|
41
|
-
for (let queueIdx = 0; queueIdx < NUM_QUEUES; queueIdx++) {
|
|
42
|
-
for (let partitionIdx = 0; partitionIdx < PARTITIONS_PER_QUEUE; partitionIdx++) {
|
|
43
|
-
const key = `q${queueIdx}_p${partitionIdx}`;
|
|
44
|
-
queuePartitionBatches.push({ queueIdx, partitionIdx, batches: [] });
|
|
45
|
-
|
|
46
|
-
let messages = [];
|
|
47
|
-
for (let msgIdx = 0; msgIdx < messagesPerPartition; msgIdx++) {
|
|
48
|
-
messages.push({
|
|
49
|
-
id: msgIdx,
|
|
50
|
-
queue: queueIdx,
|
|
51
|
-
partition: partitionIdx
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
if (messages.length >= MAX_BATCH_SIZE) {
|
|
55
|
-
queuePartitionBatches[queuePartitionBatches.length - 1].batches.push([...messages]);
|
|
56
|
-
messages = [];
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Add remaining messages
|
|
61
|
-
if (messages.length > 0) {
|
|
62
|
-
queuePartitionBatches[queuePartitionBatches.length - 1].batches.push([...messages]);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Flatten and interleave batches to distribute load evenly
|
|
68
|
-
let batches = [];
|
|
69
|
-
const maxBatchesPerQueuePartition = Math.max(...queuePartitionBatches.map(qp => qp.batches.length));
|
|
70
|
-
for (let batchIdx = 0; batchIdx < maxBatchesPerQueuePartition; batchIdx++) {
|
|
71
|
-
for (const qp of queuePartitionBatches) {
|
|
72
|
-
if (qp.batches[batchIdx]) {
|
|
73
|
-
batches.push({
|
|
74
|
-
queueIdx: qp.queueIdx,
|
|
75
|
-
partitionIdx: qp.partitionIdx,
|
|
76
|
-
messages: qp.batches[batchIdx]
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const start = Date.now();
|
|
83
|
-
console.log(`Total batches: ${batches.length}, pushing ${PUSH_PARALLEL} at a time\n`);
|
|
84
|
-
|
|
85
|
-
for (let i = 0; i < batches.length; i += PUSH_PARALLEL) {
|
|
86
|
-
const batchGroup = batches.slice(i, i + PUSH_PARALLEL);
|
|
87
|
-
const batchGroupNum = Math.floor(i/PUSH_PARALLEL) + 1;
|
|
88
|
-
const totalBatchGroups = Math.ceil(batches.length/PUSH_PARALLEL);
|
|
89
|
-
|
|
90
|
-
console.log(`[${batchGroupNum}/${totalBatchGroups}] Pushing: ${batchGroup.map(b => {
|
|
91
|
-
const queueName = `benchmark-queue-${String(b.queueIdx + 1).padStart(2, '0')}`;
|
|
92
|
-
return `${queueName}/p${b.partitionIdx} (${b.messages.length} msgs)`;
|
|
93
|
-
}).join(', ')}`);
|
|
94
|
-
|
|
95
|
-
await Promise.all(batchGroup.map(batch => {
|
|
96
|
-
const queueName = `benchmark-queue-${String(batch.queueIdx + 1).padStart(2, '0')}`;
|
|
97
|
-
return q.push(`${queueName}/${batch.partitionIdx}`, batch.messages);
|
|
98
|
-
}));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const end = Date.now();
|
|
102
|
-
const duration = (end - start) / 1000;
|
|
103
|
-
const throughput = (TOTAL_MESSAGES / duration).toFixed(2);
|
|
104
|
-
|
|
105
|
-
console.log('\n' + '='.repeat(80));
|
|
106
|
-
console.log('📤 PRODUCER RESULTS');
|
|
107
|
-
console.log('='.repeat(80));
|
|
108
|
-
console.log(`Total Messages Pushed: ${TOTAL_MESSAGES.toLocaleString()}`);
|
|
109
|
-
console.log(`Queues: ${NUM_QUEUES}`);
|
|
110
|
-
console.log(`Partitions per Queue: ${PARTITIONS_PER_QUEUE}`);
|
|
111
|
-
console.log(`Total Partitions: ${TOTAL_PARTITIONS}`);
|
|
112
|
-
console.log(`Duration: ${duration.toFixed(2)}s`);
|
|
113
|
-
console.log(`Throughput: ${throughput} msg/s`);
|
|
114
|
-
console.log('='.repeat(80));
|
|
115
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|