queen-mq 0.2.0 → 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 (84) hide show
  1. package/README.md +165 -769
  2. package/{src → client-js}/benchmark/consumer.js +5 -3
  3. package/{src → client-js}/benchmark/producer.js +9 -4
  4. package/{src → client-js}/client/client.js +272 -6
  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/{src → client-js}/test/core-tests.js +7 -2
  9. package/{src → client-js}/test/edge-case-tests.js +2 -1
  10. package/client-js/test/human.js +162 -0
  11. package/client-js/test/qos0-tests.js +334 -0
  12. package/{src → client-js}/test/test-new.js +42 -0
  13. package/package.json +8 -17
  14. package/init-db.js +0 -20
  15. package/src/cluster-server.js +0 -242
  16. package/src/config.js +0 -229
  17. package/src/database/connection.js +0 -129
  18. package/src/database/poolManager.js +0 -199
  19. package/src/database/schema-v2.sql +0 -278
  20. package/src/managers/eventManager.js +0 -59
  21. package/src/managers/queueManagerOptimized.js +0 -1634
  22. package/src/managers/resourceCache.js +0 -96
  23. package/src/managers/systemEventManager.js +0 -132
  24. package/src/routes/ack.js +0 -26
  25. package/src/routes/configure.js +0 -46
  26. package/src/routes/messages.js +0 -368
  27. package/src/routes/pop.js +0 -69
  28. package/src/routes/push.js +0 -28
  29. package/src/routes/resources.js +0 -330
  30. package/src/routes/status.js +0 -1037
  31. package/src/server.js +0 -1633
  32. package/src/test/MIGRATION_ISSUES.md +0 -174
  33. package/src/test/test.js +0 -4524
  34. package/src/utils/streaming.js +0 -231
  35. package/src/webapp-dist/assets/Analytics-DJrE4Rz2.css +0 -1
  36. package/src/webapp-dist/assets/Analytics-HEMXKmfJ.js +0 -1
  37. package/src/webapp-dist/assets/ConfirmDialog-YEoCdE7x.js +0 -1
  38. package/src/webapp-dist/assets/ConsumerGroups-CC5VJywp.css +0 -1
  39. package/src/webapp-dist/assets/ConsumerGroups-D8Z20HhZ.js +0 -1
  40. package/src/webapp-dist/assets/Dashboard-BRFOz6it.css +0 -1
  41. package/src/webapp-dist/assets/Dashboard-Bpx9I32h.js +0 -1
  42. package/src/webapp-dist/assets/LoadingSpinner-BPttXcbs.js +0 -1
  43. package/src/webapp-dist/assets/Messages-BOfVHKma.css +0 -1
  44. package/src/webapp-dist/assets/Messages-EKURCbLl.js +0 -1
  45. package/src/webapp-dist/assets/QueueDetail-BXXA7xoZ.css +0 -1
  46. package/src/webapp-dist/assets/QueueDetail-Bmxg0KiA.js +0 -1
  47. package/src/webapp-dist/assets/Queues-CvrJkrZT.css +0 -1
  48. package/src/webapp-dist/assets/Queues-yUx9_3-9.js +0 -1
  49. package/src/webapp-dist/assets/StatusBadge-C1bBtXHh.css +0 -1
  50. package/src/webapp-dist/assets/StatusBadge-C6N84XmO.js +0 -1
  51. package/src/webapp-dist/assets/analytics-BapTekLM.js +0 -1
  52. package/src/webapp-dist/assets/colors-5z6Q_kUr.js +0 -18
  53. package/src/webapp-dist/assets/index-B7w2bKT6.css +0 -1
  54. package/src/webapp-dist/assets/index-CUSnwYTH.js +0 -31
  55. package/src/webapp-dist/assets/messages-CRV6E-xU.js +0 -1
  56. package/src/webapp-dist/assets/queen-logo-blue.svg +0 -210
  57. package/src/webapp-dist/assets/queen-logo-cyan.svg +0 -210
  58. package/src/webapp-dist/assets/queen-logo-indigo.svg +0 -210
  59. package/src/webapp-dist/assets/queen-logo-orange.svg +0 -210
  60. package/src/webapp-dist/assets/queen-logo-pink.svg +0 -210
  61. package/src/webapp-dist/assets/queen-logo-purple.svg +0 -210
  62. package/src/webapp-dist/assets/queen-logo-rose.svg +0 -239
  63. package/src/webapp-dist/assets/queen-logo.svg +0 -263
  64. package/src/webapp-dist/assets/queues-BdUbxfBC.js +0 -1
  65. package/src/webapp-dist/assets/resources-Bs9U-ceF.js +0 -1
  66. package/src/webapp-dist/index.html +0 -15
  67. package/src/websocket/wsServer.js +0 -228
  68. /package/{src → client-js}/benchmark/consumer_multi.js +0 -0
  69. /package/{src → client-js}/benchmark/producer_multi.js +0 -0
  70. /package/{src → client-js}/client/utils/loadBalancer.js +0 -0
  71. /package/{src → client-js}/services/encryptionService.js +0 -0
  72. /package/{src → client-js}/services/evictionService.js +0 -0
  73. /package/{src → client-js}/services/retentionService.js +0 -0
  74. /package/{src → client-js}/services/startupSync.js +0 -0
  75. /package/{src → client-js}/test/README.md +0 -0
  76. /package/{src → client-js}/test/advanced-client-tests.js +0 -0
  77. /package/{src → client-js}/test/advanced-pattern-tests.js +0 -0
  78. /package/{src → client-js}/test/bus-mode-tests.js +0 -0
  79. /package/{src → client-js}/test/enterprise-tests.js +0 -0
  80. /package/{src → client-js}/test/partition-locking-tests.js +0 -0
  81. /package/{src → client-js}/test/utils.js +0 -0
  82. /package/{src → client-js}/test/window-buffer-test.js +0 -0
  83. /package/{src → client-js}/utils/logger.js +0 -0
  84. /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,862 +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
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.
23
-
24
- ### Why Queen?
25
-
26
- **🚀 Developer-First API**
27
- - **Pipeline & Transaction APIs**: High-level fluent interfaces for complex workflows
28
- - **4 core methods, infinite patterns**: `queue()`, `push()`, `take()`, `ack()`
29
- - **Async iteration**: Process messages with familiar `for await` syntax
30
- - **Batch processing**: Use `takeBatch()` for 250k+ msg/sec throughput on millions of messages
31
- - **Smart addressing**: `orders/urgent@workers` - queue, partition, and consumer group in one
32
-
33
- **⚡ Production-Ready Performance**
34
- - **100,000+ msg/sec** throughput with cursor-based consumption
35
- - **Constant-time batch operations** - O(batch_size) regardless of queue depth
36
- - **Long polling** for event-driven, real-time message delivery
37
- - **Partition locking** prevents duplicate processing across consumers
38
- - **Connection pooling** and optimized batch operations
39
- - **Parallel processing** across partitions with `withConcurrency()`
40
-
41
- **🏗️ Flexible Architecture**
42
- - **Queue Mode**: Competitive consumption (traditional work queue)
43
- - **Bus Mode**: Pub/sub with consumer groups (event streaming)
44
- - **Mixed Mode**: Combine both patterns in the same system
45
- - **Partitions**: FIFO ordering with parallel processing
46
- - **Exactly-Once Processing**: Lease-based locking with automatic validation
47
-
48
- **🔒 Enterprise Features**
49
- - **AES-256-GCM Encryption**: Protect sensitive data at rest
50
- - **Message Retention**: Automatic cleanup policies
51
- - **Message Eviction**: SLA enforcement for time-sensitive tasks
52
- - **Dead Letter Queue**: Handle failed messages gracefully
53
- - **Automatic Lease Renewal**: For long-running tasks
54
- - **Atomic Transactions**: Multi-operation consistency
55
-
56
- **📊 Built-in Observability**
57
- - **Real-time Dashboard**: WebSocket-powered monitoring
58
- - **Rich Analytics**: Throughput, lag, queue depth metrics
59
- - **Message Browser**: Search, inspect, and retry messages
60
- - **System Health**: Database, memory, and performance metrics
61
- - **Cursor Tracking**: Monitor consumption progress per consumer group
62
-
63
- ### Use Cases
64
-
65
- - **Task Queues**: Background jobs, email sending, data processing
66
- - **Event Streaming**: Audit logs, analytics, multi-service event handling
67
- - **Workflow Orchestration**: Multi-stage pipelines, saga patterns
68
- - **Rate Limiting**: Throttle and batch time-sensitive operations
69
- - **Priority Processing**: Handle urgent tasks before routine ones
20
+ ## Introduction
70
21
 
71
- ---
72
-
73
- ## 📋 Table of Contents
74
-
75
- - [First Queue](#first-queue)
76
- - [Quick Start](#-quick-start)
77
- - [Advanced Client APIs](#advanced-client-apis)
78
- - [Standard Client Examples](#-client-examples)
79
- - [Server Setup](#-server-setup)
80
- - [Core Concepts](#-core-concepts)
81
- - [HTTP API Reference](#-http-api-reference)
82
- - [Dashboard](#-dashboard)
83
- - [Configuration](#-configuration)
84
- - [Full Examples](#-full-examples)
85
- - [Testing](#-testing)
86
- - [Contributing](#-contributing)
22
+ QueenMQ is a queue system written in C++ and backed by Postgres. Supports queues and consumer groups.
87
23
 
88
- ---
24
+ ## JS Client usage
89
25
 
90
- ## First Queue
26
+ ```js
27
+ import { Queen } from 'queen-mq'
91
28
 
92
- ```javascript
93
- import { Queen } from 'queen-mq';
94
-
95
- const client = new Queen({
96
- baseUrl: 'http://localhost:6632' // Single server
97
- // OR for multiple servers with load balancing:
98
- // baseUrls: ['http://server1:6632', 'http://server2:6632'],
99
- // loadBalancingStrategy: 'round-robin', // or 'random', 'least-connections'
100
- // enableFailover: true
29
+ const client = new Queen({
30
+ baseUrls: ['http://localhost:6632'],
31
+ timeout: 30000,
32
+ retryAttempts: 3
101
33
  });
102
34
 
103
- // Configure queue
104
- await client.queue('tasks', {
105
- leaseTime: 300, // 5 minutes to process each message
106
- retryLimit: 3 // Retry up to 3 times
107
- });
108
-
109
- // Push a message
110
- await client.push('tasks', {
111
- action: 'send-email',
112
- to: 'user@example.com'
113
- });
114
-
115
- // Process messages
116
- for await (const message of client.take('tasks')) {
117
- console.log('Processing:', message.data);
118
- await client.ack(message);
119
- }
120
- ```
121
-
122
- That's it! You now have a working message queue system.
123
-
124
- ## 🏃 Quick Start
125
-
126
- ### Prerequisites
127
-
128
- - **Node.js 22+**
129
- - **PostgreSQL 12+**
130
-
131
- ### Installation
132
-
133
- ```bash
134
- # Clone the repository
135
- git clone https://github.com/smartpricing/queen
136
- cd queen
137
-
138
- # Install dependencies
139
- nvm use 22
140
- npm install
35
+ const queue = 'html-processing'
141
36
 
142
- # Initialize database schema
143
- node init-db.js
144
- ```
37
+ // Create a queue
38
+ await client.queue(queue, { leaseTime: 30 });
145
39
 
146
- ### Set Environment (Optional)
40
+ // Push some data, specifyng the partition
41
+ await client.push(`${queue}/customer-1828`, [ { id: 1 } ]);
147
42
 
148
- ```bash
149
- # Database connection
150
- export PG_USER=postgres
151
- export PG_HOST=localhost
152
- export PG_DB=postgres
153
- export PG_PASSWORD=postgres
154
- export PG_PORT=5432
155
-
156
- # Enable encryption (optional)
157
- export QUEEN_ENCRYPTION_KEY=$(openssl rand -hex 32)
158
- ```
159
-
160
- ### Start the Server
161
-
162
- ```bash
163
- npm start
164
- # Server starts on http://localhost:6632
165
- ```
166
-
167
- ---
168
-
169
- ## Advanced Client APIs
170
-
171
- ### Pipeline API - Fluent Message Processing
172
-
173
- The Pipeline API provides a chainable interface for complex message processing workflows:
174
-
175
- ```javascript
176
- // Simple message processing (one at a time)
177
- await client.pipeline('my-queue')
178
- .take(100) // Take up to 100 messages
179
- .process(async (message) => { // Process each message individually
180
- console.log('Processing:', message.data);
181
- return { processed: true };
182
- })
183
- .execute();
184
-
185
- // Batch processing
186
- await client.pipeline('my-queue')
187
- .take(100)
188
- .processBatch(async (messages) => { // Process messages as a batch
189
- console.log(`Processing ${messages.length} messages`);
190
- return messages.map(m => ({ processed: m.id }));
191
- })
192
- .execute();
193
-
194
- // With automatic lease renewal for long-running tasks
195
- await client.pipeline('my-queue')
196
- .take(50)
197
- .withAutoRenewal({ interval: 5000 }) // Renew lease every 5 seconds
198
- .process(async (message) => {
199
- // Long-running task - lease automatically renewed
200
- // Without this, if task takes > leaseTime, message may be redelivered
201
- await heavyComputation(message);
202
- })
203
- .execute();
204
-
205
- // Parallel processing across partitions
206
- await client.pipeline('my-queue')
207
- .take(100)
208
- .withConcurrency(4) // 4 parallel workers
209
- .process(async (message) => {
210
- await processMessage(message);
211
- })
212
- .repeat({ continuous: true }) // Keep running continuously (default)
213
- .execute();
214
-
215
- // With error handling
216
- await client.pipeline('my-queue')
217
- .take(100)
218
- .process(async (message) => {
219
- if (message.data.invalid) {
220
- throw new Error('Invalid message format');
221
- }
222
- return await riskyOperation(message);
223
- })
224
- .onError(async (error, messages) => {
225
- console.error('Processing failed:', error.message);
226
-
227
- // Move failed messages to error queue
228
- await client.push('error-queue', {
229
- error: error.message,
230
- messages: messages.map(m => m.data),
231
- timestamp: Date.now()
232
- });
233
-
234
- // ACK as failed (will retry based on retryLimit)
235
- for (const msg of messages) {
236
- await client.ack(msg, false, { error: error.message });
237
- }
238
- })
239
- .execute();
240
- ```
241
-
242
- #### Automatic Lease Renewal
243
-
244
- By default, messages have a lease time (e.g., 5 minutes). If processing takes longer, the message may be redelivered to another consumer. Use `.withAutoRenewal()` to prevent this:
245
-
246
- ```javascript
247
- // WITHOUT auto-renewal (default) - Risk of redelivery
248
- await client.pipeline('video-processing')
249
- .take(10)
250
- .process(async (message) => {
251
- // If this takes > leaseTime, message may be processed twice!
252
- await longRunningTask(message);
253
- })
254
- .execute();
255
-
256
- // WITH auto-renewal - Safe for long tasks
257
- await client.pipeline('video-processing')
258
- .take(10)
259
- .withAutoRenewal({
260
- interval: 30000 // Renew every 30 seconds (default)
261
- })
262
- .process(async (message) => {
263
- // Lease automatically renewed while processing
264
- await longRunningTask(message); // Safe even if takes hours
265
- })
266
- .execute();
267
- ```
268
-
269
- **When to use:**
270
- - Video/audio processing
271
- - Large file operations
272
- - Machine learning inference
273
- - Any task that might exceed the lease time
274
-
275
- **Important:**
276
- - Auto-renewal stops when message is ACKed or process crashes
277
- - Set interval < lease time (e.g., renew at 1/3 of lease time)
278
- - Only works within pipeline API
279
-
280
- #### Error Handling in Pipeline
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
47
+ }
281
48
 
282
- The `onError` handler provides robust error recovery:
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
53
+ }
283
54
 
284
- **Key Behaviors:**
285
- - Catches errors from `process()` or `processBatch()`
286
- - Receives the error and affected messages
287
- - Pipeline continues after error handler (doesn't stop)
288
- - Without `onError`, errors stop the pipeline
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' });
59
+ }
289
60
 
290
- **Error Recovery Strategies:**
291
- ```javascript
292
- // Strategy 1: Retry with backoff
293
- .onError(async (error, messages) => {
294
- // ACK as failed - will retry based on retryLimit
295
- await client.ack(messages, false);
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
296
72
  })
297
-
298
- // Strategy 2: Move to Dead Letter Queue
299
- .onError(async (error, messages) => {
300
- for (const msg of messages) {
301
- if (msg.retryCount >= 3) {
302
- await client.push('dlq', { original: msg, error: error.message });
303
- await client.ack(msg, true); // Remove from queue
304
- } else {
305
- await client.ack(msg, false); // Retry
306
- }
307
- }
73
+ .processBatch(async (messages) => {
74
+ return messages.map(x => x.data.id * 2);
308
75
  })
309
-
310
- // Strategy 3: Skip and continue
311
- .onError(async (error, messages) => {
312
- console.error(`Skipping ${messages.length} messages: ${error.message}`);
313
- await client.ack(messages, true); // ACK as success to remove
76
+ .atomically((tx, originalMessages, processedMessages) => { // ack and push are transactional inside atomically
77
+ tx.ack(originalMessages);
78
+ tx.push('another-queue', processedMessages);
314
79
  })
80
+ .repeat({ continuous: true })
81
+ .execute();
315
82
  ```
316
83
 
317
- **Important Notes:**
318
- - With `atomically()`: atomic operations only run on success
319
- - In `repeat()` mode: errors don't stop continuous processing
320
- - Batch processing: error affects all messages in the batch
321
- - Parallel workers: each worker has independent error handling
322
-
323
- ### Transaction API - Atomic Operations
324
-
325
- Execute multiple operations atomically with lease validation:
326
-
327
- ```javascript
328
- // Atomic ACK + PUSH
329
- const messages = await client.takeSingleBatch('input-queue');
330
-
331
- await client.transaction()
332
- .ack(messages) // ACK input messages
333
- .push('output-queue', processedResults) // Push to output
334
- .extend(leaseId) // Extend another lease
335
- .commit(); // Execute atomically
336
-
337
- // Pipeline with custom atomic operations
338
- await client.pipeline('my-queue')
339
- .take(50)
340
- .process(async (message) => {
341
- return await transform(message);
342
- })
343
- .atomically((tx, originalMessages, processedMessages) => {
344
- tx.ack(originalMessages)
345
- .push('output-queue', processedMessages)
346
- .push('audit-queue', {
347
- timestamp: Date.now(),
348
- count: processedMessages.length
349
- });
350
- })
351
- .execute();
352
- ```
353
-
354
- ---
355
-
356
- ## 💻 Client Examples
357
-
358
- The Queen client provides a minimalist API with just 4 methods that compose into any messaging pattern you need.
359
-
360
- ### Installation
361
-
362
- ```bash
363
- npm install queen-mq
364
- ```
84
+ ## 📚 Examples
365
85
 
366
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
367
89
 
368
- #### 1. Configure a Queue
369
-
370
- ```javascript
371
- import { Queen } from 'queen-mq';
372
-
373
- const client = new Queen({
374
- baseUrl: 'http://localhost:6632',
375
- timeout: 30000,
376
- retryAttempts: 3
377
- });
378
-
379
- // Configure with options
380
- await client.queue('orders', {
381
- priority: 10, // Higher priority queues processed first
382
- leaseTime: 300, // 5 minutes to process each message
383
- retryLimit: 3, // Retry up to 3 times
384
- windowBuffer: 0, // No delay (immediate processing)
385
- retentionSeconds: 86400, // Keep messages for 24 hours
386
- completedRetentionSeconds: 3600, // Keep completed messages for 1 hour
387
- partitions: 10, // Create 10 partitions for parallel processing
388
- maxWaitTimeSeconds: 600 // Evict messages waiting > 10 minutes
389
- });
390
-
391
- // Configure with namespace and task for grouping
392
- await client.queue('order-processing', {
393
- priority: 10
394
- }, {
395
- namespace: 'ecommerce',
396
- task: 'checkout'
397
- });
398
- ```
399
-
400
- #### 2. Push Messages
401
-
402
- ```javascript
403
- // Single message
404
- await client.push('orders', {
405
- orderId: 12345,
406
- amount: 99.99
407
- });
408
-
409
- // To a specific partition
410
- await client.push('orders/urgent', {
411
- orderId: 12346,
412
- amount: 999.99,
413
- priority: 'high'
414
- });
415
-
416
- // Batch messages
417
- await client.push('orders', [
418
- { orderId: 12347, amount: 49.99 },
419
- { orderId: 12348, amount: 79.99 },
420
- { orderId: 12349, amount: 29.99 }
421
- ]);
422
- ```
423
-
424
- #### 3. Take Messages (Async Iterator)
425
-
426
- ```javascript
427
- // Process continuously with long polling
428
- for await (const message of client.take('orders', {
429
- wait: true, // Enable long polling
430
- timeout: 30000, // 30 second timeout
431
- batch: 10 // Fetch up to 10 at once
432
- })) {
433
- try {
434
- await processOrder(message.data);
435
- await client.ack(message); // Success
436
- } catch (error) {
437
- await client.ack(message, false, { error: error.message }); // Failure
438
- }
439
- }
440
-
441
- // Process limited messages
442
- for await (const message of client.take('orders', { limit: 100 })) {
443
- await processOrder(message.data);
444
- await client.ack(message);
445
- }
446
-
447
- // Take from specific partition
448
- for await (const message of client.take('orders/urgent')) {
449
- await processUrgentOrder(message.data);
450
- await client.ack(message);
451
- }
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
452
94
 
453
- // Use takeBatch to get arrays of messages (higher throughput)
454
- for await (const messages of client.takeBatch('orders', {
455
- batch: 1000, // Fetch 1000 at a time
456
- wait: true
457
- })) {
458
- // messages is an array of up to 1000 messages
459
- console.log(`Processing batch of ${messages.length} messages`);
460
-
461
- try {
462
- // Process entire batch
463
- await processBatch(messages.map(m => m.data));
464
-
465
- // Acknowledge entire batch at once (efficient!)
466
- await client.ack(messages); // Pass array for batch ack
467
- } catch (error) {
468
- // Mark entire batch as failed
469
- await client.ack(messages, false, { error: error.message });
470
- }
471
- }
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
472
98
 
473
- // Take single batch (convenience method)
474
- const messages = await client.takeSingleBatch('orders', { batch: 100 });
475
- ```
99
+ ### Pipelines
100
+ - **[Transactional Pipelines](examples/03-transactional-pipeline.js)** - Atomic processing with ack and push in a transaction
476
101
 
477
- #### 4. Acknowledge Messages
102
+ ### Event Streaming (QoS 0)
103
+ - **[Event Streaming](examples/09-event-streaming.js)** - At-most-once delivery with buffering and auto-ack
478
104
 
479
- ```javascript
480
- // Acknowledge success
481
- await client.ack(message);
482
- // or
483
- await client.ack(message, true);
105
+ ## QoS 0: At-Most-Once Event Streaming
484
106
 
485
- // Acknowledge failure (will retry based on retryLimit)
486
- await client.ack(message, false);
107
+ For high-throughput event streams, Queen supports **at-most-once delivery** with server-side buffering and auto-acknowledgment.
487
108
 
488
- // Acknowledge with error context
489
- await client.ack(message, false, {
490
- error: 'Payment gateway timeout'
491
- });
492
-
493
- // Batch acknowledgment (with lease validation)
494
- await client.ack(messages); // Pass array for batch ack
495
- ```
109
+ ### Server-Side Buffering
496
110
 
497
- #### 5. Renew Leases (for long-running tasks)
111
+ Batch events on the server for 10-100x reduction in database writes:
498
112
 
499
113
  ```javascript
500
- // Renew lease for a single message
501
- const result = await client.renewLease(message);
502
- console.log(`Lease renewed until: ${result.newExpiresAt}`);
503
-
504
- // Renew by lease ID directly
505
- await client.renewLease('lease-uuid-123');
506
-
507
- // Renew multiple messages at once
508
- const results = await client.renewLease(messages);
509
- results.forEach(r => {
510
- if (r.success) {
511
- console.log(`Renewed ${r.leaseId} until ${r.newExpiresAt}`);
512
- } else {
513
- console.error(`Failed to renew ${r.leaseId}: ${r.error}`);
514
- }
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
515
118
  });
516
119
 
517
- // Example: Manual renewal during long processing
518
- for await (const message of client.take('my-queue')) {
519
- // Set up periodic renewal
520
- const renewalInterval = setInterval(async () => {
521
- await client.renewLease(message);
522
- }, 30000); // Renew every 30 seconds
523
-
524
- try {
525
- await longRunningTask(message);
526
- await client.ack(message);
527
- } finally {
528
- clearInterval(renewalInterval);
529
- }
530
- }
120
+ // Result: 1000 events = ~10 DB writes (instead of 1000)
531
121
  ```
532
122
 
533
- ### Address Notation
123
+ ### Auto-Acknowledgment
534
124
 
535
- Queen uses a simple addressing scheme that encodes queue, partition, and consumer group:
125
+ Skip manual ack for fire-and-forget consumption:
536
126
 
537
127
  ```javascript
538
- // Basic addresses
539
- 'orders' // Queue (Default partition)
540
- 'orders/urgent' // Queue with specific partition
541
- 'orders@workers' // Queue with consumer group (bus mode)
542
- 'orders/urgent@workers' // Full address: queue + partition + group
543
-
544
- // Namespace/task filtering (cross-queue consumption)
545
- 'namespace:ecommerce' // All queues in namespace
546
- 'task:checkout' // All queues with task
547
- 'namespace:ecommerce/task:checkout' // Combined filter
548
- 'namespace:ecommerce/task:checkout@audit' // With consumer group
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!
132
+ }
549
133
  ```
550
134
 
551
- ### Consumer Patterns
135
+ ### Fan-Out Pattern (Consumer Groups)
552
136
 
553
- #### Consumer Groups (Bus Mode)
137
+ Combine buffering + auto-ack + consumer groups for pub/sub:
554
138
 
555
139
  ```javascript
556
- // Multiple services process the same messages independently
557
-
558
- // Analytics service
559
- for await (const event of client.take('events@analytics')) {
560
- await updateAnalytics(event.data);
561
- await client.ack(event, true, { group: 'analytics' });
562
- }
140
+ // Publisher (buffered)
141
+ await client.push('events', { action: 'login' }, { bufferMs: 100 });
563
142
 
564
- // Monitoring service (gets same messages)
565
- for await (const event of client.take('events@monitoring')) {
566
- await checkThresholds(event.data);
567
- await client.ack(event, true, { group: 'monitoring' });
143
+ // Multiple subscribers (each group gets all messages)
144
+ for await (const e of client.take('events@dashboard', { autoAck: true })) {
145
+ updateUI(e.data);
568
146
  }
569
147
 
570
- // Audit service (also gets same messages)
571
- for await (const event of client.take('events@audit')) {
572
- await logToAudit(event.data);
573
- await client.ack(event, true, { group: 'audit' });
148
+ for await (const e of client.take('events@analytics', { autoAck: true })) {
149
+ trackEvent(e.data);
574
150
  }
575
151
  ```
576
152
 
577
- #### Graceful Shutdown
578
-
579
- ```javascript
580
- let running = true;
153
+ ### PostgreSQL Failover
581
154
 
582
- process.on('SIGTERM', () => {
583
- console.log('Shutting down gracefully...');
584
- running = false;
585
- });
155
+ Queen automatically buffers messages to disk when PostgreSQL is unavailable - **zero message loss**:
586
156
 
587
- for await (const message of client.take('orders')) {
588
- if (!running) break;
589
-
590
- await processOrder(message.data);
591
- await client.ack(message);
592
- }
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
593
162
 
594
- console.log('Shutdown complete');
595
- ```
596
-
597
- ---
598
-
599
- ## 🖥️ Server Setup
600
-
601
- ### Environment Variables
163
+ **No configuration needed** - failover is automatic!
602
164
 
165
+ **Custom directory:**
603
166
  ```bash
604
- # Server configuration
605
- PORT=6632 # Server port
606
- HOST=0.0.0.0 # Server host
607
- WORKER_ID=srv-1 # Unique server instance ID
608
- APP_NAME=queen-mq # Application name
609
-
610
- # Database connection
611
- PG_USER=postgres # PostgreSQL user
612
- PG_HOST=localhost # PostgreSQL host
613
- PG_DB=postgres # PostgreSQL database
614
- PG_PASSWORD=postgres # PostgreSQL password
615
- PG_PORT=5432 # PostgreSQL port
616
- PG_USE_SSL=false # Enable SSL for PostgreSQL
617
- DB_POOL_SIZE=150 # Connection pool size
618
- DB_IDLE_TIMEOUT=30000 # Idle connection timeout (ms)
619
- DB_CONNECTION_TIMEOUT=2000 # Connection timeout (ms)
620
- DB_STATEMENT_TIMEOUT=30000 # Statement timeout (ms)
621
-
622
- # Queue configuration
623
- DEFAULT_TIMEOUT=30000 # Default pop timeout (ms)
624
- MAX_TIMEOUT=60000 # Maximum pop timeout (ms)
625
- DEFAULT_BATCH_SIZE=1 # Default batch size
626
- BATCH_INSERT_SIZE=1000 # Batch insert size for push operations
627
- DEFAULT_LEASE_TIME=300 # Default lease time (seconds)
628
- DEFAULT_RETRY_LIMIT=3 # Default retry limit
629
- DEFAULT_RETRY_DELAY=1000 # Default retry delay (ms)
630
- DEFAULT_PRIORITY=0 # Default message priority
631
- DEFAULT_WINDOW_BUFFER=0 # Default window buffer (seconds)
632
-
633
- # Long polling configuration
634
- QUEUE_POLL_INTERVAL=100 # Initial poll interval (ms)
635
- QUEUE_MAX_POLL_INTERVAL=2000 # Max poll interval after backoff (ms)
636
- QUEUE_BACKOFF_THRESHOLD=5 # Empty polls before backoff
637
- QUEUE_BACKOFF_MULTIPLIER=2 # Exponential backoff multiplier
638
-
639
- # Encryption
640
- QUEEN_ENCRYPTION_KEY= # 32-byte hex key for AES-256-GCM encryption
641
-
642
- # Retention & Eviction
643
- DEFAULT_RETENTION_SECONDS=0 # Message retention (0 = disabled)
644
- DEFAULT_COMPLETED_RETENTION_SECONDS=0 # Completed message retention
645
- RETENTION_INTERVAL=300000 # Retention service interval (ms)
646
- RETENTION_BATCH_SIZE=1000 # Retention batch size
647
- EVICTION_INTERVAL=60000 # Eviction service interval (ms)
648
- EVICTION_BATCH_SIZE=1000 # Eviction batch size
649
- METRICS_RETENTION_DAYS=90 # Metrics retention period
650
-
651
- # System Events
652
- QUEEN_SYSTEM_EVENTS_ENABLED=false # Enable system event propagation
653
- QUEEN_SYSTEM_EVENTS_BATCH_MS=10 # Event batching window (ms)
654
- QUEEN_SYSTEM_EVENTS_SYNC_TIMEOUT=30000 # Startup sync timeout (ms)
655
-
656
- # WebSocket configuration
657
- WS_COMPRESSION=0 # WebSocket compression level
658
- WS_MAX_PAYLOAD_LENGTH=16384 # Max WebSocket payload (bytes)
659
- WS_IDLE_TIMEOUT=60 # WebSocket idle timeout (seconds)
660
- WS_MAX_CONNECTIONS=1000 # Max WebSocket connections
661
- WS_HEARTBEAT_INTERVAL=30000 # WebSocket heartbeat interval (ms)
662
-
663
- # API configuration
664
- MAX_BODY_SIZE=104857600 # Max request body size (100MB)
665
- API_DEFAULT_LIMIT=100 # Default pagination limit
666
- API_MAX_LIMIT=1000 # Max pagination limit
667
- CORS_MAX_AGE=86400 # CORS max age (seconds)
668
- CORS_ALLOWED_ORIGINS=* # CORS allowed origins
669
- CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,OPTIONS
670
- CORS_ALLOWED_HEADERS=Content-Type,Authorization
671
-
672
- # Monitoring & Logging
673
- ENABLE_REQUEST_COUNTING=true # Enable request metrics
674
- ENABLE_MESSAGE_COUNTING=true # Enable message metrics
675
- METRICS_ENDPOINT_ENABLED=true # Enable metrics endpoint
676
- HEALTH_CHECK_ENABLED=true # Enable health check endpoint
677
- ENABLE_LOGGING=true # Enable logging
678
- LOG_LEVEL=info # Log level (debug|info|warn|error)
679
- LOG_FORMAT=json # Log format (json|text)
167
+ FILE_BUFFER_DIR=/custom/path ./bin/queen-server
680
168
  ```
681
169
 
682
- ### Programmatic Server
170
+ ### When to Use
683
171
 
684
- ```javascript
685
- import { createQueenServer } from 'queen-mq/server';
686
-
687
- const server = await createQueenServer({
688
- port: 6632,
689
- database: {
690
- connectionString: 'postgresql://...',
691
- poolSize: 20
692
- },
693
- encryption: {
694
- key: 'your-32-byte-hex-key'
695
- },
696
- features: {
697
- retention: true,
698
- eviction: true
699
- }
700
- });
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 |
701
177
 
702
- await server.start();
703
- ```
178
+ ## Webapp
704
179
 
705
- ---
180
+ A modern Vue 3 web interface for managing and monitoring Queen MQ.
706
181
 
707
- ## 🔑 Core Concepts
708
-
709
- ### Message Flow
710
-
711
- 1. **Push**: Messages are encrypted and stored in PostgreSQL with metadata
712
- 2. **Pop/Take**: Messages are leased to consumers with automatic lock management
713
- 3. **Process**: Consumers process messages with automatic lease renewal
714
- 4. **ACK**: Messages are marked complete and leases are released
715
- 5. **Retry**: Failed messages are retried with exponential backoff
716
-
717
- ### Partition & Lease Management
718
-
719
- - Each queue can have multiple partitions for parallel processing
720
- - Consumers acquire exclusive leases on partitions
721
- - Leases include unique IDs for validation and fencing
722
- - Automatic lease renewal for long-running tasks
723
- - Dead letter queue for exhausted retries
724
-
725
- ### Scalability
726
-
727
- - Horizontal scaling with multiple server instances
728
- - Partition-based parallelism
729
- - Connection pooling and query optimization
730
- - WebSocket support for real-time updates
731
- - Efficient batch operations
732
-
733
- ---
734
-
735
- ## 📚 HTTP API Reference
736
-
737
- ### Queue Management
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
738
190
 
191
+ **Quick Start:**
739
192
  ```bash
740
- # Create/configure queue
741
- curl -X POST http://localhost:6632/api/v1/configure \
742
- -H "Content-Type: application/json" \
743
- -d '{
744
- "queue": "my-queue",
745
- "options": {
746
- "maxRetries": 3,
747
- "visibilityTimeout": 30000
748
- }
749
- }'
750
-
751
- # Delete queue
752
- curl -X DELETE http://localhost:6632/api/v1/configure/my-queue
193
+ cd webapp
194
+ npm install
195
+ npm run dev
753
196
  ```
754
197
 
755
- ### Message Operations
198
+ The dashboard will be available at `http://localhost:4000`
756
199
 
757
- ```bash
758
- # Push messages
759
- curl -X POST http://localhost:6632/api/v1/push \
760
- -H "Content-Type: application/json" \
761
- -d '{
762
- "queue": "my-queue",
763
- "messages": [
764
- { "data": "message 1" },
765
- { "data": "message 2", "priority": 100 }
766
- ]
767
- }'
768
-
769
- # Pop messages (take)
770
- curl -X POST http://localhost:6632/api/v1/pop \
771
- -H "Content-Type: application/json" \
772
- -d '{
773
- "queue": "my-queue",
774
- "batch": 10,
775
- "visibilityTimeout": 30000
776
- }'
777
-
778
- # Acknowledge messages
779
- curl -X POST http://localhost:6632/api/v1/ack \
780
- -H "Content-Type: application/json" \
781
- -d '{
782
- "queue": "my-queue",
783
- "transactionId": "msg-transaction-id",
784
- "status": "completed",
785
- "leaseId": "lease-uuid"
786
- }'
787
- ```
200
+ See [webapp/README.md](webapp/README.md) for more details.
788
201
 
789
- ### Advanced Operations
202
+ ## Install server and configure it
790
203
 
791
- ```bash
792
- # Atomic transaction
793
- curl -X POST http://localhost:6632/api/v1/transaction \
794
- -H "Content-Type: application/json" \
795
- -d '{
796
- "operations": [
797
- {
798
- "type": "ack",
799
- "queue": "input-queue",
800
- "transactionId": "msg-id",
801
- "status": "completed"
802
- },
803
- {
804
- "type": "push",
805
- "queue": "output-queue",
806
- "messages": [{"data": "processed"}]
807
- }
808
- ],
809
- "requiredLeases": ["lease-uuid-1", "lease-uuid-2"]
810
- }'
811
-
812
- # Extend lease
813
- curl -X POST http://localhost:6632/api/v1/lease/lease-uuid/extend \
814
- -H "Content-Type: application/json" \
815
- -d '{}'
816
-
817
- # Get queue status
818
- curl http://localhost:6632/api/v1/status/my-queue
204
+ ### Quick Start
205
+
206
+ ```sh
207
+ cd server
208
+ make clean
209
+ make deps
210
+ make build-only
211
+ DB_POOL_SIZE=50 ./bin/queen-server
819
212
  ```
820
213
 
821
- ---
214
+ **📖 Complete Build & Tuning Guide:** [server/README.md](server/README.md)
822
215
 
823
- ## 📊 Dashboard
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
824
222
 
825
- Access the real-time dashboard at `http://localhost:6632/`
223
+ ### Environment Variables
826
224
 
827
- ### Features
828
- - Real-time queue metrics
829
- - Message browser with search
830
- - Consumer group monitoring
831
- - System health indicators
832
- - Performance graphs
225
+ [The full list of environment variables is here](server/ENV_VARIABLES.md)
833
226
 
834
- ### WebSocket Events
835
- ```javascript
836
- const ws = new WebSocket('ws://localhost:6632/ws/dashboard');
837
- ws.on('message', (data) => {
838
- const event = JSON.parse(data);
839
- console.log('Queue event:', event);
840
- });
227
+ ### With Docker
228
+ ```sh
229
+ ./build.sh
841
230
  ```
842
231
 
843
- ---
232
+ ### Running on k8s
844
233
 
845
- ## 🧪 Testing
234
+ [Running in k8s](server/k8s-example.yaml)
846
235
 
847
- ```bash
848
- # Run test suite
849
- npm test
236
+ ## 🔌 Raw HTTP API
850
237
 
851
- # Run specific test
852
- npm test -- --grep "Pipeline"
238
+ You can use Queen directly from HTTP without the JS client.
853
239
 
854
- # Benchmark
855
- npm run benchmark
856
- ```
240
+ [Here the complete list of API endpoints](API.md)
857
241
 
858
- ---
242
+ ## ⚠️ Known Issues & Roadmap
859
243
 
860
- ## 🤝 Contributing
244
+ ### Server Startup Timing (Critical)
245
+ **Issue:** Worker initialization timeout (30s → 3600s) now matches file buffer recovery timeout. This is a temporary fix.
861
246
 
862
- See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
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.
863
248
 
864
- ---
249
+ **Current Fix:** Worker initialization timeout increased to 3600s to prevent premature timeout.
865
250
 
866
- ## 📄 License
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)
867
257
 
868
- Apache 2.0 - see [LICENSE.md](LICENSE.md)
258
+ ### Other TODO Items
259
+ - retention jobs
260
+ - reconsume
261
+ - fix frontend
262
+ - pg async
263
+ - pg reconnect
264
+ - memory leak?