queen-mq 0.1.6 → 0.2.11

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 (85) hide show
  1. package/README.md +166 -2078
  2. package/{src → client-js}/benchmark/consumer.js +5 -3
  3. package/{src → client-js}/benchmark/producer.js +9 -4
  4. package/client-js/client/client.js +1511 -0
  5. package/{src → client-js}/client/index.js +1 -4
  6. package/{src → client-js}/client/utils/http.js +3 -2
  7. package/{src → client-js}/client/utils/retry.js +7 -1
  8. package/client-js/test/advanced-client-tests.js +761 -0
  9. package/{src → client-js}/test/advanced-pattern-tests.js +2 -2
  10. package/{src → client-js}/test/bus-mode-tests.js +6 -6
  11. package/{src → client-js}/test/core-tests.js +8 -3
  12. package/{src → client-js}/test/edge-case-tests.js +4 -3
  13. package/client-js/test/human.js +162 -0
  14. package/client-js/test/qos0-tests.js +334 -0
  15. package/{src → client-js}/test/test-new.js +78 -1
  16. package/package.json +8 -29
  17. package/init-db.js +0 -20
  18. package/src/client/client.js +0 -586
  19. package/src/cluster-server.js +0 -242
  20. package/src/config.js +0 -229
  21. package/src/database/connection.js +0 -129
  22. package/src/database/poolManager.js +0 -199
  23. package/src/database/schema-v2.sql +0 -278
  24. package/src/managers/eventManager.js +0 -59
  25. package/src/managers/queueManagerOptimized.js +0 -1431
  26. package/src/managers/resourceCache.js +0 -96
  27. package/src/managers/systemEventManager.js +0 -132
  28. package/src/routes/ack.js +0 -26
  29. package/src/routes/configure.js +0 -46
  30. package/src/routes/messages.js +0 -368
  31. package/src/routes/pop.js +0 -69
  32. package/src/routes/push.js +0 -28
  33. package/src/routes/resources.js +0 -330
  34. package/src/routes/status.js +0 -1037
  35. package/src/server.js +0 -1528
  36. package/src/test/MIGRATION_ISSUES.md +0 -174
  37. package/src/test/test.js +0 -4524
  38. package/src/utils/streaming.js +0 -231
  39. package/src/webapp-dist/assets/Analytics-DJrE4Rz2.css +0 -1
  40. package/src/webapp-dist/assets/Analytics-HEMXKmfJ.js +0 -1
  41. package/src/webapp-dist/assets/ConfirmDialog-YEoCdE7x.js +0 -1
  42. package/src/webapp-dist/assets/ConsumerGroups-CC5VJywp.css +0 -1
  43. package/src/webapp-dist/assets/ConsumerGroups-D8Z20HhZ.js +0 -1
  44. package/src/webapp-dist/assets/Dashboard-BRFOz6it.css +0 -1
  45. package/src/webapp-dist/assets/Dashboard-Bpx9I32h.js +0 -1
  46. package/src/webapp-dist/assets/LoadingSpinner-BPttXcbs.js +0 -1
  47. package/src/webapp-dist/assets/Messages-BOfVHKma.css +0 -1
  48. package/src/webapp-dist/assets/Messages-EKURCbLl.js +0 -1
  49. package/src/webapp-dist/assets/QueueDetail-BXXA7xoZ.css +0 -1
  50. package/src/webapp-dist/assets/QueueDetail-Bmxg0KiA.js +0 -1
  51. package/src/webapp-dist/assets/Queues-CvrJkrZT.css +0 -1
  52. package/src/webapp-dist/assets/Queues-yUx9_3-9.js +0 -1
  53. package/src/webapp-dist/assets/StatusBadge-C1bBtXHh.css +0 -1
  54. package/src/webapp-dist/assets/StatusBadge-C6N84XmO.js +0 -1
  55. package/src/webapp-dist/assets/analytics-BapTekLM.js +0 -1
  56. package/src/webapp-dist/assets/colors-5z6Q_kUr.js +0 -18
  57. package/src/webapp-dist/assets/index-B7w2bKT6.css +0 -1
  58. package/src/webapp-dist/assets/index-CUSnwYTH.js +0 -31
  59. package/src/webapp-dist/assets/messages-CRV6E-xU.js +0 -1
  60. package/src/webapp-dist/assets/queen-logo-blue.svg +0 -210
  61. package/src/webapp-dist/assets/queen-logo-cyan.svg +0 -210
  62. package/src/webapp-dist/assets/queen-logo-indigo.svg +0 -210
  63. package/src/webapp-dist/assets/queen-logo-orange.svg +0 -210
  64. package/src/webapp-dist/assets/queen-logo-pink.svg +0 -210
  65. package/src/webapp-dist/assets/queen-logo-purple.svg +0 -210
  66. package/src/webapp-dist/assets/queen-logo-rose.svg +0 -239
  67. package/src/webapp-dist/assets/queen-logo.svg +0 -263
  68. package/src/webapp-dist/assets/queues-BdUbxfBC.js +0 -1
  69. package/src/webapp-dist/assets/resources-Bs9U-ceF.js +0 -1
  70. package/src/webapp-dist/index.html +0 -15
  71. package/src/websocket/wsServer.js +0 -228
  72. /package/{src → client-js}/benchmark/consumer_multi.js +0 -0
  73. /package/{src → client-js}/benchmark/producer_multi.js +0 -0
  74. /package/{src → client-js}/client/utils/loadBalancer.js +0 -0
  75. /package/{src → client-js}/services/encryptionService.js +0 -0
  76. /package/{src → client-js}/services/evictionService.js +0 -0
  77. /package/{src → client-js}/services/retentionService.js +0 -0
  78. /package/{src → client-js}/services/startupSync.js +0 -0
  79. /package/{src → client-js}/test/README.md +0 -0
  80. /package/{src → client-js}/test/enterprise-tests.js +0 -0
  81. /package/{src → client-js}/test/partition-locking-tests.js +0 -0
  82. /package/{src → client-js}/test/utils.js +0 -0
  83. /package/{src → client-js}/test/window-buffer-test.js +0 -0
  84. /package/{src → client-js}/utils/logger.js +0 -0
  85. /package/{src → client-js}/utils/uuid.js +0 -0
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Queen - PostgreSQL-backed Message Queue System
1
+ # Queen MQ - PostgreSQL-backed C++ Message Queue
2
2
 
3
3
  <div align="center">
4
4
 
@@ -7,2170 +7,258 @@
7
7
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE.md)
8
8
  [![Node](https://img.shields.io/badge/node-%3E%3D22.0.0-brightgreen.svg)](https://nodejs.org/)
9
9
 
10
- [Quick Start](#-quick-start) • [Client Examples](#-client-examples) • [Server Setup](#-server-setup) • [Core Concepts](#-core-concepts) • [API Reference](#-http-api-reference) • [Dashboard](#-dashboard)
10
+ [Quick Start](#js-client-usage) • [Examples](#-examples) • [Webapp](#webapp) • [Server Setup](#install-server-and-configure-it) • [HTTP API](#raw-http-api)
11
11
 
12
12
  <p align="center">
13
- <img src="assets/queen-logo.svg" alt="Queen Logo" width="120" />
13
+ <img src="assets/queen-logo-rose.svg" alt="Queen Logo" width="120" />
14
14
  </p>
15
15
 
16
16
  </div>
17
17
 
18
18
  ---
19
19
 
20
- ## 🎯 Introduction
20
+ ## Introduction
21
21
 
22
- **Queen** is a production-ready message queue system that combines the reliability of PostgreSQL with the performance of modern async architectures. Built with uWebSockets.js for blazing-fast HTTP handling and designed for real-world workloads.
22
+ QueenMQ is a queue system written in C++ and backed by Postgres. Supports queues and consumer groups.
23
23
 
24
- ### Why Queen?
24
+ ## JS Client usage
25
25
 
26
- **🚀 Developer-First API**
27
- - **4 core methods, infinite patterns**: `queue()`, `push()`, `take()`, `ack()`
28
- - **Async iteration**: Process messages with familiar `for await` syntax
29
- - **Batch processing**: Use `takeBatch()` for 250k+ msg/sec throughput on millions of messages
30
- - **Smart addressing**: `orders/urgent@workers` - queue, partition, and consumer group in one
26
+ ```js
27
+ import { Queen } from 'queen-mq'
31
28
 
32
- **⚡ Production-Ready Performance**
33
- - **100,000+ msg/sec** throughput with cursor-based consumption
34
- - **Constant-time batch operations** - O(batch_size) regardless of queue depth
35
- - **Long polling** for event-driven, real-time message delivery
36
- - **Partition locking** prevents duplicate processing across consumers
37
- - **Connection pooling** and optimized batch operations
38
-
39
- **🏗️ Flexible Architecture**
40
- - **Queue Mode**: Competitive consumption (traditional work queue)
41
- - **Bus Mode**: Pub/sub with consumer groups (event streaming)
42
- - **Mixed Mode**: Combine both patterns in the same system
43
- - **Partitions**: FIFO ordering with parallel processing
44
-
45
- **🔒 Enterprise Features**
46
- - **AES-256-GCM Encryption**: Protect sensitive data at rest
47
- - **Message Retention**: Automatic cleanup policies
48
- - **Message Eviction**: SLA enforcement for time-sensitive tasks
49
- - **Dead Letter Queue**: Handle failed messages gracefully
50
-
51
- **📊 Built-in Observability**
52
- - **Real-time Dashboard**: WebSocket-powered monitoring
53
- - **Rich Analytics**: Throughput, lag, queue depth metrics
54
- - **Message Browser**: Search, inspect, and retry messages
55
- - **System Health**: Database, memory, and performance metrics
56
- - **Cursor Tracking**: Monitor consumption progress per consumer group
57
-
58
- ### Use Cases
59
-
60
- - **Task Queues**: Background jobs, email sending, data processing
61
- - **Event Streaming**: Audit logs, analytics, multi-service event handling
62
- - **Workflow Orchestration**: Multi-stage pipelines, saga patterns
63
- - **Rate Limiting**: Throttle and batch time-sensitive operations
64
- - **Priority Processing**: Handle urgent tasks before routine ones
65
-
66
- ---
67
-
68
- ## 📋 Table of Contents
69
-
70
- - [First Queue](#-first-queue)
71
- - [Quick Start](#-quick-start)
72
- - [Client Examples](#-client-examples)
73
- - [Server Setup](#-server-setup)
74
- - [Core Concepts](#-core-concepts)
75
- - [Cursor-Based Consumption Strategy](#-cursor-based-consumption-strategy)
76
- - [HTTP API Reference](#-http-api-reference)
77
- - [Dashboard](#-dashboard)
78
- - [Configuration](#-configuration)
79
- - [Full Examples](#-full-examples)
80
- - [Testing](#-testing)
81
- - [Contributing](#-contributing)
82
-
83
- ---
84
-
85
- ## First Queue
86
-
87
- ```javascript
88
- import { Queen } from 'queen-mq';
89
-
90
- const client = new Queen({
91
- baseUrls: ['http://localhost:6632']
92
- });
93
-
94
- // Configure queue
95
- await client.queue('tasks', {
96
- leaseTime: 300,
97
- retryLimit: 3
98
- });
99
-
100
- // Push a message
101
- await client.push('tasks', {
102
- action: 'send-email',
103
- to: 'user@example.com'
104
- });
105
-
106
- // Process messages
107
- for await (const message of client.take('tasks')) {
108
- console.log('Processing:', message.data);
109
- await client.ack(message);
110
- }
111
- ```
112
-
113
- That's it! You now have a working message queue system.
114
-
115
- ## 🏃 Quick Start
116
-
117
- ### Prerequisites
118
-
119
- - **Node.js 22+**
120
- - **PostgreSQL 12+**
121
-
122
- ### Installation
123
-
124
- ```bash
125
- # Clone the repository
126
- git clone https://github.com/smartpricing/queen
127
- cd queen
128
-
129
- # Install dependencies
130
- nvm use 22
131
- npm install
132
-
133
- # Initialize database schema
134
- node init-db.js
135
- ```
136
-
137
- ### Running the Server
138
-
139
- **Option 1: Standalone Server (Traditional)**
140
-
141
- ```bash
142
- npm start
143
- ```
144
-
145
- **Option 2: Programmatic Server (New in v0.1.2)**
146
-
147
- ```javascript
148
- import { QueenServer } from 'queen-mq';
149
-
150
- // Start server with default config
151
- const server = await QueenServer();
152
-
153
- // Or with custom options
154
- const server = await QueenServer({
155
- port: 3000,
156
- host: '127.0.0.1'
157
- });
158
-
159
- console.log(`Server running at http://${server.host}:${server.port}`);
160
- ```
161
-
162
- This allows you to embed Queen MQ directly in your application, run multiple instances, or easily start/stop servers in tests. See [PROGRAMMATIC_SERVER.md](./PROGRAMMATIC_SERVER.md) for more details.
163
-
164
- ### Set Environment (Optional)
165
-
166
- ```bash
167
- # Database connection
168
- export PG_USER=postgres
169
- export PG_HOST=localhost
170
- export PG_DB=postgres
171
- export PG_PASSWORD=postgres
172
- export PG_PORT=5432
173
-
174
- # Enable encryption (optional)
175
- export QUEEN_ENCRYPTION_KEY=$(openssl rand -hex 32)
176
- ```
177
-
178
- ### Start the Server
179
-
180
- ```bash
181
- npm start
182
- # Server starts on http://localhost:6632
183
- ```
184
-
185
- ---
186
-
187
- ## 💻 Client Examples
188
-
189
- The Queen client provides a minimalist API with just 4 methods that compose into any messaging pattern you need.
190
-
191
- ### Installation
192
-
193
- ```bash
194
- npm install queen-mq
195
- ```
196
-
197
- ### Basic Usage
198
-
199
- #### 1. Configure a Queue
200
-
201
- ```javascript
202
- import { Queen } from 'queen-mq';
203
-
204
- const client = new Queen({
205
- baseUrls: ['http://localhost:6632'],
206
- timeout: 30000,
207
- retryAttempts: 3
208
- });
209
-
210
- // Configure with options
211
- await client.queue('orders', {
212
- priority: 10, // Higher priority queues processed first
213
- leaseTime: 600, // 10 minutes to process each message
214
- retryLimit: 3, // Retry up to 3 times
215
- delayedProcessing: 0, // No delay (immediate processing)
216
- });
217
-
218
- // Configure with namespace and task for grouping
219
- await client.queue('order-processing', {
220
- priority: 10
221
- }, {
222
- namespace: 'ecommerce',
223
- task: 'checkout'
224
- });
225
- ```
226
-
227
- #### 2. Push Messages
228
-
229
- ```javascript
230
- // Single message
231
- await client.push('orders', {
232
- orderId: 12345,
233
- amount: 99.99
234
- });
235
-
236
- // To a specific partition
237
- await client.push('orders/urgent', {
238
- orderId: 12346,
239
- amount: 999.99,
240
- priority: 'high'
241
- });
242
-
243
- // Batch messages
244
- await client.push('orders', [
245
- { orderId: 12347, amount: 49.99 },
246
- { orderId: 12348, amount: 79.99 },
247
- { orderId: 12349, amount: 29.99 }
248
- ]);
249
-
250
- // With message properties
251
- await client.push('orders', {
252
- orderId: 12350,
253
- amount: 199.99
254
- }, {
255
- transactionId: 'txn-12350', // For idempotency
256
- traceId: '550e8400-e29b-41d4-a716-446655440000' // Valid UUID for tracing
257
- });
258
- ```
259
-
260
- #### 3. Take Messages (Async Iterator)
261
-
262
- ```javascript
263
- // Process continuously with long polling
264
- for await (const message of client.take('orders', {
265
- wait: true, // Enable long polling
266
- timeout: 30000, // 30 second timeout
267
- batch: 10 // Fetch up to 10 at once
268
- })) {
269
- try {
270
- await processOrder(message.data);
271
- await client.ack(message); // Success
272
- } catch (error) {
273
- await client.ack(message, false, { error: error.message }); // Failure
274
- }
275
- }
276
-
277
- // Process limited messages
278
- for await (const message of client.take('orders', { limit: 100 })) {
279
- await processOrder(message.data);
280
- await client.ack(message);
281
- }
282
-
283
- // Take from specific partition
284
- for await (const message of client.take('orders/urgent')) {
285
- await processUrgentOrder(message.data);
286
- await client.ack(message);
287
- }
288
-
289
- // Use takeBatch to get arrays of messages (higher throughput)
290
- for await (const messages of client.takeBatch('orders', {
291
- batch: 1000, // Fetch 1000 at a time
292
- wait: true
293
- })) {
294
- // messages is an array of up to 1000 messages
295
- console.log(`Processing batch of ${messages.length} messages`);
296
-
297
- try {
298
- // Process entire batch
299
- await processBatch(messages.map(m => m.data));
300
-
301
- // Acknowledge entire batch at once (efficient!)
302
- await client.ack(messages); // Pass array for batch ack
303
- } catch (error) {
304
- // Mark entire batch as failed
305
- await client.ack(messages, false, { error: error.message });
306
- }
307
- }
308
- ```
309
-
310
- #### 4. Acknowledge Messages
311
-
312
- ```javascript
313
- // Acknowledge success
314
- await client.ack(message);
315
- // or
316
- await client.ack(message, true);
317
-
318
- // Acknowledge failure (will retry based on retryLimit)
319
- await client.ack(message, false);
320
-
321
- // Acknowledge with error context
322
- await client.ack(message, false, {
323
- error: 'Payment gateway timeout'
29
+ const client = new Queen({
30
+ baseUrls: ['http://localhost:6632'],
31
+ timeout: 30000,
32
+ retryAttempts: 3
324
33
  });
325
34
 
326
- // Acknowledge using transaction ID
327
- await client.ack('4dfb0478-655b-4c91-bcd9-b7acacf0400f', true);
328
-
329
- // Request explicit retry
330
- await client.ack(message, 'retry');
331
- ```
332
-
333
- ### Address Notation
334
-
335
- Queen uses a simple addressing scheme that encodes queue, partition, and consumer group:
336
-
337
- ```javascript
338
- // Basic addresses
339
- 'orders' // Queue (Default partition)
340
- 'orders/urgent' // Queue with specific partition
341
- 'orders@workers' // Queue with consumer group (bus mode)
342
- 'orders/urgent@workers' // Full address: queue + partition + group
343
-
344
- // Namespace/task filtering (cross-queue consumption)
345
- 'namespace:ecommerce' // All queues in namespace
346
- 'task:checkout' // All queues with task
347
- 'namespace:ecommerce/task:checkout' // Combined filter
348
- 'namespace:ecommerce/task:checkout@audit' // With consumer group
349
- ```
350
-
351
- ### Consumer Patterns
352
-
353
- #### Continuous Processing (Long Polling)
354
-
355
- ```javascript
356
- // Efficient real-time processing
357
- for await (const message of client.take('tasks', {
358
- wait: true, // Long polling - waits for messages
359
- timeout: 30000 // Server timeout
360
- })) {
361
- await processTask(message.data);
362
- await client.ack(message);
363
- }
364
- ```
365
-
366
- #### Batch Processing
367
-
368
- ```javascript
369
- // Method 1: Manual batching with take()
370
- const batch = [];
371
- for await (const message of client.take('analytics', { batch: 100 })) {
372
- batch.push(message);
373
-
374
- if (batch.length >= 100) {
375
- await processBatch(batch.map(m => m.data));
376
-
377
- // Acknowledge all
378
- for (const msg of batch) {
379
- await client.ack(msg);
380
- }
381
- batch.length = 0;
382
- }
383
- }
384
-
385
- // Method 2: Direct batch processing with takeBatch() (RECOMMENDED)
386
- for await (const messages of client.takeBatch('analytics', { batch: 1000 })) {
387
- // messages is already an array!
388
- await processBatch(messages.map(m => m.data));
389
-
390
- // Single batch acknowledgment (much faster!)
391
- await client.ack(messages);
392
- }
393
- ```
394
-
395
- #### Parallel Processing with Partitions
396
-
397
- ```javascript
398
- // Create workers for parallel processing
399
- const partitions = ['worker-1', 'worker-2', 'worker-3', 'worker-4'];
400
-
401
- // Distribute messages across partitions
402
- for (let i = 0; i < messages.length; i++) {
403
- const partition = partitions[i % partitions.length];
404
- await client.push(`tasks/${partition}`, messages[i]);
405
- }
406
-
407
- // Each worker processes its own partition (in parallel)
408
- async function worker(partition) {
409
- for await (const msg of client.take(`tasks/${partition}`)) {
410
- await processTask(msg.data);
411
- await client.ack(msg);
412
- }
413
- }
414
-
415
- // Start all workers
416
- await Promise.all(partitions.map(p => worker(p)));
417
- ```
418
-
419
- #### Consumer Groups (Bus Mode)
420
-
421
- ```javascript
422
- // Multiple services process the same messages independently
423
-
424
- // Analytics service
425
- for await (const event of client.take('events@analytics')) {
426
- await updateAnalytics(event.data);
427
- await client.ack(event, true, { group: 'analytics' });
428
- }
429
-
430
- // Monitoring service (gets same messages)
431
- for await (const event of client.take('events@monitoring')) {
432
- await checkThresholds(event.data);
433
- await client.ack(event, true, { group: 'monitoring' });
434
- }
35
+ const queue = 'html-processing'
435
36
 
436
- // Audit service (also gets same messages)
437
- for await (const event of client.take('events@audit')) {
438
- await logToAudit(event.data);
439
- await client.ack(event, true, { group: 'audit' });
440
- }
441
- ```
442
-
443
- #### Subscription Modes
444
-
445
- ```javascript
446
- // Start from all existing messages (replay)
447
- for await (const event of client.take('events@replay-service', {
448
- subscriptionMode: 'all'
449
- })) {
450
- await replayEvent(event.data);
451
- await client.ack(event);
452
- }
37
+ // Create a queue
38
+ await client.queue(queue, { leaseTime: 30 });
453
39
 
454
- // Start from new messages only (real-time)
455
- for await (const event of client.take('events@realtime', {
456
- subscriptionMode: 'new'
457
- })) {
458
- await processEvent(event.data);
459
- await client.ack(event);
460
- }
40
+ // Push some data, specifyng the partition
41
+ await client.push(`${queue}/customer-1828`, [ { id: 1 } ]);
461
42
 
462
- // Start from specific timestamp
463
- for await (const event of client.take('events@historical', {
464
- subscriptionMode: 'from',
465
- subscriptionFrom: '2024-01-01T00:00:00Z'
466
- })) {
467
- await processHistoricalEvent(event.data);
468
- await client.ack(event);
43
+ // Consume data with iterators
44
+ for await (const msg of client.take(queue, { limit: 1 })) {
45
+ console.log(msg.data.id)
46
+ await client.ack(msg) // OR await client.ack(msg, false) for nack
469
47
  }
470
- ```
471
48
 
472
- #### Error Handling
473
-
474
- ```javascript
475
- // Robust error handling with retries
476
- for await (const message of client.take('critical-tasks')) {
477
- let retries = 3;
478
-
479
- while (retries > 0) {
480
- try {
481
- await processTask(message.data);
482
- await client.ack(message);
483
- break;
484
- } catch (error) {
485
- retries--;
486
- if (retries === 0) {
487
- console.error('Task failed after retries:', error);
488
- await client.ack(message, false, { error: error.message });
489
- } else {
490
- await new Promise(r => setTimeout(r, 1000 * (4 - retries)));
491
- }
492
- }
493
- }
49
+ // Consume data with iterators, getting the entire batch
50
+ for await (const messages of client.takeBatch(queue, { limit: 1, batch: 10, wait: true, timeout: 2000 })) {
51
+ const newMex = messages.map(x => x.data.id * 2)
52
+ await client.ack(messages) // OR await client.ack(messages, false) for nack
494
53
  }
495
- ```
496
-
497
- #### Graceful Shutdown
498
-
499
- ```javascript
500
- let running = true;
501
-
502
- process.on('SIGTERM', () => {
503
- console.log('Shutting down gracefully...');
504
- running = false;
505
- });
506
54
 
507
- for await (const message of client.take('orders')) {
508
- if (!running) break;
509
-
510
- await processOrder(message.data);
511
- await client.ack(message);
55
+ // Consume data with a consumer group
56
+ for await (const msg of client.take(`${queue}@analytics-data`, { limit: 2, batch: 2 })) {
57
+ // Do your computation and than ack with consumer group
58
+ await client.ack(msg, true, { group: 'analytics-data' });
512
59
  }
513
60
 
514
- await client.close();
515
- console.log('Shutdown complete');
61
+ // Consume data from any partition of the queue, continusly
62
+ // This "pipeline" is useful for doing exactly one processing
63
+ await client
64
+ .pipeline(queue)
65
+ .withAutoRenewal({
66
+ interval: 5000 // Renew lease every 5 seconds
67
+ })
68
+ .withConcurrency(5) // Five parallel promises
69
+ .take(10, {
70
+ wait: true, // Use long polling
71
+ timeout: 30000 // Long polling length in millisconds
72
+ })
73
+ .processBatch(async (messages) => {
74
+ return messages.map(x => x.data.id * 2);
75
+ })
76
+ .atomically((tx, originalMessages, processedMessages) => { // ack and push are transactional inside atomically
77
+ tx.ack(originalMessages);
78
+ tx.push('another-queue', processedMessages);
79
+ })
80
+ .repeat({ continuous: true })
81
+ .execute();
516
82
  ```
517
83
 
518
- ---
84
+ ## 📚 Examples
519
85
 
520
- ## 🖥️ Server Setup
86
+ ### Basic Usage
87
+ - **[Basic Queue Operations](examples/01-basic-usage.js)** - Create queue, push, take, and ack messages
88
+ - **[Batch Operations](examples/02-batch-operations.js)** - Push and consume messages in batches
521
89
 
522
- ### Single Server
90
+ ### Advanced Features
91
+ - **[Queue Configuration](examples/06-queue-configuration.js)** - Configure maxSize, windowBuffer, delay, retryLimit, and priority
92
+ - **[Delayed Processing](examples/04-delayed-processing.js)** - Process messages after a delay
93
+ - **[Window Buffer](examples/05-window-buffer.js)** - Delay message availability after push
523
94
 
524
- ```bash
525
- # Start the server
526
- npm start
527
-
528
- # Or with custom configuration
529
- PORT=6632 \
530
- DB_POOL_SIZE=20 \
531
- QUEEN_ENCRYPTION_KEY=$(openssl rand -hex 32) \
532
- npm start
533
- ```
95
+ ### Filtering & Routing
96
+ - **[Namespace & Task Filtering](examples/07-namespace-task-filtering.js)** - Route and filter messages by namespace and task
97
+ - **[Consumer Groups](examples/08-consumer-groups.js)** - Multiple consumer groups processing same messages
534
98
 
535
- ### Multi-Server (Load Balanced)
99
+ ### Pipelines
100
+ - **[Transactional Pipelines](examples/03-transactional-pipeline.js)** - Atomic processing with ack and push in a transaction
536
101
 
537
- Queen supports running multiple servers for high availability and load distribution:
102
+ ### Event Streaming (QoS 0)
103
+ - **[Event Streaming](examples/09-event-streaming.js)** - At-most-once delivery with buffering and auto-ack
538
104
 
539
- ```bash
540
- # Server 1
541
- PORT=6632 WORKER_ID=server-1 npm start
105
+ ## QoS 0: At-Most-Once Event Streaming
542
106
 
543
- # Server 2
544
- PORT=6633 WORKER_ID=server-2 npm start
107
+ For high-throughput event streams, Queen supports **at-most-once delivery** with server-side buffering and auto-acknowledgment.
545
108
 
546
- # Server 3
547
- PORT=6634 WORKER_ID=server-3 npm start
548
- ```
109
+ ### Server-Side Buffering
549
110
 
550
- Client configuration:
111
+ Batch events on the server for 10-100x reduction in database writes:
551
112
 
552
113
  ```javascript
553
- const client = new Queen({
554
- baseUrls: [
555
- 'http://localhost:6632',
556
- 'http://localhost:6633',
557
- 'http://localhost:6634'
558
- ],
559
- loadBalancingStrategy: 'ROUND_ROBIN', // or 'RANDOM', 'LEAST_CONNECTIONS'
560
- enableFailover: true
114
+ // Push with buffering (QoS 0)
115
+ await client.push('metrics', { cpu: 45, memory: 67 }, {
116
+ bufferMs: 100, // Server batches for 100ms
117
+ bufferMax: 100 // Or until 100 events
561
118
  });
562
- ```
563
-
564
- ### Docker Deployment
565
-
566
- ```dockerfile
567
- FROM node:22-alpine
568
-
569
- WORKDIR /app
570
- COPY package*.json ./
571
- RUN npm ci --production
572
-
573
- COPY . .
574
-
575
- EXPOSE 6632
576
- CMD ["node", "src/server.js"]
577
- ```
578
-
579
- ```yaml
580
- # docker-compose.yml
581
- version: '3.8'
582
-
583
- services:
584
- postgres:
585
- image: postgres:16
586
- environment:
587
- POSTGRES_DB: queen
588
- POSTGRES_USER: queen
589
- POSTGRES_PASSWORD: queen
590
- volumes:
591
- - postgres_data:/var/lib/postgresql/data
592
- ports:
593
- - "5432:5432"
594
-
595
- queen:
596
- build: .
597
- ports:
598
- - "6632:6632"
599
- environment:
600
- PG_HOST: postgres
601
- PG_DB: queen
602
- PG_USER: queen
603
- PG_PASSWORD: queen
604
- DB_POOL_SIZE: 20
605
- QUEEN_ENCRYPTION_KEY: ${QUEEN_ENCRYPTION_KEY}
606
- depends_on:
607
- - postgres
608
-
609
- volumes:
610
- postgres_data:
611
- ```
612
-
613
- ### Environment Variables
614
-
615
- See the [Configuration](#-configuration) section for a complete list of environment variables.
616
-
617
- ### Database Schema
618
-
619
- The database schema is automatically created when you run:
620
-
621
- ```bash
622
- node init-db.js
623
- ```
624
-
625
- This creates:
626
- - `queen.queues` - Top-level message containers
627
- - `queen.partitions` - Subdivisions within queues (FIFO ordering)
628
- - `queen.messages` - Individual messages with processing state
629
-
630
- ---
631
-
632
- ## 💡 Core Concepts
633
-
634
- ### Architecture
635
-
636
- Queen uses a two-tier architecture:
637
-
638
- ```
639
- Queues (optional namespace/task grouping)
640
- └── Partitions (FIFO ordering, parallel processing)
641
- └── Messages (lease-based processing)
642
- ```
643
-
644
- **Key Principles:**
645
- - **Configuration at queue level**: All settings (priority, lease time, retries) apply to the entire queue
646
- - **FIFO within partitions**: Messages in the same partition are always processed in order
647
- - **Partition locking**: Prevents duplicate processing across consumers
648
- - **Lease-based processing**: Messages automatically return to pending if not acknowledged
649
-
650
- ### Queues and Partitions
651
-
652
- **Queues** are top-level organizational units. Each queue automatically gets a "Default" partition, and you can create additional partitions for logical separation or parallel processing.
653
-
654
- ```javascript
655
- // Messages go to "Default" partition
656
- await client.push('orders', { orderId: 123 });
657
-
658
- // Push to specific partition
659
- await client.push('orders/high-priority', { orderId: 456 });
660
-
661
- // Take from specific partition
662
- for await (const order of client.take('orders/high-priority')) {
663
- await processUrgentOrder(order.data);
664
- await client.ack(order);
665
- }
666
- ```
667
-
668
- **Partitions enable:**
669
- - **Parallel processing**: Different consumers can process different partitions simultaneously
670
- - **Ordered processing**: FIFO guarantees within each partition
671
- - **Logical separation**: Different priorities, teams, or workflow stages
672
- - **Resource isolation**: Lock contention is per-partition
673
-
674
- ### Message Lifecycle
675
-
676
- ```
677
- pending → processing → completed/failed → (retry) → dead_letter
678
- ```
679
-
680
- 1. **Pending**: Message queued, waiting to be processed
681
- 2. **Processing**: Leased to a worker (with timeout)
682
- 3. **Completed**: Successfully processed
683
- 4. **Failed**: Processing failed (may retry based on `retryLimit`)
684
- 5. **Dead Letter**: Exceeded retry limits
685
-
686
- ### Partition Locking
687
119
 
688
- **Partition locking ensures message processing isolation** - when a consumer retrieves messages from a partition, that partition is locked to prevent other consumers from accessing it until:
689
-
690
- - The consumer acknowledges all messages (releases lock)
691
- - The lease expires (automatic release)
692
- - The consumer explicitly releases the partition
693
-
694
- **Lock Scope:**
695
- - **Queue Mode**: Each consumer session is unique - locks prevent any other consumer from accessing the partition
696
- - **Bus Mode**: Locks are per consumer group - different groups can process the same partition independently
697
-
698
- ```javascript
699
- // Example: Partition locking in action
700
-
701
- // Consumer 1 takes from partition A (locks it)
702
- for await (const msg of client.take('orders', { limit: 5 })) {
703
- // Processing partition A - no other consumer can access it
704
- await client.ack(msg);
705
- // Partition A unlocked after all 5 messages acknowledged
706
- }
707
-
708
- // Consumer 2 gets messages from partition B (A was locked)
709
- for await (const msg of client.take('orders', { limit: 5 })) {
710
- // Processing partition B instead
711
- await client.ack(msg);
712
- }
120
+ // Result: 1000 events = ~10 DB writes (instead of 1000)
713
121
  ```
714
122
 
715
- ### FIFO Ordering
123
+ ### Auto-Acknowledgment
716
124
 
717
- Queen provides **strong FIFO guarantees within each partition**:
125
+ Skip manual ack for fire-and-forget consumption:
718
126
 
719
127
  ```javascript
720
- // These messages will be processed in order 1, 2, 3
721
- await client.push('tasks/user-123', [
722
- { step: 1, action: 'create' },
723
- { step: 2, action: 'update' },
724
- { step: 3, action: 'complete' }
725
- ]);
726
-
727
- // Consumer will always receive them in order
728
- for await (const task of client.take('tasks/user-123')) {
729
- console.log(task.data.step); // Prints: 1, then 2, then 3
730
- await client.ack(task);
128
+ // Consume with auto-ack
129
+ for await (const msg of client.take('metrics', { autoAck: true })) {
130
+ updateDashboard(msg.data);
131
+ // No ack() needed - automatically acknowledged!
731
132
  }
732
133
  ```
733
134
 
734
- **Use cases:**
735
- - **Per-user operations**: Use user ID as partition for ordered processing
736
- - **Per-resource operations**: Use resource ID to maintain operation order
737
- - **Workflow stages**: Use partition to represent different stages
135
+ ### Fan-Out Pattern (Consumer Groups)
738
136
 
739
- ### Consumer Groups (Bus Mode)
740
-
741
- Consumer groups enable **pub-sub messaging** where multiple independent consumers process the same messages:
137
+ Combine buffering + auto-ack + consumer groups for pub/sub:
742
138
 
743
139
  ```javascript
744
- // Push once
745
- await client.push('events', { type: 'order.created', orderId: 123 });
746
-
747
- // Multiple services consume independently
748
- // Service 1: Analytics
749
- for await (const event of client.take('events@analytics')) {
750
- await updateAnalytics(event.data);
751
- await client.ack(event);
752
- }
140
+ // Publisher (buffered)
141
+ await client.push('events', { action: 'login' }, { bufferMs: 100 });
753
142
 
754
- // Service 2: Notification (gets same message)
755
- for await (const event of client.take('events@notification')) {
756
- await sendNotification(event.data);
757
- await client.ack(event);
143
+ // Multiple subscribers (each group gets all messages)
144
+ for await (const e of client.take('events@dashboard', { autoAck: true })) {
145
+ updateUI(e.data);
758
146
  }
759
147
 
760
- // Service 3: Audit (also gets same message)
761
- for await (const event of client.take('events@audit')) {
762
- await logEvent(event.data);
763
- await client.ack(event);
148
+ for await (const e of client.take('events@analytics', { autoAck: true })) {
149
+ trackEvent(e.data);
764
150
  }
765
151
  ```
766
152
 
767
- **Each consumer group maintains:**
768
- - Independent message status tracking
769
- - Separate partition leases
770
- - Individual retry counters
771
- - Isolated processing state
772
-
773
- ### Queue Mode vs Bus Mode
774
-
775
- **Queue Mode** (default - competitive consumption):
776
- ```javascript
777
- // Without consumer group - messages distributed
778
- await client.push('tasks', { id: 1 });
779
- await client.push('tasks', { id: 2 });
780
-
781
- // Worker 1 gets message 1
782
- for await (const msg of client.take('tasks')) { }
783
-
784
- // Worker 2 gets message 2 (different message)
785
- for await (const msg of client.take('tasks')) { }
786
- ```
787
-
788
- **Bus Mode** (pub-sub with consumer groups):
789
- ```javascript
790
- // With consumer groups - all groups see all messages
791
- await client.push('events', { id: 1 });
153
+ ### PostgreSQL Failover
792
154
 
793
- // Group 1 gets message 1
794
- for await (const msg of client.take('events@group1')) { }
155
+ Queen automatically buffers messages to disk when PostgreSQL is unavailable - **zero message loss**:
795
156
 
796
- // Group 2 also gets message 1 (same message)
797
- for await (const msg of client.take('events@group2')) { }
798
- ```
157
+ - Normal pushes go directly to PostgreSQL (FIFO preserved)
158
+ - If PostgreSQL is down, messages buffered to file (macOS: `/tmp/queen`, Linux: `/var/lib/queen/buffers`)
159
+ - Automatic replay when PostgreSQL recovers
160
+ - Survives server crashes and restarts
161
+ - Directory auto-created on first run
799
162
 
800
- **Mixed Mode** (combine both):
801
- ```javascript
802
- // Competitive workers process jobs
803
- for await (const job of client.take('jobs')) {
804
- await processJob(job.data);
805
- await client.ack(job);
806
- }
163
+ **No configuration needed** - failover is automatic!
807
164
 
808
- // Monitoring sees all jobs (bus mode)
809
- for await (const job of client.take('jobs@monitoring')) {
810
- await monitorJob(job.data);
811
- await client.ack(job);
812
- }
165
+ **Custom directory:**
166
+ ```bash
167
+ FILE_BUFFER_DIR=/custom/path ./bin/queen-server
813
168
  ```
814
169
 
815
- ### Priority Processing
816
-
817
- Configure priority at the **queue level**:
170
+ ### When to Use
818
171
 
819
- ```javascript
820
- await client.queue('urgent-orders', { priority: 100 });
821
- await client.queue('normal-orders', { priority: 50 });
822
- await client.queue('batch-jobs', { priority: 10 });
823
-
824
- // Urgent orders processed first, then normal, then batch
825
- ```
172
+ | Feature | Use For | Don't Use For |
173
+ |---------|---------|---------------|
174
+ | **Buffering** | Metrics, logs, analytics, UI updates | Critical tasks, payments |
175
+ | **Auto-Ack** | Fire-and-forget events, notifications | Tasks requiring retry logic |
176
+ | **Failover** | Everything (automatic) | N/A - always beneficial |
826
177
 
827
- ### Lease-Based Processing
178
+ ## Webapp
828
179
 
829
- Messages are "leased" to workers for a specific duration. If not acknowledged within the lease time, they automatically return to pending status:
180
+ A modern Vue 3 web interface for managing and monitoring Queen MQ.
830
181
 
831
- ```javascript
832
- // Configure lease time
833
- await client.queue('long-tasks', {
834
- leaseTime: 600 // 10 minutes to process
835
- });
182
+ **Features:**
183
+ - 📊 Real-time dashboard with system metrics
184
+ - 📈 Message throughput visualization
185
+ - 🔍 Queue management and monitoring
186
+ - 👥 Consumer group tracking
187
+ - 💬 Message browser
188
+ - 📉 Analytics and insights
189
+ - 🌓 Dark/light theme support
836
190
 
837
- // If worker crashes or takes too long:
838
- // - After 10 minutes, lease expires
839
- // - Message returns to pending
840
- // - Another worker can pick it up
191
+ **Quick Start:**
192
+ ```bash
193
+ cd webapp
194
+ npm install
195
+ npm run dev
841
196
  ```
842
197
 
843
- ### Delayed Processing
844
-
845
- Schedule messages for future processing:
198
+ The dashboard will be available at `http://localhost:4000`
846
199
 
847
- ```javascript
848
- await client.queue('scheduled-jobs', {
849
- delayedProcessing: 3600 // 1 hour delay
850
- });
851
-
852
- await client.push('scheduled-jobs', {
853
- reportType: 'daily-sales'
854
- });
855
- // Message won't be available for processing until 1 hour later
856
- ```
857
-
858
- ### Window Buffering
200
+ See [webapp/README.md](webapp/README.md) for more details.
859
201
 
860
- Batch messages within a time window:
202
+ ## Install server and configure it
861
203
 
862
- ```javascript
863
- await client.queue('analytics', {
864
- windowBuffer: 60 // Wait 60 seconds to accumulate messages
865
- });
204
+ ### Quick Start
866
205
 
867
- // Messages held for 60 seconds to allow efficient batching
206
+ ```sh
207
+ cd server
208
+ make clean
209
+ make deps
210
+ make build-only
211
+ DB_POOL_SIZE=50 ./bin/queen-server
868
212
  ```
869
213
 
870
- ### Retry and Dead Letter Queue
214
+ **📖 Complete Build & Tuning Guide:** [server/README.md](server/README.md)
871
215
 
872
- ```javascript
873
- await client.queue('payments', {
874
- retryLimit: 3, // Retry up to 3 times
875
- dlqAfterMaxRetries: true // Move to DLQ after max retries
876
- });
877
-
878
- // Failed messages automatically retry
879
- await client.ack(message, false); // Will retry if retries < 3
880
-
881
- // After 3 failures, message moves to dead_letter status
882
- ```
216
+ Includes:
217
+ - Build instructions and optimization
218
+ - Performance tuning (worker threads, database pool)
219
+ - Production deployment (systemd, Docker, load balancing)
220
+ - Troubleshooting common issues
221
+ - Benchmarking guides
883
222
 
884
- ### Enterprise Features
223
+ ### Environment Variables
885
224
 
886
- #### 1. Encryption (AES-256-GCM)
225
+ [The full list of environment variables is here](server/ENV_VARIABLES.md)
887
226
 
888
- ```bash
889
- # Generate encryption key
890
- export QUEEN_ENCRYPTION_KEY=$(openssl rand -hex 32)
227
+ ### With Docker
228
+ ```sh
229
+ ./build.sh
891
230
  ```
892
231
 
893
- ```javascript
894
- await client.queue('sensitive-data', {
895
- encryptionEnabled: true
896
- });
232
+ ### Running on k8s
897
233
 
898
- // Messages encrypted at rest in database
899
- await client.push('sensitive-data', { ssn: '123-45-6789' });
900
- ```
234
+ [Running in k8s](server/k8s-example.yaml)
901
235
 
902
- #### 2. Message Retention
236
+ ## 🔌 Raw HTTP API
903
237
 
904
- ```javascript
905
- await client.queue('temp-queue', {
906
- retentionSeconds: 3600, // Delete pending after 1 hour
907
- completedRetentionSeconds: 300, // Delete completed after 5 minutes
908
- retentionEnabled: true
909
- });
910
- ```
238
+ You can use Queen directly from HTTP without the JS client.
911
239
 
912
- #### 3. Message Eviction (SLA Enforcement)
240
+ [Here the complete list of API endpoints](API.md)
913
241
 
914
- ```javascript
915
- await client.queue('time-sensitive', {
916
- maxWaitTimeSeconds: 60 // Evict messages older than 1 minute
917
- });
918
- ```
242
+ ## ⚠️ Known Issues & Roadmap
919
243
 
920
- ### Best Practices
244
+ ### Server Startup Timing (Critical)
245
+ **Issue:** Worker initialization timeout (30s → 3600s) now matches file buffer recovery timeout. This is a temporary fix.
921
246
 
922
- **1. Partition Strategy**
923
- - Use user IDs for per-user ordering
924
- - Use resource IDs for per-resource ordering
925
- - Use round-robin for load distribution
926
- - Keep partition counts manageable (10-100s, not 1000s)
247
+ **Problem:** During startup, if the file buffer has many events to recover (e.g., after a long PostgreSQL outage), Worker 0 performs blocking recovery that can take up to 1 hour. The acceptor waits for all workers to initialize before starting to accept connections.
927
248
 
928
- **2. Lease Management**
929
- - Set lease time slightly longer than expected processing time
930
- - Handle timeouts gracefully
931
- - Acknowledge messages as soon as processing completes
249
+ **Current Fix:** Worker initialization timeout increased to 3600s to prevent premature timeout.
932
250
 
933
- **3. Consumer Group Design**
934
- - One clear purpose per consumer group
935
- - Design groups to be independent
936
- - Ensure operations are idempotent
251
+ **Better Solution Needed:**
252
+ - Make recovery non-blocking while preserving FIFO ordering guarantees
253
+ - Implement progressive readiness with memory-buffered queue during recovery
254
+ - Add configurable recovery timeout with graceful degradation
255
+ - See: `server/src/services/file_buffer.cpp:212` (MAX_STARTUP_RECOVERY_SECONDS)
256
+ - See: `server/src/acceptor_server.cpp:1876` (worker initialization timeout)
937
257
 
938
- **4. Error Handling**
939
- - Always wrap processing in try-catch
940
- - Provide meaningful error messages in ack
941
- - Use retry limits appropriately
942
- - Monitor dead letter queue
943
-
944
- ---
945
-
946
- ## 🚀 Cursor-Based Consumption Strategy
947
-
948
- Queen uses a **cursor-based consumption model** for optimal performance at scale, providing O(batch_size) constant-time operations regardless of queue depth.
949
-
950
- ### How It Works
951
-
952
- Traditional message queues scan through all messages to find pending ones, leading to performance degradation as messages accumulate. Queen's cursor-based approach maintains a position marker (cursor) for each consumer, allowing direct access to the next batch of messages.
953
-
954
- **Partition Cursors:**
955
-
956
- Each partition maintains a cursor position per consumer group:
957
- - `last_consumed_created_at`: Timestamp of last consumed message
958
- - `last_consumed_id`: UUID of last consumed message (tie-breaker for same timestamp)
959
- - `total_messages_consumed`: Running count of consumed messages
960
-
961
- The cursor always moves **forward** in time, ensuring strict FIFO ordering.
962
-
963
- ### The takeBatch Method
964
-
965
- Queen provides two consumption methods:
966
-
967
- **1. `take()` - Individual message iterator:**
968
- ```javascript
969
- // Processes messages one at a time
970
- for await (const message of client.take('orders', { batch: 1000 })) {
971
- await processOrder(message.data);
972
- await client.ack(message);
973
- }
974
- ```
975
-
976
- **2. `takeBatch()` - Array iterator (HIGH PERFORMANCE):**
977
- ```javascript
978
- // Yields arrays of messages - achieves 100k+ msg/s throughput
979
- for await (const messages of client.takeBatch('orders', { batch: 1000 })) {
980
- // messages is an array of up to 1000 message objects
981
- await processBatch(messages.map(m => m.data));
982
-
983
- // Batch acknowledge - single DB transaction for all messages
984
- await client.ack(messages);
985
- }
986
- ```
987
-
988
- **Under the hood**, both methods use cursor-based batch retrieval:
989
-
990
- ```sql
991
- -- Cursor-based query (simplified)
992
- SELECT * FROM messages
993
- WHERE partition_id = $1
994
- AND id > $2::uuid -- Start after last cursor position
995
- ORDER BY created_at ASC, id ASC
996
- LIMIT $3 -- Batch size
997
- FOR UPDATE SKIP LOCKED
998
- ```
999
-
1000
- **Key characteristics:**
1001
- 1. **Constant-time**: Performance stays consistent whether you've consumed 0% or 99% of messages
1002
- 2. **FIFO guarantee**: Messages always returned in creation order
1003
- 3. **Lock-free scanning**: `SKIP LOCKED` prevents contention between consumers
1004
- 4. **Efficient**: No table scans - direct cursor-based access using UUIDv7 (time-ordered)
1005
-
1006
- **Performance tip:** Use `takeBatch()` with large batch sizes (1,000-10,000) for maximum throughput. The server fetches messages in batches regardless, but `takeBatch()` gives you the array directly, allowing bulk processing and batch acknowledgment in a single operation.
1007
-
1008
- ### Batch Acknowledgment Semantics
1009
-
1010
- Queen handles batch acknowledgments intelligently:
1011
-
1012
- **Partial Success** (some messages succeed, some fail):
1013
- ```javascript
1014
- // Batch: 10,000 messages
1015
- // Success: 9,999 messages
1016
- // Failed: 1 message
1017
-
1018
- // Behavior:
1019
- // ✅ Cursor advances past all 10,000 messages
1020
- // ✅ Failed message moved to Dead Letter Queue
1021
- // ✅ Next take() starts from message 10,001
1022
- // ✅ FIFO maintained, no redelivery of successful messages
1023
- ```
1024
-
1025
- **Total Batch Failure** (all messages fail):
1026
- ```javascript
1027
- // Batch: 10,000 messages
1028
- // Success: 0 messages
1029
- // Failed: 10,000 messages
1030
-
1031
- // Behavior:
1032
- // ❌ Cursor DOES NOT advance
1033
- // ❌ Messages NOT moved to DLQ
1034
- // ✅ Lease released
1035
- // ✅ Next take() gets SAME batch (retry)
1036
- // ✅ Allows recovery from transient failures
1037
- ```
1038
-
1039
- This design handles transient failures (network issues, service outages) gracefully while preventing poison messages from blocking the queue.
1040
-
1041
- ### Performance Comparison
1042
-
1043
- | Operation | Traditional Approach | Cursor Approach | Improvement |
1044
- |-----------|---------------------|-----------------|-------------|
1045
- | Pop @ 0% consumed | O(partition_size) | O(batch_size) | Same |
1046
- | Pop @ 50% consumed | O(partition_size) | O(batch_size) | **10-100x faster** |
1047
- | Pop @ 99% consumed | O(partition_size) | O(batch_size) | **100-1000x faster** |
1048
-
1049
- **Real-world benchmark** (1M messages):
1050
- ```
1051
- Traditional:
1052
- Early batches: 300ms per pop
1053
- Late batches: 3500ms per pop (10x degradation)
1054
-
1055
- Cursor-based:
1056
- Early batches: 150ms per pop
1057
- Late batches: 200ms per pop (constant!)
1058
- ```
1059
-
1060
- ### Dead Letter Queue
1061
-
1062
- Individual message failures are moved to the Dead Letter Queue for inspection and manual intervention:
1063
-
1064
- ```javascript
1065
- // Monitor DLQ
1066
- const response = await fetch('http://localhost:6632/api/v1/analytics/dlq');
1067
- const dlqMessages = await response.json();
1068
-
1069
- // Inspect failed messages
1070
- for (const msg of dlqMessages) {
1071
- console.log(`Failed: ${msg.error_message}`);
1072
-
1073
- // After fixing issue, can re-push if needed
1074
- await client.push(msg.queue, fixedPayload);
1075
- }
1076
- ```
1077
-
1078
- **DLQ Query:**
1079
- ```sql
1080
- SELECT * FROM queen.dead_letter_queue
1081
- WHERE consumer_group = 'my-group'
1082
- ORDER BY failed_at DESC
1083
- LIMIT 100;
1084
- ```
1085
-
1086
- ### Batch Size Guidelines
1087
-
1088
- Choose batch sizes based on your workload:
1089
-
1090
- **Smaller batches (100-1,000):**
1091
- - ✅ Faster individual batch processing
1092
- - ✅ Less impact if entire batch fails
1093
- - ✅ Lower memory footprint
1094
- - ❌ More network round-trips
1095
-
1096
- **Larger batches (5,000-10,000):**
1097
- - ✅ Higher throughput (100,000+ msg/sec achievable)
1098
- - ✅ Fewer network round-trips
1099
- - ✅ Better database efficiency
1100
- - ❌ More messages retry if entire batch fails
1101
- - ❌ Higher memory usage
1102
-
1103
- **Recommendation:** Start with 1,000-2,000 for balanced performance. Increase to 5,000-10,000 for maximum throughput with reliable processing.
1104
-
1105
- ### Monitoring Cursor Progress
1106
-
1107
- Track consumption progress via SQL:
1108
-
1109
- ```sql
1110
- -- View cursor positions
1111
- SELECT
1112
- p.name as partition,
1113
- pc.consumer_group,
1114
- pc.total_messages_consumed,
1115
- pc.total_batches_consumed,
1116
- pc.last_consumed_at,
1117
- EXTRACT(EPOCH FROM (NOW() - pc.last_consumed_at)) as seconds_since_last_consume
1118
- FROM queen.partition_cursors pc
1119
- JOIN queen.partitions p ON p.id = pc.partition_id
1120
- ORDER BY pc.last_consumed_at DESC;
1121
-
1122
- -- Monitor DLQ
1123
- SELECT COUNT(*) as failed_count, consumer_group
1124
- FROM queen.dead_letter_queue
1125
- GROUP BY consumer_group;
1126
- ```
1127
-
1128
- ---
1129
-
1130
- ## 🔌 HTTP API Reference
1131
-
1132
- Base URL: `http://localhost:6632/api/v1`
1133
-
1134
- ### Push Messages
1135
-
1136
- **Endpoint:** `POST /api/v1/push`
1137
-
1138
- **Request:**
1139
- ```json
1140
- {
1141
- "items": [
1142
- {
1143
- "queue": "orders",
1144
- "partition": "urgent",
1145
- "payload": { "orderId": 123, "amount": 99.99 },
1146
- "transactionId": "optional-idempotency-key",
1147
- "traceId": "550e8400-e29b-41d4-a716-446655440000"
1148
- }
1149
- ]
1150
- }
1151
- ```
1152
-
1153
- **Response:**
1154
- ```json
1155
- {
1156
- "messages": [
1157
- {
1158
- "id": "018e63b7-6165-453f-88ae-56effa177605",
1159
- "transactionId": "4dfb0478-655b-4c91-bcd9-b7acacf0400f",
1160
- "status": "queued"
1161
- }
1162
- ]
1163
- }
1164
- ```
1165
-
1166
- ### Pop Messages
1167
-
1168
- **From specific partition:**
1169
- ```
1170
- GET /api/v1/pop/queue/{queue}/partition/{partition}?wait=true&timeout=30000&batch=10
1171
- ```
1172
-
1173
- **From any partition in queue:**
1174
- ```
1175
- GET /api/v1/pop/queue/{queue}?wait=true&timeout=30000&batch=10
1176
- ```
1177
-
1178
- **With namespace/task filter:**
1179
- ```
1180
- GET /api/v1/pop?namespace=ecommerce&task=checkout&wait=true&timeout=30000&batch=10
1181
- ```
1182
-
1183
- **With consumer group (bus mode):**
1184
- ```
1185
- GET /api/v1/pop/queue/{queue}?consumerGroup=analytics&subscriptionMode=all&wait=true&timeout=30000
1186
- ```
1187
-
1188
- **Response:**
1189
- ```json
1190
- {
1191
- "messages": [
1192
- {
1193
- "id": "018e63b7-6165-453f-88ae-56effa177605",
1194
- "transactionId": "4dfb0478-655b-4c91-bcd9-b7acacf0400f",
1195
- "queue": "orders",
1196
- "partition": "urgent",
1197
- "data": { "orderId": 123, "amount": 99.99 },
1198
- "retryCount": 0,
1199
- "priority": 10,
1200
- "createdAt": "2024-10-08T12:00:00.000Z",
1201
- "options": { "leaseTime": 300, "retryLimit": 3 }
1202
- }
1203
- ]
1204
- }
1205
- ```
1206
-
1207
- ### Acknowledge Messages
1208
-
1209
- **Single:**
1210
- ```json
1211
- POST /api/v1/ack
1212
- {
1213
- "transactionId": "4dfb0478-655b-4c91-bcd9-b7acacf0400f",
1214
- "status": "completed",
1215
- "consumerGroup": "analytics",
1216
- "error": null
1217
- }
1218
- ```
1219
-
1220
- **Batch:**
1221
- ```json
1222
- POST /api/v1/ack/batch
1223
- {
1224
- "acknowledgments": [
1225
- { "transactionId": "uuid-1", "status": "completed" },
1226
- { "transactionId": "uuid-2", "status": "failed", "error": "Processing error" }
1227
- ]
1228
- }
1229
- ```
1230
-
1231
- ### Configure Queue
1232
-
1233
- ```json
1234
- POST /api/v1/configure
1235
- {
1236
- "queue": "orders",
1237
- "namespace": "ecommerce",
1238
- "task": "checkout",
1239
- "options": {
1240
- "leaseTime": 600,
1241
- "retryLimit": 5,
1242
- "priority": 10,
1243
- "maxSize": 10000,
1244
- "ttl": 3600,
1245
- "dlqAfterMaxRetries": true,
1246
- "delayedProcessing": 0,
1247
- "windowBuffer": 0,
1248
- "retentionSeconds": 0,
1249
- "completedRetentionSeconds": 0,
1250
- "retentionEnabled": false,
1251
- "encryptionEnabled": false,
1252
- "maxWaitTimeSeconds": 0
1253
- }
1254
- }
1255
- ```
1256
-
1257
- ### Analytics
1258
-
1259
- **Queue statistics:**
1260
- ```
1261
- GET /api/v1/analytics/queue/{queue}
1262
- ```
1263
-
1264
- **All queues overview:**
1265
- ```
1266
- GET /api/v1/analytics/queues
1267
- ```
1268
-
1269
- **Queue depths:**
1270
- ```
1271
- GET /api/v1/analytics/queue-depths
1272
- ```
1273
-
1274
- **Throughput metrics:**
1275
- ```
1276
- GET /api/v1/analytics/throughput
1277
- ```
1278
-
1279
- **Queue lag analysis:**
1280
- ```
1281
- GET /api/v1/analytics/queue-lag?queue=orders
1282
- ```
1283
-
1284
- ### Message Management
1285
-
1286
- **List messages:**
1287
- ```
1288
- GET /api/v1/messages?queue=orders&status=pending&limit=100
1289
- ```
1290
-
1291
- **Get single message:**
1292
- ```
1293
- GET /api/v1/messages/{transactionId}
1294
- ```
1295
-
1296
- **Delete message:**
1297
- ```
1298
- DELETE /api/v1/messages/{transactionId}
1299
- ```
1300
-
1301
- **Retry failed message:**
1302
- ```
1303
- POST /api/v1/messages/{transactionId}/retry
1304
- ```
1305
-
1306
- **Move to dead letter queue:**
1307
- ```
1308
- POST /api/v1/messages/{transactionId}/dlq
1309
- ```
1310
-
1311
- **Clear queue:**
1312
- ```
1313
- DELETE /api/v1/queues/{queue}/clear
1314
- ```
1315
-
1316
- **Delete queue:**
1317
- ```
1318
- DELETE /api/v1/resources/queues/{queue}
1319
- ```
1320
- _Note: Deletes the queue and all its partitions, messages, and related data._
1321
-
1322
- ### System Health
1323
-
1324
- **Health check:**
1325
- ```
1326
- GET /health
1327
- ```
1328
-
1329
- **Detailed metrics:**
1330
- ```
1331
- GET /metrics
1332
- ```
1333
-
1334
- ### WebSocket (Real-time Updates)
1335
-
1336
- **Connect:**
1337
- ```javascript
1338
- const ws = new WebSocket('ws://localhost:6632/ws/dashboard');
1339
-
1340
- ws.onmessage = (event) => {
1341
- const { event: eventType, data } = JSON.parse(event.data);
1342
- // Handle events: message.pushed, message.completed, queue.depth, etc.
1343
- };
1344
- ```
1345
-
1346
- **Events:**
1347
- - `message.pushed` - New message added
1348
- - `message.processing` - Message being processed
1349
- - `message.completed` - Message completed
1350
- - `message.failed` - Message failed
1351
- - `queue.created` - New queue created
1352
- - `queue.depth` - Queue depth update (every 5s)
1353
- - `system.stats` - System statistics (every 10s)
1354
-
1355
- See [API.md](API.md) for complete API documentation.
1356
-
1357
- ---
1358
-
1359
- ## 📊 Dashboard
1360
-
1361
- Queen includes a comprehensive web dashboard for monitoring and management.
1362
-
1363
- [Dashboard](/assets/dashboard-01.png)
1364
-
1365
- ### Access
1366
-
1367
- 1. Start the server: `npm start`
1368
- 2. Open browser: `http://localhost:4000`
1369
- 3. WebSocket connection provides real-time updates
1370
-
1371
- ### Features
1372
-
1373
- **System Overview**
1374
- - Real-time metrics: total messages, processing rate, system health
1375
- - Queue summary with pending/processing/completed counts
1376
- - Performance indicators: throughput, latency, error rates
1377
-
1378
- **Queue Management**
1379
- - Queue list with status and message counts
1380
- - Partition view with priority indicators
1381
- - Message browser with search and filter
1382
- - Retry and DLQ management
1383
-
1384
- **Real-time Monitoring**
1385
- - Live updates via WebSocket
1386
- - Throughput charts (messages per second over time)
1387
- - Queue depth graphs with trend analysis
1388
- - Lag monitoring (processing time and backlog)
1389
-
1390
- **Analytics Dashboard**
1391
- - Performance metrics per queue
1392
- - Historical trends and patterns
1393
- - System health monitoring
1394
- - Database connections and memory usage
1395
-
1396
- **Message Browser**
1397
- - Search by queue, partition, status, time range
1398
- - View full payload and metadata
1399
- - Manually retry failed messages
1400
- - Dead letter queue management
1401
-
1402
- ### Dashboard Development
1403
-
1404
- The dashboard is built with Vue.js and located in the `dashboard/` directory:
1405
-
1406
- ```bash
1407
- cd dashboard
1408
- npm install
1409
- npm run dev # Development mode
1410
- npm run build # Production build
1411
- ```
1412
-
1413
- ---
1414
-
1415
- ## ⚙️ Configuration
1416
-
1417
- All configuration uses environment variables with sensible defaults. Configuration is centralized in `src/config.js`.
1418
-
1419
- ### Server Configuration
1420
-
1421
- ```bash
1422
- PORT=6632 # Server port (default: 6632)
1423
- HOST=0.0.0.0 # Server host (default: 0.0.0.0)
1424
- WORKER_ID=worker-1 # Worker identifier
1425
- APP_NAME=queen-mq # Application name
1426
-
1427
- # CORS
1428
- CORS_MAX_AGE=86400
1429
- CORS_ALLOWED_ORIGINS=*
1430
- CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,OPTIONS
1431
- CORS_ALLOWED_HEADERS=Content-Type,Authorization
1432
- ```
1433
-
1434
- ### Database Configuration
1435
-
1436
- ```bash
1437
- # Connection
1438
- PG_USER=postgres
1439
- PG_HOST=localhost
1440
- PG_DB=postgres
1441
- PG_PASSWORD=postgres
1442
- PG_PORT=5432
1443
-
1444
- # Connection pool
1445
- DB_POOL_SIZE=20 # Max connections
1446
- DB_IDLE_TIMEOUT=30000 # Idle timeout (ms)
1447
- DB_CONNECTION_TIMEOUT=2000 # Connection timeout (ms)
1448
- DB_STATEMENT_TIMEOUT=30000 # Statement timeout (ms)
1449
- DB_QUERY_TIMEOUT=30000 # Query timeout (ms)
1450
- DB_MAX_RETRIES=3 # Max retry attempts
1451
- ```
1452
-
1453
- ### Queue Processing
1454
-
1455
- ```bash
1456
- # Pop defaults
1457
- DEFAULT_TIMEOUT=30000 # Default pop timeout (ms)
1458
- MAX_TIMEOUT=60000 # Maximum pop timeout (ms)
1459
- DEFAULT_BATCH_SIZE=1 # Default batch size
1460
- BATCH_INSERT_SIZE=1000 # Batch size for bulk inserts
1461
-
1462
- # Long polling
1463
- QUEUE_POLL_INTERVAL=100 # Poll interval (ms)
1464
- QUEUE_POLL_INTERVAL_FILTERED=1000 # Poll interval for filtered pops (ms)
1465
-
1466
- # Queue defaults
1467
- DEFAULT_LEASE_TIME=300 # Lease time (seconds)
1468
- DEFAULT_RETRY_LIMIT=3 # Retry limit
1469
- DEFAULT_RETRY_DELAY=1000 # Retry delay (ms)
1470
- DEFAULT_MAX_SIZE=10000 # Max queue size
1471
- DEFAULT_TTL=3600 # TTL (seconds)
1472
- DEFAULT_PRIORITY=0 # Priority
1473
- DEFAULT_DELAYED_PROCESSING=0 # Delayed processing (seconds)
1474
- DEFAULT_WINDOW_BUFFER=0 # Window buffer (seconds)
1475
- ```
1476
-
1477
- ### Background Jobs
1478
-
1479
- ```bash
1480
- LEASE_RECLAIM_INTERVAL=5000 # Lease reclamation (ms)
1481
- RETENTION_INTERVAL=300000 # Retention checks (ms)
1482
- RETENTION_BATCH_SIZE=1000 # Retention batch size
1483
- PARTITION_CLEANUP_DAYS=7 # Days before cleaning empty partitions
1484
- EVICTION_INTERVAL=60000 # Eviction checks (ms)
1485
- EVICTION_BATCH_SIZE=1000 # Eviction batch size
1486
- ```
1487
-
1488
- ### WebSocket
1489
-
1490
- ```bash
1491
- WS_COMPRESSION=0 # Compression level
1492
- WS_MAX_PAYLOAD_LENGTH=16384 # Max payload (bytes)
1493
- WS_IDLE_TIMEOUT=60 # Idle timeout (seconds)
1494
- WS_MAX_CONNECTIONS=1000 # Max connections
1495
- WS_HEARTBEAT_INTERVAL=30000 # Heartbeat (ms)
1496
- ```
1497
-
1498
- ### Encryption
1499
-
1500
- ```bash
1501
- # Generate key: openssl rand -hex 32
1502
- QUEEN_ENCRYPTION_KEY=<64-hex-chars> # AES-256-GCM encryption key
1503
- ```
1504
-
1505
- ### Client SDK
1506
-
1507
- ```bash
1508
- QUEEN_BASE_URL=http://localhost:6632
1509
- CLIENT_RETRY_ATTEMPTS=3
1510
- CLIENT_RETRY_DELAY=1000
1511
- CLIENT_RETRY_BACKOFF=2
1512
- CLIENT_POOL_SIZE=10
1513
- CLIENT_REQUEST_TIMEOUT=30000
1514
- ```
1515
-
1516
- ### Queue Options
1517
-
1518
- ```javascript
1519
- {
1520
- // Processing
1521
- leaseTime: 300, // Seconds before lease expires
1522
- retryLimit: 3, // Max retry attempts
1523
- priority: 0, // Queue priority (higher = first)
1524
- delayedProcessing: 0, // Delay in seconds
1525
- windowBuffer: 0, // Buffer time for batching
1526
- dlqAfterMaxRetries: true, // Move to DLQ after max retries
1527
-
1528
- // Encryption (Queue-level)
1529
- encryptionEnabled: false, // Enable AES-256-GCM encryption
1530
-
1531
- // Retention (Partition-level)
1532
- retentionSeconds: 0, // Delete pending messages after X seconds
1533
- completedRetentionSeconds: 0, // Delete completed/failed after X seconds
1534
- partitionRetentionSeconds: 0, // Delete empty partitions after X seconds
1535
- retentionEnabled: false, // Enable retention
1536
-
1537
- // Eviction (Queue-level)
1538
- maxWaitTimeSeconds: 0 // Evict messages older than X seconds
1539
- }
1540
- ```
1541
-
1542
- ---
1543
-
1544
- ## 📚 Full Examples
1545
-
1546
- ### Example 1: Email Queue with Priority
1547
-
1548
- ```javascript
1549
- import { Queen } from 'queen-mq';
1550
-
1551
- const client = new Queen({
1552
- baseUrls: ['http://localhost:6632']
1553
- });
1554
-
1555
- // Configure queues with different priorities
1556
- await client.queue('emails-urgent', {
1557
- priority: 10,
1558
- leaseTime: 300,
1559
- retryLimit: 5
1560
- });
1561
-
1562
- await client.queue('emails-normal', {
1563
- priority: 5,
1564
- leaseTime: 300,
1565
- retryLimit: 3
1566
- });
1567
-
1568
- // Producer: Send emails
1569
- async function sendEmails() {
1570
- // Urgent email
1571
- await client.push('emails-urgent', {
1572
- to: 'admin@company.com',
1573
- subject: 'Critical Alert',
1574
- body: 'System issue detected',
1575
- timestamp: Date.now()
1576
- });
1577
-
1578
- // Normal email
1579
- await client.push('emails-normal', {
1580
- to: 'user@example.com',
1581
- subject: 'Welcome',
1582
- body: 'Thanks for signing up',
1583
- timestamp: Date.now()
1584
- });
1585
- }
1586
-
1587
- // Consumer: Process emails
1588
- async function processEmails() {
1589
- // Urgent emails processed first (higher priority)
1590
- for await (const email of client.take('emails-urgent', {
1591
- wait: true,
1592
- timeout: 30000
1593
- })) {
1594
- try {
1595
- console.log('Sending urgent email:', email.data.to);
1596
- await sendEmail(email.data);
1597
- await client.ack(email);
1598
- } catch (error) {
1599
- console.error('Failed to send email:', error);
1600
- await client.ack(email, false, { error: error.message });
1601
- }
1602
- }
1603
- }
1604
-
1605
- // Send batch of emails
1606
- await sendEmails();
1607
-
1608
- // Start processing
1609
- processEmails().catch(console.error);
1610
- ```
1611
-
1612
- ### Example 2: Task Pipeline
1613
-
1614
- ```javascript
1615
- import { Queen } from 'queen-mq';
1616
-
1617
- const client = new Queen({
1618
- baseUrls: ['http://localhost:6632']
1619
- });
1620
-
1621
- // Configure pipeline stages
1622
- await client.queue('stage-1-validate', { priority: 10 });
1623
- await client.queue('stage-2-process', { priority: 9 });
1624
- await client.queue('stage-3-finalize', { priority: 8 });
1625
-
1626
- // Stage 1: Validate
1627
- async function validateStage() {
1628
- for await (const msg of client.take('stage-1-validate', { wait: true })) {
1629
- try {
1630
- const validated = await validate(msg.data);
1631
- await client.ack(msg);
1632
-
1633
- // Pass to next stage
1634
- await client.push('stage-2-process', validated);
1635
- } catch (error) {
1636
- await client.ack(msg, false, { error: error.message });
1637
- }
1638
- }
1639
- }
1640
-
1641
- // Stage 2: Process
1642
- async function processStage() {
1643
- for await (const msg of client.take('stage-2-process', { wait: true })) {
1644
- try {
1645
- const processed = await process(msg.data);
1646
- await client.ack(msg);
1647
-
1648
- // Pass to next stage
1649
- await client.push('stage-3-finalize', processed);
1650
- } catch (error) {
1651
- await client.ack(msg, false, { error: error.message });
1652
- }
1653
- }
1654
- }
1655
-
1656
- // Stage 3: Finalize
1657
- async function finalizeStage() {
1658
- for await (const msg of client.take('stage-3-finalize', { wait: true })) {
1659
- try {
1660
- await finalize(msg.data);
1661
- await client.ack(msg);
1662
- console.log('Pipeline complete:', msg.data.id);
1663
- } catch (error) {
1664
- await client.ack(msg, false, { error: error.message });
1665
- }
1666
- }
1667
- }
1668
-
1669
- // Start pipeline
1670
- Promise.all([
1671
- validateStage(),
1672
- processStage(),
1673
- finalizeStage()
1674
- ]);
1675
-
1676
- // Add work to pipeline
1677
- await client.push('stage-1-validate', { id: 1, data: 'raw data' });
1678
- ```
1679
-
1680
- ### Example 3: Event Streaming (Bus Mode)
1681
-
1682
- ```javascript
1683
- import { Queen } from 'queen-mq';
1684
-
1685
- const client = new Queen({
1686
- baseUrls: ['http://localhost:6632']
1687
- });
1688
-
1689
- // Configure event queue
1690
- await client.queue('events', {
1691
- priority: 10,
1692
- leaseTime: 60
1693
- });
1694
-
1695
- // Producer: Emit events
1696
- async function emitEvents() {
1697
- await client.push('events', {
1698
- type: 'order.created',
1699
- orderId: 12345,
1700
- userId: 789,
1701
- amount: 99.99,
1702
- timestamp: Date.now()
1703
- });
1704
- }
1705
-
1706
- // Consumer 1: Analytics Service
1707
- async function analyticsService() {
1708
- for await (const event of client.take('events@analytics', {
1709
- subscriptionMode: 'all', // Replay all messages
1710
- wait: true
1711
- })) {
1712
- console.log('[Analytics] Processing event:', event.data.type);
1713
- await updateAnalytics(event.data);
1714
- await client.ack(event, true, { group: 'analytics' });
1715
- }
1716
- }
1717
-
1718
- // Consumer 2: Notification Service
1719
- async function notificationService() {
1720
- for await (const event of client.take('events@notifications', {
1721
- subscriptionMode: 'new', // Only new messages
1722
- wait: true
1723
- })) {
1724
- console.log('[Notifications] Processing event:', event.data.type);
1725
- await sendNotification(event.data);
1726
- await client.ack(event, true, { group: 'notifications' });
1727
- }
1728
- }
1729
-
1730
- // Consumer 3: Audit Service
1731
- async function auditService() {
1732
- for await (const event of client.take('events@audit', {
1733
- subscriptionMode: 'all', // Log everything
1734
- wait: true
1735
- })) {
1736
- console.log('[Audit] Logging event:', event.data.type);
1737
- await logToAudit(event.data);
1738
- await client.ack(event, true, { group: 'audit' });
1739
- }
1740
- }
1741
-
1742
- // Start all services (they all see the same events)
1743
- Promise.all([
1744
- analyticsService(),
1745
- notificationService(),
1746
- auditService()
1747
- ]);
1748
-
1749
- // Emit events
1750
- await emitEvents();
1751
- ```
1752
-
1753
- ### Example 4: Batch Processing (High Throughput)
1754
-
1755
- ```javascript
1756
- import { Queen } from 'queen-mq';
1757
-
1758
- const client = new Queen({
1759
- baseUrls: ['http://localhost:6632']
1760
- });
1761
-
1762
- // Configure for batch processing
1763
- await client.queue('data-processing', {
1764
- priority: 5,
1765
- leaseTime: 600, // 10 minutes for batch
1766
- windowBuffer: 30 // Buffer for 30 seconds
1767
- });
1768
-
1769
- // Producer: Send data
1770
- async function sendData() {
1771
- const records = [];
1772
- for (let i = 0; i < 100000; i++) {
1773
- records.push({ id: i, value: Math.random() });
1774
- }
1775
-
1776
- // Push in batches
1777
- await client.push('data-processing/analytics', records);
1778
- }
1779
-
1780
- // Consumer: HIGH PERFORMANCE batch processor using takeBatch()
1781
- async function batchProcessor() {
1782
- const BATCH_SIZE = 5000; // Large batches for 100k+ msg/s throughput
1783
-
1784
- // takeBatch() yields arrays directly - no manual batching needed!
1785
- for await (const messages of client.takeBatch('data-processing/analytics', {
1786
- batch: BATCH_SIZE,
1787
- wait: true,
1788
- timeout: 30000
1789
- })) {
1790
- try {
1791
- console.log(`Processing batch of ${messages.length} records`);
1792
-
1793
- // Extract data
1794
- const records = messages.map(m => m.data);
1795
-
1796
- // Bulk process (single DB operation)
1797
- await bulkInsertToDatabase(records);
1798
-
1799
- // Batch acknowledge (single DB transaction!)
1800
- await client.ack(messages);
1801
-
1802
- console.log(`✓ Batch complete in single transaction`);
1803
- } catch (error) {
1804
- console.error('Batch processing failed:', error);
1805
-
1806
- // Mark entire batch as failed (single transaction)
1807
- await client.ack(messages, false, { error: error.message });
1808
- }
1809
- }
1810
- }
1811
-
1812
- // Run
1813
- await sendData();
1814
- await batchProcessor();
1815
-
1816
- // Performance characteristics:
1817
- // - Batch size 5000: ~100,000 messages/second
1818
- // - Single DB transaction per batch (fetch + ack)
1819
- // - Constant memory usage
1820
- // - No performance degradation as queue grows
1821
- ```
1822
-
1823
- ### Example 5: Scheduled Jobs
1824
-
1825
- ```javascript
1826
- import { Queen } from 'queen-mq';
1827
-
1828
- const client = new Queen({
1829
- baseUrls: ['http://localhost:6632']
1830
- });
1831
-
1832
- // Configure with delayed processing
1833
- await client.queue('scheduled-jobs', {
1834
- delayedProcessing: 3600, // 1 hour delay
1835
- priority: 5
1836
- });
1837
-
1838
- // Schedule a job
1839
- async function scheduleReport() {
1840
- await client.push('scheduled-jobs/daily-reports', {
1841
- reportType: 'daily-sales',
1842
- date: new Date().toISOString().split('T')[0],
1843
- recipients: ['manager@company.com'],
1844
- scheduledAt: Date.now()
1845
- });
1846
-
1847
- console.log('Report scheduled for processing in 1 hour');
1848
- }
1849
-
1850
- // Process scheduled jobs
1851
- async function processScheduledJobs() {
1852
- for await (const job of client.take('scheduled-jobs/daily-reports', {
1853
- wait: true
1854
- })) {
1855
- try {
1856
- console.log('Generating report:', job.data.reportType);
1857
- await generateReport(job.data);
1858
- await client.ack(job);
1859
- } catch (error) {
1860
- await client.ack(job, false, { error: error.message });
1861
- }
1862
- }
1863
- }
1864
-
1865
- await scheduleReport();
1866
- processScheduledJobs().catch(console.error);
1867
- ```
1868
-
1869
- ### Example 6: Rate Limiting
1870
-
1871
- ```javascript
1872
- import { Queen } from 'queen-mq';
1873
-
1874
- const client = new Queen({
1875
- baseUrls: ['http://localhost:6632']
1876
- });
1877
-
1878
- await client.queue('api-calls', {
1879
- priority: 5,
1880
- leaseTime: 60
1881
- });
1882
-
1883
- // Producer: Queue API calls
1884
- async function queueApiCalls(calls) {
1885
- await client.push('api-calls', calls);
1886
- }
1887
-
1888
- // Consumer: Rate-limited processor (10 calls per second max)
1889
- async function rateLimitedProcessor() {
1890
- const RATE_LIMIT = 10; // calls per second
1891
- const INTERVAL = 1000; // 1 second
1892
-
1893
- let callsThisInterval = 0;
1894
- let intervalStart = Date.now();
1895
-
1896
- for await (const call of client.take('api-calls', { wait: true })) {
1897
- // Check if we need to wait
1898
- if (callsThisInterval >= RATE_LIMIT) {
1899
- const elapsed = Date.now() - intervalStart;
1900
- if (elapsed < INTERVAL) {
1901
- await new Promise(r => setTimeout(r, INTERVAL - elapsed));
1902
- }
1903
- callsThisInterval = 0;
1904
- intervalStart = Date.now();
1905
- }
1906
-
1907
- try {
1908
- await makeApiCall(call.data);
1909
- await client.ack(call);
1910
- callsThisInterval++;
1911
- } catch (error) {
1912
- await client.ack(call, false, { error: error.message });
1913
- }
1914
- }
1915
- }
1916
-
1917
- // Generate calls
1918
- const calls = Array.from({ length: 100 }, (_, i) => ({
1919
- id: i,
1920
- endpoint: '/api/data',
1921
- method: 'GET'
1922
- }));
1923
-
1924
- await queueApiCalls(calls);
1925
- rateLimitedProcessor().catch(console.error);
1926
- ```
1927
-
1928
- ### Example 7: Enterprise Features
1929
-
1930
- ```javascript
1931
- import { Queen } from 'queen-mq';
1932
-
1933
- const client = new Queen({
1934
- baseUrls: ['http://localhost:6632']
1935
- });
1936
-
1937
- // Configure with all enterprise features
1938
- await client.queue('production-queue', {
1939
- // Encryption
1940
- encryptionEnabled: true,
1941
-
1942
- // Retention
1943
- retentionSeconds: 86400, // Delete pending after 24 hours
1944
- completedRetentionSeconds: 3600, // Delete completed after 1 hour
1945
- retentionEnabled: true,
1946
-
1947
- // Eviction (SLA enforcement)
1948
- maxWaitTimeSeconds: 600, // Evict messages older than 10 minutes
1949
-
1950
- // Standard options
1951
- priority: 10,
1952
- leaseTime: 300,
1953
- retryLimit: 3,
1954
- dlqAfterMaxRetries: true
1955
- });
1956
-
1957
- // Push sensitive data (will be encrypted)
1958
- await client.push('production-queue', {
1959
- userId: 123,
1960
- creditCard: '4111-1111-1111-1111',
1961
- amount: 99.99,
1962
- timestamp: Date.now()
1963
- });
1964
-
1965
- // Process (data decrypted automatically)
1966
- for await (const message of client.take('production-queue', { wait: true })) {
1967
- console.log('Processing encrypted data:', message.data.userId);
1968
- await processPayment(message.data);
1969
- await client.ack(message);
1970
- }
1971
- ```
1972
-
1973
- ---
1974
-
1975
- ## 🧪 Testing
1976
-
1977
- Queen includes a comprehensive test suite covering all features.
1978
-
1979
- ### Run Tests
1980
-
1981
- ```bash
1982
- # Start the server first
1983
- npm start
1984
-
1985
- # Run all tests
1986
- node src/test/test-new.js
1987
-
1988
- # Run specific test categories
1989
- node src/test/test-new.js core # Core features
1990
- node src/test/test-new.js partition # Partition locking
1991
- node src/test/test-new.js enterprise # Enterprise features
1992
- node src/test/test-new.js bus # Bus mode
1993
- node src/test/test-new.js edge # Edge cases
1994
- node src/test/test-new.js advanced # Advanced patterns
1995
-
1996
- # Show help
1997
- node src/test/test-new.js help
1998
- ```
1999
-
2000
- ### Test Coverage
2001
-
2002
- The test suite verifies:
2003
-
2004
- **Core Features:**
2005
- - Queue creation and configuration
2006
- - Single and batch message push
2007
- - Message take and acknowledgment
2008
- - Delayed processing
2009
- - Partition FIFO ordering
2010
-
2011
- **Partition Locking:**
2012
- - Lock acquisition and release
2013
- - Bus mode partition locking
2014
- - Specific partition locking
2015
- - Namespace/task filtering with locking
2016
-
2017
- **Enterprise Features:**
2018
- - AES-256-GCM encryption
2019
- - Message retention policies
2020
- - Message eviction
2021
- - Combined enterprise features
2022
-
2023
- **Bus Mode:**
2024
- - Consumer groups
2025
- - Subscription modes (all, new, from)
2026
- - Consumer group isolation
2027
- - Mixed mode (queue + bus)
2028
-
2029
- **Edge Cases:**
2030
- - Empty and null payloads
2031
- - Very large payloads
2032
- - Concurrent operations
2033
- - Lease expiration
2034
- - SQL injection prevention
2035
- - XSS prevention
2036
-
2037
- **Advanced Patterns:**
2038
- - Multi-stage pipelines
2039
- - Fan-out/fan-in
2040
- - Priority scenarios
2041
- - Dead letter queue
2042
- - Circuit breaker
2043
- - Message deduplication
2044
- - Event sourcing
2045
-
2046
- ### Test Results
2047
-
2048
- Example output:
2049
- ```
2050
- 🚀 Starting Queen Message Queue Test Suite
2051
- Using the new minimalist Queen client interface
2052
- ================================================================================
2053
-
2054
- 📦 CORE FEATURES
2055
- ----------------------------------------
2056
- ✅ Queue Creation Policy
2057
- ✅ Single Message Push
2058
- ✅ Batch Message Push
2059
- ✅ Queue Configuration
2060
- ✅ Take and Acknowledgment
2061
- ✅ Delayed Processing
2062
- ✅ Partition FIFO Ordering
2063
-
2064
- 🔒 PARTITION LOCKING
2065
- ----------------------------------------
2066
- ✅ Partition Locking
2067
- ✅ Bus Partition Locking
2068
- ✅ Specific Partition Locking
2069
- ✅ Namespace Task Filtering
2070
- ✅ Namespace Task Bus Mode
2071
-
2072
- 📈 Test Summary
2073
- ================================================================================
2074
- Total: 42 | Passed: 42 | Failed: 0 | Duration: 45.2s
2075
- ```
2076
-
2077
- ---
2078
-
2079
- ## 🤝 Contributing
2080
-
2081
- We welcome contributions! Here's how to get started:
2082
-
2083
- 1. **Fork the repository**
2084
- 2. **Create a feature branch**: `git checkout -b feature/amazing-feature`
2085
- 3. **Make your changes**
2086
- 4. **Run the test suite**: `node src/test/test-new.js`
2087
- 5. **Commit your changes**: `git commit -m 'Add amazing feature'`
2088
- 6. **Push to the branch**: `git push origin feature/amazing-feature`
2089
- 7. **Open a Pull Request**
2090
-
2091
- ### Development Setup
2092
-
2093
- ```bash
2094
- # Clone your fork
2095
- git clone https://github.com/your-username/queen
2096
- cd queen
2097
-
2098
- # Install dependencies
2099
- nvm use 22
2100
- npm install
2101
-
2102
- # Initialize database
2103
- node init-db.js
2104
-
2105
- # Start server
2106
- npm start
2107
-
2108
- # Run tests
2109
- node src/test/test-new.js
2110
- ```
2111
-
2112
- ### Code Style
2113
-
2114
- - Use ES6+ features
2115
- - Follow existing code style
2116
- - Add comments for complex logic
2117
- - Write tests for new features
2118
-
2119
- ---
2120
-
2121
- ## 📄 License
2122
-
2123
- Apache License 2.0 - see [LICENSE.md](LICENSE.md) for details.
2124
-
2125
- ---
2126
-
2127
- ## 🔗 Links
2128
-
2129
- - **Repository**: [github.com/smartpricing/queen](https://github.com/smartpricing/queen)
2130
- - **Documentation**: See `docs/` directory
2131
- - **Issues**: [GitHub Issues](https://github.com/smartpricing/queen/issues)
2132
- - **API Reference**: [API.md](API.md)
2133
-
2134
- ---
2135
-
2136
- ## 📈 Performance
2137
-
2138
- **Benchmarks** (PostgreSQL 16, Node.js 22, cursor-based consumption):
2139
- - **Throughput**: 100,000+ messages/second with batch operations
2140
- - **Latency**: < 10ms for immediate pop operations
2141
- - **Constant-time consumption**: O(batch_size) regardless of queue depth
2142
- - **Concurrent Connections**: 1,000+ long polling connections
2143
- - **Database**: Optimized with proper indexing and connection pooling
2144
-
2145
- **Cursor-Based Architecture Benefits:**
2146
- - **No performance degradation**: Consistent speed whether queue has 1K or 1B messages
2147
- - **Predictable latency**: 150-200ms per batch throughout entire queue lifecycle
2148
- - **Efficient batch processing**: Direct cursor access eliminates table scans
2149
- - **Scalable to billions**: UUIDv7-based cursor positioning
2150
-
2151
- **Additional Optimization Features:**
2152
- - Connection pooling with configurable size
2153
- - Resource caching for queue/partition lookups
2154
- - Batch operations for bulk inserts/updates (up to 10,000 messages per batch)
2155
- - Optimized SQL queries with proper indexes
2156
- - Event-driven architecture for minimal polling overhead
2157
- - Long polling for real-time message delivery
2158
- - SKIP LOCKED for lock-free concurrent consumption
2159
-
2160
- ---
2161
-
2162
- ## 🎯 Roadmap
2163
-
2164
- - [ ] **Message Scheduling**: Cron-like scheduling for recurring jobs
2165
- - [ ] **Client Libraries**: Python, Go, Java clients
2166
- - [ ] **Kubernetes Operator**: Native K8s support
2167
-
2168
- ---
2169
-
2170
- <div align="center">
2171
-
2172
- **Queen Message Queue System** - Built for performance, reliability, and developer happiness 🚀
2173
-
2174
- Made with ❤️ by [Smartness](https://github.com/smartpricing)
2175
-
2176
- </div>
258
+ ### Other TODO Items
259
+ - retention jobs
260
+ - reconsume
261
+ - fix frontend
262
+ - pg async
263
+ - pg reconnect
264
+ - memory leak?