queen-mq 0.1.0 → 0.1.2

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.
Files changed (160) hide show
  1. package/API.md +862 -752
  2. package/AUTH.md +2044 -0
  3. package/LICENSE.md +202 -0
  4. package/README.md +1705 -1051
  5. package/WEBAPP.md +1889 -0
  6. package/assets/dashboard-01.png +0 -0
  7. package/assets/queen-logo-blue.svg +210 -0
  8. package/assets/queen-logo-cyan.svg +210 -0
  9. package/assets/queen-logo-indigo.svg +210 -0
  10. package/assets/queen-logo-orange.svg +210 -0
  11. package/assets/queen-logo-pink.svg +210 -0
  12. package/assets/queen-logo-purple.svg +210 -0
  13. package/assets/queen-logo-rose.svg +239 -0
  14. package/assets/queen-logo.svg +263 -0
  15. package/examples/batch-processing.js +58 -0
  16. package/examples/test-complete-client.js +260 -0
  17. package/examples/test-dashboard-api.js +200 -0
  18. package/examples/test-traceid.js +147 -0
  19. package/package.json +17 -4
  20. package/server.log +1 -0
  21. package/src/benchmark/consumer.js +207 -0
  22. package/src/benchmark/consumer_multi.js +216 -0
  23. package/src/benchmark/producer.js +75 -0
  24. package/src/benchmark/producer_multi.js +115 -0
  25. package/src/client/client.js +300 -31
  26. package/src/client/queenClient.js +5 -0
  27. package/src/cluster-server.js +242 -0
  28. package/src/config.js +19 -5
  29. package/src/database/connection.js +42 -16
  30. package/src/database/poolManager.js +7 -0
  31. package/src/database/schema-v2.sql +194 -130
  32. package/src/managers/queueManagerOptimized.js +823 -933
  33. package/src/managers/systemEventManager.js +8 -3
  34. package/src/routes/messages.js +127 -57
  35. package/src/routes/pop.js +27 -43
  36. package/src/routes/resources.js +61 -27
  37. package/src/routes/status.js +1037 -0
  38. package/src/server.js +308 -272
  39. package/src/services/evictionService.js +57 -28
  40. package/src/services/retentionService.js +44 -11
  41. package/src/test/MIGRATION_ISSUES.md +174 -0
  42. package/src/test/README.md +203 -0
  43. package/src/test/advanced-pattern-tests.js +1137 -0
  44. package/src/test/bus-mode-tests.js +361 -0
  45. package/src/test/core-tests.js +342 -0
  46. package/src/test/edge-case-tests.js +561 -0
  47. package/src/test/enterprise-tests.js +637 -0
  48. package/src/test/partition-locking-tests.js +545 -0
  49. package/src/test/test-new.js +278 -0
  50. package/src/test/test.js +6 -3
  51. package/src/test/utils.js +169 -0
  52. package/src/utils/streaming.js +231 -0
  53. package/src/utils/uuid.js +2 -2
  54. package/src/websocket/wsServer.js +10 -3
  55. package/test-keepalive-v2.sh +22 -0
  56. package/webapp/COLOR_GUIDE.md +118 -0
  57. package/webapp/README.md +143 -0
  58. package/webapp/index.html +14 -0
  59. package/webapp/package-lock.json +3184 -0
  60. package/webapp/package.json +25 -0
  61. package/webapp/postcss.config.js +7 -0
  62. package/webapp/public/assets/queen-logo-blue.svg +210 -0
  63. package/webapp/public/assets/queen-logo-cyan.svg +210 -0
  64. package/webapp/public/assets/queen-logo-indigo.svg +210 -0
  65. package/webapp/public/assets/queen-logo-orange.svg +210 -0
  66. package/webapp/public/assets/queen-logo-pink.svg +210 -0
  67. package/webapp/public/assets/queen-logo-purple.svg +210 -0
  68. package/webapp/public/assets/queen-logo-rose.svg +239 -0
  69. package/webapp/public/assets/queen-logo.svg +263 -0
  70. package/webapp/src/App.vue +19 -0
  71. package/webapp/src/api/analytics.js +10 -0
  72. package/webapp/src/api/client.js +29 -0
  73. package/webapp/src/api/consumers.js +52 -0
  74. package/webapp/src/api/health.js +7 -0
  75. package/webapp/src/api/messages.js +26 -0
  76. package/webapp/src/api/queues.js +14 -0
  77. package/webapp/src/api/resources.js +8 -0
  78. package/webapp/src/assets/styles/main.css +357 -0
  79. package/webapp/src/components/analytics/AnalyticsFilters.vue +87 -0
  80. package/webapp/src/components/analytics/AnalyticsMetrics.vue +57 -0
  81. package/webapp/src/components/analytics/MessageDistributionChart.vue +111 -0
  82. package/webapp/src/components/analytics/MessageFlowChart.vue +173 -0
  83. package/webapp/src/components/analytics/TimeRangeSelector.vue +27 -0
  84. package/webapp/src/components/analytics/TopQueuesChart.vue +132 -0
  85. package/webapp/src/components/common/ConfirmDialog.vue +56 -0
  86. package/webapp/src/components/common/LoadingSpinner.vue +6 -0
  87. package/webapp/src/components/common/MetricCard.vue +43 -0
  88. package/webapp/src/components/common/StatusBadge.vue +45 -0
  89. package/webapp/src/components/dashboard/MessageStatusCard.vue +50 -0
  90. package/webapp/src/components/dashboard/PerformanceCard.vue +38 -0
  91. package/webapp/src/components/dashboard/ThroughputChart.vue +182 -0
  92. package/webapp/src/components/dashboard/TopQueuesTable.vue +53 -0
  93. package/webapp/src/components/layout/AppLayout.vue +110 -0
  94. package/webapp/src/components/layout/AppSidebar.vue +304 -0
  95. package/webapp/src/components/messages/MessageDetailPanel.vue +242 -0
  96. package/webapp/src/components/messages/MessageFilters.vue +114 -0
  97. package/webapp/src/components/queue-detail/PartitionList.vue +79 -0
  98. package/webapp/src/components/queue-detail/PushMessageModal.vue +175 -0
  99. package/webapp/src/components/queue-detail/QueueConfig.vue +63 -0
  100. package/webapp/src/components/queue-detail/QueueDetailHeader.vue +53 -0
  101. package/webapp/src/components/queue-detail/RecentMessages.vue +76 -0
  102. package/webapp/src/components/queues/CreateQueueModal.vue +193 -0
  103. package/webapp/src/components/queues/QueueFilters.vue +90 -0
  104. package/webapp/src/composables/useApi.js +34 -0
  105. package/webapp/src/composables/useTheme.js +36 -0
  106. package/webapp/src/main.js +11 -0
  107. package/webapp/src/router/index.js +42 -0
  108. package/webapp/src/utils/colors.js +96 -0
  109. package/webapp/src/utils/formatters.js +49 -0
  110. package/webapp/src/views/Analytics.vue +377 -0
  111. package/webapp/src/views/ConsumerGroups.vue +433 -0
  112. package/webapp/src/views/Dashboard.vue +418 -0
  113. package/webapp/src/views/Messages.vue +361 -0
  114. package/webapp/src/views/QueueDetail.vue +582 -0
  115. package/webapp/src/views/Queues.vue +496 -0
  116. package/webapp/tailwind.config.js +25 -0
  117. package/webapp/vite.config.js +10 -0
  118. package/CACHE.md +0 -519
  119. package/DASHBOARD-V3.md +0 -478
  120. package/DASHBOARD.md +0 -382
  121. package/MOD_QUEUE.md +0 -453
  122. package/PARTITION_LOCKING_DESIGN.md +0 -989
  123. package/PLAN.md +0 -707
  124. package/QUERY_ANALSYS.md +0 -72
  125. package/QUEUE_BUS.md +0 -334
  126. package/V2-PLAN.md +0 -236
  127. package/dashboard/.vscode/extensions.json +0 -3
  128. package/dashboard/README.md +0 -5
  129. package/dashboard/index.html +0 -14
  130. package/dashboard/package-lock.json +0 -1458
  131. package/dashboard/package.json +0 -25
  132. package/dashboard/public/vite.svg +0 -1
  133. package/dashboard/src/App.vue +0 -29
  134. package/dashboard/src/assets/styles/main.css +0 -908
  135. package/dashboard/src/assets/vue.svg +0 -1
  136. package/dashboard/src/components/cards/MetricCard.vue +0 -298
  137. package/dashboard/src/components/charts/QueueDepthChart.vue +0 -276
  138. package/dashboard/src/components/charts/QueueLagChart.vue +0 -436
  139. package/dashboard/src/components/charts/ThroughputChart.vue +0 -302
  140. package/dashboard/src/components/common/ActivityFeed.vue +0 -251
  141. package/dashboard/src/components/layout/AppHeader.vue +0 -208
  142. package/dashboard/src/components/layout/AppLayout.vue +0 -88
  143. package/dashboard/src/components/layout/AppSidebar.vue +0 -261
  144. package/dashboard/src/main.js +0 -44
  145. package/dashboard/src/router.js +0 -54
  146. package/dashboard/src/services/api.js +0 -187
  147. package/dashboard/src/services/websocket.js +0 -167
  148. package/dashboard/src/utils/constants.js +0 -56
  149. package/dashboard/src/utils/helpers.js +0 -118
  150. package/dashboard/src/views/Analytics.vue +0 -912
  151. package/dashboard/src/views/Dashboard.vue +0 -906
  152. package/dashboard/src/views/Messages.vue +0 -437
  153. package/dashboard/src/views/QueueDetail.vue +0 -501
  154. package/dashboard/src/views/Queues.vue +0 -333
  155. package/dashboard/vite.config.js +0 -30
  156. package/debug-namespace.js +0 -110
  157. package/docs/long-polling.md +0 -159
  158. package/docs/multi-server-cache-solutions.md +0 -185
  159. package/docs/performance-tuning.md +0 -222
  160. package/src/routes/analytics.js +0 -812
@@ -0,0 +1,207 @@
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 BATCH_SIZE = 10;
7
+ const CONSUME_MODE = 'partition';
8
+
9
+ // Global metrics tracking
10
+ const metrics = {
11
+ startTime: null,
12
+ endTime: null,
13
+ totalProcessed: 0,
14
+ consumers: [],
15
+ batchTimes: [],
16
+ ackTimes: [],
17
+ popTimes: [],
18
+ firstMessageTime: null,
19
+ lastMessageTime: null
20
+ };
21
+
22
+ async function consumer(consumerId, partition) {
23
+ const q = new Queen({
24
+ baseUrls: ['http://localhost:6632'],
25
+ timeout: 30000
26
+ });
27
+
28
+ const consumerMetrics = {
29
+ consumerId,
30
+ partition,
31
+ messagesProcessed: 0,
32
+ batchesProcessed: 0,
33
+ batchTimes: [],
34
+ ackTimes: [],
35
+ popTimes: [],
36
+ startTime: Date.now(),
37
+ endTime: null
38
+ };
39
+
40
+ const target = CONSUME_MODE === 'partition' ? `${QUEUE_NAME}/${partition}` : `${QUEUE_NAME}`;
41
+
42
+ metrics.consumers.push(consumerMetrics);
43
+ if (target === `${QUEUE_NAME}`) {
44
+ console.log(`[Consumer ${consumerId}] Started, consuming all partitions`);
45
+ } else {
46
+ console.log(`[Consumer ${consumerId}] Started, consuming partition ${partition}`);
47
+ }
48
+
49
+ try {
50
+ for await (const messages of q.takeBatch(target, { //`namespace:benchmark`,
51
+ wait: true,
52
+ timeout: 15000,
53
+ batch: BATCH_SIZE,
54
+ idleTimeout: 5000
55
+ })) {
56
+ if (!metrics.firstMessageTime) {
57
+ metrics.firstMessageTime = Date.now();
58
+ }
59
+ metrics.lastMessageTime = Date.now();
60
+ const batchStartTime = Date.now();
61
+ const popTime = batchStartTime - (consumerMetrics.endTime || consumerMetrics.startTime);
62
+ consumerMetrics.popTimes.push(popTime);
63
+ metrics.popTimes.push(popTime);
64
+
65
+ try {
66
+ // Validate message ordering
67
+ const partitionLastIds = new Map();
68
+
69
+ for (const message of messages) {
70
+ if (!message.data || typeof message.data.id !== 'number') {
71
+ throw new Error(`Message missing or invalid data.id: ${JSON.stringify(message)}`);
72
+ }
73
+
74
+ const partition = message.partition || 'default';
75
+ const messageId = message.data.id;
76
+
77
+ if (partitionLastIds.has(partition)) {
78
+ const lastId = partitionLastIds.get(partition);
79
+ if (messageId !== lastId + 1) {
80
+ console.log(message.partition, message);
81
+ throw new Error(`Message ordering violation in partition '${partition}': expected id ${lastId + 1}, got ${messageId}`);
82
+ }
83
+ }
84
+
85
+ partitionLastIds.set(partition, messageId);
86
+ }
87
+
88
+ // Acknowledge all messages
89
+ const ackStartTime = Date.now();
90
+ await q.ack(messages);
91
+ const ackTime = Date.now() - ackStartTime;
92
+ consumerMetrics.ackTimes.push(ackTime);
93
+ metrics.ackTimes.push(ackTime);
94
+
95
+ const batchEndTime = Date.now();
96
+ const batchDuration = batchEndTime - batchStartTime;
97
+
98
+ consumerMetrics.messagesProcessed += messages.length;
99
+ consumerMetrics.batchesProcessed++;
100
+ consumerMetrics.batchTimes.push(batchDuration);
101
+ consumerMetrics.endTime = batchEndTime;
102
+
103
+ metrics.totalProcessed += messages.length;
104
+ metrics.batchTimes.push(batchDuration);
105
+
106
+ const overallDuration = (batchEndTime - metrics.startTime) / 1000;
107
+ const currentRate = (metrics.totalProcessed / overallDuration).toFixed(0);
108
+
109
+ 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`);
110
+ metrics.lastMessageTime = Date.now();
111
+ } catch (error) {
112
+ console.error(new Date().toISOString(), `[Consumer ${consumerId}] Error processing batch:`, error);
113
+ await q.ack(messages, false, { error: error.message });
114
+ }
115
+ }
116
+ } catch (error) {
117
+ console.error(`[Consumer ${consumerId}] Fatal error:`, error);
118
+ }
119
+
120
+ consumerMetrics.endTime = Date.now();
121
+ console.log(`[Consumer ${consumerId}] Finished. Processed ${consumerMetrics.messagesProcessed} messages in ${consumerMetrics.batchesProcessed} batches`);
122
+ }
123
+
124
+ // Start metrics
125
+ metrics.startTime = Date.now();
126
+
127
+ // Launch all consumers
128
+ const consumerPromises = [];
129
+ for (let i = 0; i < NUMBER_OF_CONSUMERS; i++) {
130
+ consumerPromises.push(consumer(i, i));
131
+ }
132
+
133
+ // Wait for all consumers to complete
134
+ await Promise.all(consumerPromises);
135
+ metrics.endTime = Date.now();
136
+
137
+ // Calculate actual processing time (excluding idle timeout)
138
+ // Find the time when the last batch was actually completed
139
+ let lastBatchCompletedTime = metrics.startTime;
140
+ metrics.consumers.forEach(c => {
141
+ if (c.endTime && c.batchesProcessed > 0) {
142
+ // c.endTime is when consumer exited (after idle timeout)
143
+ // Actual last batch time = c.endTime - (idle timeout we didn't track)
144
+ // Better: use the sum of all processing times
145
+ const totalBatchTime = c.batchTimes.reduce((a,b)=>a+b,0);
146
+ const totalPopTime = c.popTimes.reduce((a,b)=>a+b,0);
147
+ const actualEndTime = c.startTime + totalBatchTime + totalPopTime;
148
+ if (actualEndTime > lastBatchCompletedTime) {
149
+ lastBatchCompletedTime = actualEndTime;
150
+ }
151
+ }
152
+ });
153
+
154
+ const totalDuration = (metrics.endTime - metrics.startTime) / 1000;
155
+ const actualProcessingDuration = (lastBatchCompletedTime - metrics.startTime) / 1000;
156
+ const avgThroughput = (metrics.totalProcessed / actualProcessingDuration).toFixed(0);
157
+
158
+ console.log('\n' + '='.repeat(80));
159
+ console.log('📊 BENCHMARK RESULTS');
160
+ console.log('='.repeat(80));
161
+ console.log(`Total Messages Processed: ${metrics.totalProcessed.toLocaleString()}`);
162
+ console.log(`Total Wall Clock Time: ${totalDuration.toFixed(2)}s (includes idle timeout)`);
163
+ console.log(`Actual Processing Time: ${actualProcessingDuration.toFixed(2)}s`);
164
+ console.log(`Idle/Wait Time: ${(totalDuration - actualProcessingDuration).toFixed(2)}s`);
165
+ console.log(`Average Throughput: ${avgThroughput} msg/s (based on processing time)`);
166
+ console.log(`Number of Consumers: ${NUMBER_OF_CONSUMERS}`);
167
+ console.log(`Total Batches: ${metrics.batchTimes.length}`);
168
+ console.log(`Total Time: ${metrics.lastMessageTime - metrics.firstMessageTime}ms`);
169
+ console.log(`Total msg/s: ${metrics.totalProcessed / (metrics.lastMessageTime - metrics.firstMessageTime) * 1000} msg/s`);
170
+
171
+ if (metrics.batchTimes.length > 0) {
172
+ const avgBatchTime = (metrics.batchTimes.reduce((a, b) => a + b, 0) / metrics.batchTimes.length / 1000).toFixed(3);
173
+ const minBatchTime = (Math.min(...metrics.batchTimes) / 1000).toFixed(3);
174
+ const maxBatchTime = (Math.max(...metrics.batchTimes) / 1000).toFixed(3);
175
+
176
+ console.log(`\nBatch Times:`);
177
+ console.log(` Average: ${avgBatchTime}s`);
178
+ console.log(` Min: ${minBatchTime}s`);
179
+ console.log(` Max: ${maxBatchTime}s`);
180
+ }
181
+
182
+ if (metrics.popTimes.length > 0) {
183
+ const avgPopTime = (metrics.popTimes.reduce((a, b) => a + b, 0) / metrics.popTimes.length / 1000).toFixed(3);
184
+ console.log(`\nAverage POP Time: ${avgPopTime}s`);
185
+ }
186
+
187
+ if (metrics.ackTimes.length > 0) {
188
+ const avgAckTime = (metrics.ackTimes.reduce((a, b) => a + b, 0) / metrics.ackTimes.length / 1000).toFixed(3);
189
+ console.log(`Average ACK Time: ${avgAckTime}s`);
190
+ }
191
+
192
+ console.log(`\nPer-Consumer Stats:`);
193
+ metrics.consumers.forEach(c => {
194
+ // Calculate actual processing time (sum of all batch times + pop times)
195
+ const totalBatchTime = c.batchTimes.reduce((a,b)=>a+b,0);
196
+ const totalPopTime = c.popTimes.reduce((a,b)=>a+b,0);
197
+ const actualProcessingTime = (totalBatchTime + totalPopTime) / 1000;
198
+
199
+ const wallClockTime = ((c.endTime - c.startTime) / 1000).toFixed(2);
200
+ const rate = (c.messagesProcessed / actualProcessingTime).toFixed(0);
201
+ const avgBatch = c.batchTimes.length > 0 ? (totalBatchTime / c.batchTimes.length / 1000).toFixed(3) : 'N/A';
202
+ const idleTime = (wallClockTime - actualProcessingTime).toFixed(2);
203
+
204
+ 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`);
205
+ });
206
+
207
+ console.log('='.repeat(80));
@@ -0,0 +1,216 @@
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
+
@@ -0,0 +1,75 @@
1
+ import { Queen } from '../client/client.js';
2
+ import fs from 'fs';
3
+
4
+ const TOTAL_MESSAGES = 100000;
5
+ const PARTITIONS = 10;
6
+ const MAX_BATCH_SIZE = 1000; // 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
+ //await q.queueDelete(QUEUE_NAME);
16
+ await q.queue(QUEUE_NAME, {
17
+ leaseTime: 10,
18
+ retryLimit: 3
19
+ }, { namespace: 'benchmark' });
20
+
21
+
22
+
23
+ let batches = []
24
+ const messagesPerPartition = Math.floor(TOTAL_MESSAGES / PARTITIONS);
25
+
26
+ // Create batches for each partition
27
+ const partitionBatches = [];
28
+ for (let i = 0; i < PARTITIONS; i++) {
29
+ partitionBatches[i] = [];
30
+ let messages = [];
31
+ for (let j = 0; j < messagesPerPartition; j++) {
32
+ messages.push({
33
+ id: j,
34
+ //message: `Message ${globalMessageId}`,
35
+ //timestamp: new Date().toISOString()
36
+ });
37
+ if (messages.length >= MAX_BATCH_SIZE) {
38
+ console.log(`Pushing ${messages.length} messages for partition ${i}`);
39
+ partitionBatches[i].push({
40
+ partition: i,
41
+ messages: messages
42
+ });
43
+ messages = [];
44
+ }
45
+ }
46
+ // Only add the remaining batch if it has messages
47
+ if (messages.length > 0) {
48
+ console.log(`Pushing ${messages.length} messages for partition ${i}`);
49
+ partitionBatches[i].push({
50
+ partition: i,
51
+ messages: messages
52
+ });
53
+ }
54
+ }
55
+
56
+ // Interleave batches from different partitions to avoid lock contention
57
+ // This ensures parallel pushes go to different partitions
58
+ const maxBatchesPerPartition = Math.max(...partitionBatches.map(pb => pb.length));
59
+ for (let batchIndex = 0; batchIndex < maxBatchesPerPartition; batchIndex++) {
60
+ for (let partition = 0; partition < PARTITIONS; partition++) {
61
+ if (partitionBatches[partition][batchIndex]) {
62
+ batches.push(partitionBatches[partition][batchIndex]);
63
+ }
64
+ }
65
+ }
66
+
67
+ const start = Date.now();
68
+ console.log(`Total batches: ${batches.length}, pushing ${PUSH_PARALLEL} at a time`);
69
+ for (let i = 0; i < batches.length; i += PUSH_PARALLEL) {
70
+ const batchGroup = batches.slice(i, i + PUSH_PARALLEL);
71
+ 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(', ')}`);
72
+ await Promise.all(batchGroup.map(batch => q.push(`${QUEUE_NAME}/${batch.partition}`, batch.messages)));
73
+ }
74
+ const end = Date.now();
75
+ console.log(`Pushed ${TOTAL_MESSAGES} messages in ${(end - start) / 1000}s, ${(TOTAL_MESSAGES / ((end - start) / 1000)).toFixed(2)} msg/s`);
@@ -0,0 +1,115 @@
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
+