queen-mq 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +0 -0
- package/README.md +1483 -1052
- package/examples/test-complete-client.js +260 -0
- package/examples/test-traceid.js +147 -0
- package/package.json +14 -3
- package/src/client/client.js +113 -20
- package/src/test/MIGRATION_ISSUES.md +174 -0
- package/src/test/README.md +203 -0
- package/src/test/advanced-pattern-tests.js +1137 -0
- package/src/test/bus-mode-tests.js +348 -0
- package/src/test/core-tests.js +342 -0
- package/src/test/edge-case-tests.js +554 -0
- package/src/test/enterprise-tests.js +604 -0
- package/src/test/partition-locking-tests.js +545 -0
- package/src/test/test-new.js +278 -0
- package/src/test/test.js +6 -3
- package/src/test/utils.js +169 -0
- package/API.md +0 -1116
- package/CACHE.md +0 -519
- package/DASHBOARD-V3.md +0 -478
- package/DASHBOARD.md +0 -382
- package/MOD_QUEUE.md +0 -453
- package/PARTITION_LOCKING_DESIGN.md +0 -989
- package/PLAN.md +0 -707
- package/QUERY_ANALSYS.md +0 -72
- package/QUEUE_BUS.md +0 -334
- package/V2-PLAN.md +0 -236
- package/debug-namespace.js +0 -110
- package/docs/long-polling.md +0 -159
- package/docs/multi-server-cache-solutions.md +0 -185
- package/docs/performance-tuning.md +0 -222
package/README.md
CHANGED
|
@@ -1,40 +1,117 @@
|
|
|
1
|
-
# Queen -
|
|
1
|
+
# Queen - PostgreSQL-backed Message Queue System
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
**A modern, high-performance message queue system built on PostgreSQL**
|
|
6
|
+
|
|
7
|
+
[](LICENSE.md)
|
|
8
|
+
[](https://nodejs.org/)
|
|
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)
|
|
11
|
+
|
|
12
|
+
</div>
|
|
4
13
|
|
|
5
14
|

|
|
6
15
|
|
|
7
|
-
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 🎯 Introduction
|
|
19
|
+
|
|
20
|
+
**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.
|
|
21
|
+
|
|
22
|
+
### Why Queen?
|
|
8
23
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
24
|
+
**🚀 Developer-First API**
|
|
25
|
+
- **4 methods, infinite patterns**: `queue()`, `push()`, `take()`, `ack()`
|
|
26
|
+
- **Async iteration**: Process messages with familiar `for await` syntax
|
|
27
|
+
- **Smart addressing**: `orders/urgent@workers` - queue, partition, and consumer group in one
|
|
28
|
+
|
|
29
|
+
**⚡ Production-Ready Performance**
|
|
30
|
+
- **10,000+ msg/sec** throughput with sub-10ms latency
|
|
31
|
+
- **Long polling** for event-driven, real-time message delivery
|
|
32
|
+
- **Partition locking** prevents duplicate processing across consumers
|
|
33
|
+
- **Connection pooling** and optimized batch operations
|
|
34
|
+
|
|
35
|
+
**🏗️ Flexible Architecture**
|
|
36
|
+
- **Queue Mode**: Competitive consumption (traditional work queue)
|
|
37
|
+
- **Bus Mode**: Pub/sub with consumer groups (event streaming)
|
|
38
|
+
- **Mixed Mode**: Combine both patterns in the same system
|
|
39
|
+
- **Partitions**: FIFO ordering with parallel processing
|
|
40
|
+
|
|
41
|
+
**🔒 Enterprise Features**
|
|
42
|
+
- **AES-256-GCM Encryption**: Protect sensitive data at rest
|
|
43
|
+
- **Message Retention**: Automatic cleanup policies
|
|
44
|
+
- **Message Eviction**: SLA enforcement for time-sensitive tasks
|
|
45
|
+
- **Dead Letter Queue**: Handle failed messages gracefully
|
|
46
|
+
|
|
47
|
+
**📊 Built-in Observability**
|
|
48
|
+
- **Real-time Dashboard**: WebSocket-powered monitoring
|
|
49
|
+
- **Rich Analytics**: Throughput, lag, queue depth metrics
|
|
50
|
+
- **Message Browser**: Search, inspect, and retry messages
|
|
51
|
+
- **System Health**: Database, memory, and performance metrics
|
|
52
|
+
|
|
53
|
+
### Use Cases
|
|
54
|
+
|
|
55
|
+
- **Task Queues**: Background jobs, email sending, data processing
|
|
56
|
+
- **Event Streaming**: Audit logs, analytics, multi-service event handling
|
|
57
|
+
- **Workflow Orchestration**: Multi-stage pipelines, saga patterns
|
|
58
|
+
- **Rate Limiting**: Throttle and batch time-sensitive operations
|
|
59
|
+
- **Priority Processing**: Handle urgent tasks before routine ones
|
|
60
|
+
|
|
61
|
+
---
|
|
19
62
|
|
|
20
63
|
## 📋 Table of Contents
|
|
21
64
|
|
|
22
|
-
- [
|
|
23
|
-
- [
|
|
24
|
-
- [
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
- [
|
|
28
|
-
- [
|
|
29
|
-
- [
|
|
30
|
-
- [
|
|
65
|
+
- [First Queue](#-first-queue)
|
|
66
|
+
- [Quick Start](#-quick-start)
|
|
67
|
+
- [Client Examples](#-client-examples)
|
|
68
|
+
- [Server Setup](#-server-setup)
|
|
69
|
+
- [Core Concepts](#-core-concepts)
|
|
70
|
+
- [HTTP API Reference](#-http-api-reference)
|
|
71
|
+
- [Dashboard](#-dashboard)
|
|
72
|
+
- [Configuration](#-configuration)
|
|
73
|
+
- [Full Examples](#-full-examples)
|
|
74
|
+
- [Testing](#-testing)
|
|
75
|
+
- [Contributing](#-contributing)
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## First Queue
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
import { Queen } from 'queen-mq';
|
|
83
|
+
|
|
84
|
+
const client = new Queen({
|
|
85
|
+
baseUrls: ['http://localhost:6632']
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Configure queue
|
|
89
|
+
await client.queue('tasks', {
|
|
90
|
+
leaseTime: 300,
|
|
91
|
+
retryLimit: 3
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Push a message
|
|
95
|
+
await client.push('tasks', {
|
|
96
|
+
action: 'send-email',
|
|
97
|
+
to: 'user@example.com'
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Process messages
|
|
101
|
+
for await (const message of client.take('tasks')) {
|
|
102
|
+
console.log('Processing:', message.data);
|
|
103
|
+
await client.ack(message);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
That's it! You now have a working message queue system.
|
|
31
108
|
|
|
32
109
|
## 🏃 Quick Start
|
|
33
110
|
|
|
34
111
|
### Prerequisites
|
|
35
112
|
|
|
36
|
-
- Node.js 22
|
|
37
|
-
- PostgreSQL 12
|
|
113
|
+
- **Node.js 22+**
|
|
114
|
+
- **PostgreSQL 12+**
|
|
38
115
|
|
|
39
116
|
### Installation
|
|
40
117
|
|
|
@@ -47,556 +124,788 @@ cd queen
|
|
|
47
124
|
nvm use 22
|
|
48
125
|
npm install
|
|
49
126
|
|
|
50
|
-
#
|
|
127
|
+
# Initialize database schema
|
|
128
|
+
node init-db.js
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Set Environment (Optional)
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Database connection
|
|
51
135
|
export PG_USER=postgres
|
|
52
136
|
export PG_HOST=localhost
|
|
53
137
|
export PG_DB=postgres
|
|
54
138
|
export PG_PASSWORD=postgres
|
|
55
139
|
export PG_PORT=5432
|
|
140
|
+
|
|
141
|
+
# Enable encryption (optional)
|
|
142
|
+
export QUEEN_ENCRYPTION_KEY=$(openssl rand -hex 32)
|
|
56
143
|
```
|
|
57
144
|
|
|
58
|
-
###
|
|
145
|
+
### Start the Server
|
|
59
146
|
|
|
60
147
|
```bash
|
|
61
|
-
|
|
62
|
-
|
|
148
|
+
npm start
|
|
149
|
+
# Server starts on http://localhost:6632
|
|
63
150
|
```
|
|
64
151
|
|
|
65
|
-
|
|
152
|
+
---
|
|
66
153
|
|
|
67
|
-
|
|
68
|
-
# Optional: Enable encryption
|
|
69
|
-
export QUEEN_ENCRYPTION_KEY=$(openssl rand -hex 32)
|
|
154
|
+
## 💻 Client Examples
|
|
70
155
|
|
|
71
|
-
|
|
72
|
-
npm start
|
|
73
|
-
# Or use the startup script
|
|
74
|
-
./start.sh
|
|
156
|
+
The Queen client provides a minimalist API with just 4 methods that compose into any messaging pattern you need.
|
|
75
157
|
|
|
76
|
-
|
|
158
|
+
### Installation
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm install queen-mq
|
|
77
162
|
```
|
|
78
163
|
|
|
79
164
|
### Basic Usage
|
|
80
165
|
|
|
166
|
+
#### 1. Configure a Queue
|
|
167
|
+
|
|
81
168
|
```javascript
|
|
82
|
-
import {
|
|
169
|
+
import { Queen } from 'queen-mq';
|
|
170
|
+
|
|
171
|
+
const client = new Queen({
|
|
172
|
+
baseUrls: ['http://localhost:6632'],
|
|
173
|
+
timeout: 30000,
|
|
174
|
+
retryAttempts: 3
|
|
175
|
+
});
|
|
83
176
|
|
|
84
|
-
|
|
85
|
-
|
|
177
|
+
// Configure with options
|
|
178
|
+
await client.queue('orders', {
|
|
179
|
+
priority: 10, // Higher priority queues processed first
|
|
180
|
+
leaseTime: 600, // 10 minutes to process each message
|
|
181
|
+
retryLimit: 3, // Retry up to 3 times
|
|
182
|
+
delayedProcessing: 0, // No delay (immediate processing)
|
|
86
183
|
});
|
|
87
184
|
|
|
88
|
-
//
|
|
89
|
-
await client.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}]
|
|
185
|
+
// Configure with namespace and task for grouping
|
|
186
|
+
await client.queue('order-processing', {
|
|
187
|
+
priority: 10
|
|
188
|
+
}, {
|
|
189
|
+
namespace: 'ecommerce',
|
|
190
|
+
task: 'checkout'
|
|
95
191
|
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### 2. Push Messages
|
|
96
195
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
196
|
+
```javascript
|
|
197
|
+
// Single message
|
|
198
|
+
await client.push('orders', {
|
|
199
|
+
orderId: 12345,
|
|
200
|
+
amount: 99.99
|
|
102
201
|
});
|
|
103
202
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
203
|
+
// To a specific partition
|
|
204
|
+
await client.push('orders/urgent', {
|
|
205
|
+
orderId: 12346,
|
|
206
|
+
amount: 999.99,
|
|
207
|
+
priority: 'high'
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Batch messages
|
|
211
|
+
await client.push('orders', [
|
|
212
|
+
{ orderId: 12347, amount: 49.99 },
|
|
213
|
+
{ orderId: 12348, amount: 79.99 },
|
|
214
|
+
{ orderId: 12349, amount: 29.99 }
|
|
215
|
+
]);
|
|
216
|
+
|
|
217
|
+
// With message properties
|
|
218
|
+
await client.push('orders', {
|
|
219
|
+
orderId: 12350,
|
|
220
|
+
amount: 199.99
|
|
221
|
+
}, {
|
|
222
|
+
transactionId: 'txn-12350', // For idempotency
|
|
223
|
+
traceId: '550e8400-e29b-41d4-a716-446655440000' // Valid UUID for tracing
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### 3. Take Messages (Async Iterator)
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
// Process continuously with long polling
|
|
231
|
+
for await (const message of client.take('orders', {
|
|
232
|
+
wait: true, // Enable long polling
|
|
233
|
+
timeout: 30000, // 30 second timeout
|
|
234
|
+
batch: 10 // Fetch up to 10 at once
|
|
235
|
+
})) {
|
|
236
|
+
try {
|
|
237
|
+
await processOrder(message.data);
|
|
238
|
+
await client.ack(message); // Success
|
|
239
|
+
} catch (error) {
|
|
240
|
+
await client.ack(message, false, { error: error.message }); // Failure
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Process limited messages
|
|
245
|
+
for await (const message of client.take('orders', { limit: 100 })) {
|
|
246
|
+
await processOrder(message.data);
|
|
247
|
+
await client.ack(message);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Take from specific partition
|
|
251
|
+
for await (const message of client.take('orders/urgent')) {
|
|
252
|
+
await processUrgentOrder(message.data);
|
|
253
|
+
await client.ack(message);
|
|
107
254
|
}
|
|
108
255
|
```
|
|
109
256
|
|
|
110
|
-
|
|
257
|
+
#### 4. Acknowledge Messages
|
|
258
|
+
|
|
259
|
+
```javascript
|
|
260
|
+
// Acknowledge success
|
|
261
|
+
await client.ack(message);
|
|
262
|
+
// or
|
|
263
|
+
await client.ack(message, true);
|
|
264
|
+
|
|
265
|
+
// Acknowledge failure (will retry based on retryLimit)
|
|
266
|
+
await client.ack(message, false);
|
|
111
267
|
|
|
112
|
-
|
|
268
|
+
// Acknowledge with error context
|
|
269
|
+
await client.ack(message, false, {
|
|
270
|
+
error: 'Payment gateway timeout'
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Acknowledge using transaction ID
|
|
274
|
+
await client.ack('4dfb0478-655b-4c91-bcd9-b7acacf0400f', true);
|
|
113
275
|
|
|
276
|
+
// Request explicit retry
|
|
277
|
+
await client.ack(message, 'retry');
|
|
114
278
|
```
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
279
|
+
|
|
280
|
+
### Address Notation
|
|
281
|
+
|
|
282
|
+
Queen uses a simple addressing scheme that encodes queue, partition, and consumer group:
|
|
283
|
+
|
|
284
|
+
```javascript
|
|
285
|
+
// Basic addresses
|
|
286
|
+
'orders' // Queue (Default partition)
|
|
287
|
+
'orders/urgent' // Queue with specific partition
|
|
288
|
+
'orders@workers' // Queue with consumer group (bus mode)
|
|
289
|
+
'orders/urgent@workers' // Full address: queue + partition + group
|
|
290
|
+
|
|
291
|
+
// Namespace/task filtering (cross-queue consumption)
|
|
292
|
+
'namespace:ecommerce' // All queues in namespace
|
|
293
|
+
'task:checkout' // All queues with task
|
|
294
|
+
'namespace:ecommerce/task:checkout' // Combined filter
|
|
295
|
+
'namespace:ecommerce/task:checkout@audit' // With consumer group
|
|
123
296
|
```
|
|
124
297
|
|
|
125
|
-
###
|
|
298
|
+
### Consumer Patterns
|
|
299
|
+
|
|
300
|
+
#### Continuous Processing (Long Polling)
|
|
126
301
|
|
|
302
|
+
```javascript
|
|
303
|
+
// Efficient real-time processing
|
|
304
|
+
for await (const message of client.take('tasks', {
|
|
305
|
+
wait: true, // Long polling - waits for messages
|
|
306
|
+
timeout: 30000 // Server timeout
|
|
307
|
+
})) {
|
|
308
|
+
await processTask(message.data);
|
|
309
|
+
await client.ack(message);
|
|
310
|
+
}
|
|
127
311
|
```
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
312
|
+
|
|
313
|
+
#### Batch Processing
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
// Accumulate and process in batches
|
|
317
|
+
const batch = [];
|
|
318
|
+
for await (const message of client.take('analytics', { batch: 100 })) {
|
|
319
|
+
batch.push(message);
|
|
320
|
+
|
|
321
|
+
if (batch.length >= 100) {
|
|
322
|
+
await processBatch(batch.map(m => m.data));
|
|
323
|
+
|
|
324
|
+
// Acknowledge all
|
|
325
|
+
for (const msg of batch) {
|
|
326
|
+
await client.ack(msg);
|
|
327
|
+
}
|
|
328
|
+
batch.length = 0;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
131
331
|
```
|
|
132
332
|
|
|
133
|
-
|
|
134
|
-
- `queen.queues` - Top-level message containers with optional grouping
|
|
135
|
-
- `queen.partitions` - Subdivisions within queues where FIFO is maintained
|
|
136
|
-
- `queen.messages` - Individual messages with processing state
|
|
333
|
+
#### Parallel Processing with Partitions
|
|
137
334
|
|
|
138
|
-
|
|
335
|
+
```javascript
|
|
336
|
+
// Create workers for parallel processing
|
|
337
|
+
const partitions = ['worker-1', 'worker-2', 'worker-3', 'worker-4'];
|
|
139
338
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
339
|
+
// Distribute messages across partitions
|
|
340
|
+
for (let i = 0; i < messages.length; i++) {
|
|
341
|
+
const partition = partitions[i % partitions.length];
|
|
342
|
+
await client.push(`tasks/${partition}`, messages[i]);
|
|
343
|
+
}
|
|
145
344
|
|
|
146
|
-
|
|
345
|
+
// Each worker processes its own partition (in parallel)
|
|
346
|
+
async function worker(partition) {
|
|
347
|
+
for await (const msg of client.take(`tasks/${partition}`)) {
|
|
348
|
+
await processTask(msg.data);
|
|
349
|
+
await client.ack(msg);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
147
352
|
|
|
148
|
-
|
|
353
|
+
// Start all workers
|
|
354
|
+
await Promise.all(partitions.map(p => worker(p)));
|
|
355
|
+
```
|
|
149
356
|
|
|
150
|
-
|
|
357
|
+
#### Consumer Groups (Bus Mode)
|
|
151
358
|
|
|
152
359
|
```javascript
|
|
153
|
-
//
|
|
154
|
-
await client.push({
|
|
155
|
-
items: [{ queue: 'orders', payload: { orderId: 123 } }]
|
|
156
|
-
});
|
|
360
|
+
// Multiple services process the same messages independently
|
|
157
361
|
|
|
158
|
-
//
|
|
159
|
-
await client.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
362
|
+
// Analytics service
|
|
363
|
+
for await (const event of client.take('events@analytics')) {
|
|
364
|
+
await updateAnalytics(event.data);
|
|
365
|
+
await client.ack(event, true, { group: 'analytics' });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Monitoring service (gets same messages)
|
|
369
|
+
for await (const event of client.take('events@monitoring')) {
|
|
370
|
+
await checkThresholds(event.data);
|
|
371
|
+
await client.ack(event, true, { group: 'monitoring' });
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Audit service (also gets same messages)
|
|
375
|
+
for await (const event of client.take('events@audit')) {
|
|
376
|
+
await logToAudit(event.data);
|
|
377
|
+
await client.ack(event, true, { group: 'audit' });
|
|
378
|
+
}
|
|
166
379
|
```
|
|
167
380
|
|
|
168
|
-
|
|
381
|
+
#### Subscription Modes
|
|
382
|
+
|
|
383
|
+
```javascript
|
|
384
|
+
// Start from all existing messages (replay)
|
|
385
|
+
for await (const event of client.take('events@replay-service', {
|
|
386
|
+
subscriptionMode: 'all'
|
|
387
|
+
})) {
|
|
388
|
+
await replayEvent(event.data);
|
|
389
|
+
await client.ack(event);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Start from new messages only (real-time)
|
|
393
|
+
for await (const event of client.take('events@realtime', {
|
|
394
|
+
subscriptionMode: 'new'
|
|
395
|
+
})) {
|
|
396
|
+
await processEvent(event.data);
|
|
397
|
+
await client.ack(event);
|
|
398
|
+
}
|
|
169
399
|
|
|
170
|
-
|
|
400
|
+
// Start from specific timestamp
|
|
401
|
+
for await (const event of client.take('events@historical', {
|
|
402
|
+
subscriptionMode: 'from',
|
|
403
|
+
subscriptionFrom: '2024-01-01T00:00:00Z'
|
|
404
|
+
})) {
|
|
405
|
+
await processHistoricalEvent(event.data);
|
|
406
|
+
await client.ack(event);
|
|
407
|
+
}
|
|
408
|
+
```
|
|
171
409
|
|
|
172
|
-
|
|
173
|
-
2. **FIFO Within Partitions**: Messages within the same partition are always processed in order
|
|
174
|
-
3. **Partitions**: Partitions are now simple FIFO containers - all configuration is at the queue level
|
|
410
|
+
#### Error Handling
|
|
175
411
|
|
|
176
412
|
```javascript
|
|
177
|
-
//
|
|
178
|
-
await client.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
413
|
+
// Robust error handling with retries
|
|
414
|
+
for await (const message of client.take('critical-tasks')) {
|
|
415
|
+
let retries = 3;
|
|
416
|
+
|
|
417
|
+
while (retries > 0) {
|
|
418
|
+
try {
|
|
419
|
+
await processTask(message.data);
|
|
420
|
+
await client.ack(message);
|
|
421
|
+
break;
|
|
422
|
+
} catch (error) {
|
|
423
|
+
retries--;
|
|
424
|
+
if (retries === 0) {
|
|
425
|
+
console.error('Task failed after retries:', error);
|
|
426
|
+
await client.ack(message, false, { error: error.message });
|
|
427
|
+
} else {
|
|
428
|
+
await new Promise(r => setTimeout(r, 1000 * (4 - retries)));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
182
433
|
```
|
|
183
434
|
|
|
184
|
-
|
|
435
|
+
#### Graceful Shutdown
|
|
436
|
+
|
|
437
|
+
```javascript
|
|
438
|
+
let running = true;
|
|
439
|
+
|
|
440
|
+
process.on('SIGTERM', () => {
|
|
441
|
+
console.log('Shutting down gracefully...');
|
|
442
|
+
running = false;
|
|
443
|
+
});
|
|
185
444
|
|
|
445
|
+
for await (const message of client.take('orders')) {
|
|
446
|
+
if (!running) break;
|
|
447
|
+
|
|
448
|
+
await processOrder(message.data);
|
|
449
|
+
await client.ack(message);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
await client.close();
|
|
453
|
+
console.log('Shutdown complete');
|
|
186
454
|
```
|
|
187
|
-
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## 🖥️ Server Setup
|
|
459
|
+
|
|
460
|
+
### Single Server
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
# Start the server
|
|
464
|
+
npm start
|
|
465
|
+
|
|
466
|
+
# Or with custom configuration
|
|
467
|
+
PORT=6632 \
|
|
468
|
+
DB_POOL_SIZE=20 \
|
|
469
|
+
QUEEN_ENCRYPTION_KEY=$(openssl rand -hex 32) \
|
|
470
|
+
npm start
|
|
188
471
|
```
|
|
189
472
|
|
|
190
|
-
|
|
191
|
-
2. **Processing**: Message is leased to a worker (with timeout)
|
|
192
|
-
3. **Completed**: Message was successfully processed
|
|
193
|
-
4. **Failed**: Message processing failed (may retry based on configuration)
|
|
194
|
-
5. **Dead Letter**: Message exceeded retry limits
|
|
473
|
+
### Multi-Server (Load Balanced)
|
|
195
474
|
|
|
196
|
-
|
|
475
|
+
Queen supports running multiple servers for high availability and load distribution:
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
# Server 1
|
|
479
|
+
PORT=6632 WORKER_ID=server-1 npm start
|
|
480
|
+
|
|
481
|
+
# Server 2
|
|
482
|
+
PORT=6633 WORKER_ID=server-2 npm start
|
|
483
|
+
|
|
484
|
+
# Server 3
|
|
485
|
+
PORT=6634 WORKER_ID=server-3 npm start
|
|
486
|
+
```
|
|
197
487
|
|
|
198
|
-
|
|
488
|
+
Client configuration:
|
|
199
489
|
|
|
200
490
|
```javascript
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
491
|
+
const client = new Queen({
|
|
492
|
+
baseUrls: [
|
|
493
|
+
'http://localhost:6632',
|
|
494
|
+
'http://localhost:6633',
|
|
495
|
+
'http://localhost:6634'
|
|
496
|
+
],
|
|
497
|
+
loadBalancingStrategy: 'ROUND_ROBIN', // or 'RANDOM', 'LEAST_CONNECTIONS'
|
|
498
|
+
enableFailover: true
|
|
205
499
|
});
|
|
206
500
|
```
|
|
207
501
|
|
|
208
|
-
|
|
502
|
+
### Docker Deployment
|
|
209
503
|
|
|
210
|
-
|
|
504
|
+
```dockerfile
|
|
505
|
+
FROM node:22-alpine
|
|
506
|
+
|
|
507
|
+
WORKDIR /app
|
|
508
|
+
COPY package*.json ./
|
|
509
|
+
RUN npm ci --production
|
|
211
510
|
|
|
212
|
-
|
|
511
|
+
COPY . .
|
|
213
512
|
|
|
214
|
-
|
|
513
|
+
EXPOSE 6632
|
|
514
|
+
CMD ["node", "src/server.js"]
|
|
515
|
+
```
|
|
215
516
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
- Records the lease with an expiration time based on the queue's `leaseTime`
|
|
517
|
+
```yaml
|
|
518
|
+
# docker-compose.yml
|
|
519
|
+
version: '3.8'
|
|
220
520
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
521
|
+
services:
|
|
522
|
+
postgres:
|
|
523
|
+
image: postgres:16
|
|
524
|
+
environment:
|
|
525
|
+
POSTGRES_DB: queen
|
|
526
|
+
POSTGRES_USER: queen
|
|
527
|
+
POSTGRES_PASSWORD: queen
|
|
528
|
+
volumes:
|
|
529
|
+
- postgres_data:/var/lib/postgresql/data
|
|
530
|
+
ports:
|
|
531
|
+
- "5432:5432"
|
|
225
532
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
533
|
+
queen:
|
|
534
|
+
build: .
|
|
535
|
+
ports:
|
|
536
|
+
- "6632:6632"
|
|
537
|
+
environment:
|
|
538
|
+
PG_HOST: postgres
|
|
539
|
+
PG_DB: queen
|
|
540
|
+
PG_USER: queen
|
|
541
|
+
PG_PASSWORD: queen
|
|
542
|
+
DB_POOL_SIZE: 20
|
|
543
|
+
QUEEN_ENCRYPTION_KEY: ${QUEEN_ENCRYPTION_KEY}
|
|
544
|
+
depends_on:
|
|
545
|
+
- postgres
|
|
229
546
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
547
|
+
volumes:
|
|
548
|
+
postgres_data:
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Environment Variables
|
|
552
|
+
|
|
553
|
+
See the [Configuration](#-configuration) section for a complete list of environment variables.
|
|
234
554
|
|
|
235
|
-
|
|
236
|
-
// Consumer 2 gets messages from partition B (A is locked)
|
|
555
|
+
### Database Schema
|
|
237
556
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
557
|
+
The database schema is automatically created when you run:
|
|
558
|
+
|
|
559
|
+
```bash
|
|
560
|
+
node init-db.js
|
|
241
561
|
```
|
|
242
562
|
|
|
243
|
-
|
|
563
|
+
This creates:
|
|
564
|
+
- `queen.queues` - Top-level message containers
|
|
565
|
+
- `queen.partitions` - Subdivisions within queues (FIFO ordering)
|
|
566
|
+
- `queen.messages` - Individual messages with processing state
|
|
244
567
|
|
|
245
|
-
|
|
568
|
+
---
|
|
246
569
|
|
|
247
|
-
|
|
570
|
+
## 💡 Core Concepts
|
|
248
571
|
|
|
249
|
-
|
|
572
|
+
### Architecture
|
|
250
573
|
|
|
251
|
-
|
|
252
|
-
// These messages will be processed in order 1, 2, 3
|
|
253
|
-
await client.push({
|
|
254
|
-
items: [
|
|
255
|
-
{ queue: 'tasks', partition: 'user-123', payload: { step: 1 } },
|
|
256
|
-
{ queue: 'tasks', partition: 'user-123', payload: { step: 2 } },
|
|
257
|
-
{ queue: 'tasks', partition: 'user-123', payload: { step: 3 } }
|
|
258
|
-
]
|
|
259
|
-
});
|
|
574
|
+
Queen uses a two-tier architecture:
|
|
260
575
|
|
|
261
|
-
|
|
262
|
-
|
|
576
|
+
```
|
|
577
|
+
Queues (optional namespace/task grouping)
|
|
578
|
+
└── Partitions (FIFO ordering, parallel processing)
|
|
579
|
+
└── Messages (lease-based processing)
|
|
263
580
|
```
|
|
264
581
|
|
|
265
|
-
|
|
582
|
+
**Key Principles:**
|
|
583
|
+
- **Configuration at queue level**: All settings (priority, lease time, retries) apply to the entire queue
|
|
584
|
+
- **FIFO within partitions**: Messages in the same partition are always processed in order
|
|
585
|
+
- **Partition locking**: Prevents duplicate processing across consumers
|
|
586
|
+
- **Lease-based processing**: Messages automatically return to pending if not acknowledged
|
|
266
587
|
|
|
267
|
-
|
|
588
|
+
### Queues and Partitions
|
|
589
|
+
|
|
590
|
+
**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.
|
|
268
591
|
|
|
269
592
|
```javascript
|
|
270
|
-
//
|
|
271
|
-
await client.push({
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
593
|
+
// Messages go to "Default" partition
|
|
594
|
+
await client.push('orders', { orderId: 123 });
|
|
595
|
+
|
|
596
|
+
// Push to specific partition
|
|
597
|
+
await client.push('orders/high-priority', { orderId: 456 });
|
|
598
|
+
|
|
599
|
+
// Take from specific partition
|
|
600
|
+
for await (const order of client.take('orders/high-priority')) {
|
|
601
|
+
await processUrgentOrder(order.data);
|
|
602
|
+
await client.ack(order);
|
|
603
|
+
}
|
|
277
604
|
```
|
|
278
605
|
|
|
279
|
-
|
|
606
|
+
**Partitions enable:**
|
|
607
|
+
- **Parallel processing**: Different consumers can process different partitions simultaneously
|
|
608
|
+
- **Ordered processing**: FIFO guarantees within each partition
|
|
609
|
+
- **Logical separation**: Different priorities, teams, or workflow stages
|
|
610
|
+
- **Resource isolation**: Lock contention is per-partition
|
|
280
611
|
|
|
281
|
-
|
|
282
|
-
- **Per-Resource Processing**: Use resource ID to maintain operation order for specific resources
|
|
283
|
-
- **Priority Lanes**: Use different partitions for different priority levels
|
|
612
|
+
### Message Lifecycle
|
|
284
613
|
|
|
285
|
-
|
|
614
|
+
```
|
|
615
|
+
pending → processing → completed/failed → (retry) → dead_letter
|
|
616
|
+
```
|
|
286
617
|
|
|
287
|
-
|
|
618
|
+
1. **Pending**: Message queued, waiting to be processed
|
|
619
|
+
2. **Processing**: Leased to a worker (with timeout)
|
|
620
|
+
3. **Completed**: Successfully processed
|
|
621
|
+
4. **Failed**: Processing failed (may retry based on `retryLimit`)
|
|
622
|
+
5. **Dead Letter**: Exceeded retry limits
|
|
288
623
|
|
|
289
|
-
|
|
624
|
+
### Partition Locking
|
|
290
625
|
|
|
291
|
-
|
|
292
|
-
- Message status tracking
|
|
293
|
-
- Partition leases
|
|
294
|
-
- Retry counters
|
|
295
|
-
- Processing state
|
|
626
|
+
**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:
|
|
296
627
|
|
|
297
|
-
|
|
628
|
+
- The consumer acknowledges all messages (releases lock)
|
|
629
|
+
- The lease expires (automatic release)
|
|
630
|
+
- The consumer explicitly releases the partition
|
|
298
631
|
|
|
299
|
-
|
|
632
|
+
**Lock Scope:**
|
|
633
|
+
- **Queue Mode**: Each consumer session is unique - locks prevent any other consumer from accessing the partition
|
|
634
|
+
- **Bus Mode**: Locks are per consumer group - different groups can process the same partition independently
|
|
300
635
|
|
|
301
636
|
```javascript
|
|
302
|
-
//
|
|
303
|
-
const analyticsResult = await client.pop({
|
|
304
|
-
queue: 'events',
|
|
305
|
-
consumerGroup: 'analytics-service'
|
|
306
|
-
});
|
|
637
|
+
// Example: Partition locking in action
|
|
307
638
|
|
|
308
|
-
//
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
639
|
+
// Consumer 1 takes from partition A (locks it)
|
|
640
|
+
for await (const msg of client.take('orders', { limit: 5 })) {
|
|
641
|
+
// Processing partition A - no other consumer can access it
|
|
642
|
+
await client.ack(msg);
|
|
643
|
+
// Partition A unlocked after all 5 messages acknowledged
|
|
644
|
+
}
|
|
313
645
|
|
|
314
|
-
//
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
646
|
+
// Consumer 2 gets messages from partition B (A was locked)
|
|
647
|
+
for await (const msg of client.take('orders', { limit: 5 })) {
|
|
648
|
+
// Processing partition B instead
|
|
649
|
+
await client.ack(msg);
|
|
650
|
+
}
|
|
319
651
|
```
|
|
320
652
|
|
|
321
|
-
|
|
653
|
+
### FIFO Ordering
|
|
322
654
|
|
|
323
|
-
|
|
655
|
+
Queen provides **strong FIFO guarantees within each partition**:
|
|
324
656
|
|
|
325
657
|
```javascript
|
|
326
|
-
//
|
|
327
|
-
await client.
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
// Start from messages created after joining
|
|
334
|
-
await client.pop({
|
|
335
|
-
queue: 'events',
|
|
336
|
-
consumerGroup: 'realtime-service',
|
|
337
|
-
subscriptionMode: 'new'
|
|
338
|
-
});
|
|
658
|
+
// These messages will be processed in order 1, 2, 3
|
|
659
|
+
await client.push('tasks/user-123', [
|
|
660
|
+
{ step: 1, action: 'create' },
|
|
661
|
+
{ step: 2, action: 'update' },
|
|
662
|
+
{ step: 3, action: 'complete' }
|
|
663
|
+
]);
|
|
339
664
|
|
|
340
|
-
//
|
|
341
|
-
await client.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
});
|
|
665
|
+
// Consumer will always receive them in order
|
|
666
|
+
for await (const task of client.take('tasks/user-123')) {
|
|
667
|
+
console.log(task.data.step); // Prints: 1, then 2, then 3
|
|
668
|
+
await client.ack(task);
|
|
669
|
+
}
|
|
346
670
|
```
|
|
347
671
|
|
|
348
|
-
|
|
672
|
+
**Use cases:**
|
|
673
|
+
- **Per-user operations**: Use user ID as partition for ordered processing
|
|
674
|
+
- **Per-resource operations**: Use resource ID to maintain operation order
|
|
675
|
+
- **Workflow stages**: Use partition to represent different stages
|
|
349
676
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
#### Namespace-Based Routing
|
|
677
|
+
### Consumer Groups (Bus Mode)
|
|
353
678
|
|
|
354
|
-
|
|
679
|
+
Consumer groups enable **pub-sub messaging** where multiple independent consumers process the same messages:
|
|
355
680
|
|
|
356
681
|
```javascript
|
|
357
|
-
//
|
|
358
|
-
await client.
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
682
|
+
// Push once
|
|
683
|
+
await client.push('events', { type: 'order.created', orderId: 123 });
|
|
684
|
+
|
|
685
|
+
// Multiple services consume independently
|
|
686
|
+
// Service 1: Analytics
|
|
687
|
+
for await (const event of client.take('events@analytics')) {
|
|
688
|
+
await updateAnalytics(event.data);
|
|
689
|
+
await client.ack(event);
|
|
690
|
+
}
|
|
364
691
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
});
|
|
692
|
+
// Service 2: Notification (gets same message)
|
|
693
|
+
for await (const event of client.take('events@notification')) {
|
|
694
|
+
await sendNotification(event.data);
|
|
695
|
+
await client.ack(event);
|
|
696
|
+
}
|
|
371
697
|
|
|
372
|
-
//
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
698
|
+
// Service 3: Audit (also gets same message)
|
|
699
|
+
for await (const event of client.take('events@audit')) {
|
|
700
|
+
await logEvent(event.data);
|
|
701
|
+
await client.ack(event);
|
|
702
|
+
}
|
|
377
703
|
```
|
|
378
704
|
|
|
379
|
-
|
|
705
|
+
**Each consumer group maintains:**
|
|
706
|
+
- Independent message status tracking
|
|
707
|
+
- Separate partition leases
|
|
708
|
+
- Individual retry counters
|
|
709
|
+
- Isolated processing state
|
|
380
710
|
|
|
381
|
-
|
|
711
|
+
### Queue Mode vs Bus Mode
|
|
382
712
|
|
|
713
|
+
**Queue Mode** (default - competitive consumption):
|
|
383
714
|
```javascript
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
task: 'process'
|
|
388
|
-
}, { batch: 5 });
|
|
389
|
-
```
|
|
715
|
+
// Without consumer group - messages distributed
|
|
716
|
+
await client.push('tasks', { id: 1 });
|
|
717
|
+
await client.push('tasks', { id: 2 });
|
|
390
718
|
|
|
391
|
-
|
|
719
|
+
// Worker 1 gets message 1
|
|
720
|
+
for await (const msg of client.take('tasks')) { }
|
|
392
721
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
- Consumer groups maintain independent locks
|
|
722
|
+
// Worker 2 gets message 2 (different message)
|
|
723
|
+
for await (const msg of client.take('tasks')) { }
|
|
724
|
+
```
|
|
397
725
|
|
|
726
|
+
**Bus Mode** (pub-sub with consumer groups):
|
|
398
727
|
```javascript
|
|
399
|
-
//
|
|
400
|
-
|
|
728
|
+
// With consumer groups - all groups see all messages
|
|
729
|
+
await client.push('events', { id: 1 });
|
|
401
730
|
|
|
402
|
-
//
|
|
403
|
-
const
|
|
731
|
+
// Group 1 gets message 1
|
|
732
|
+
for await (const msg of client.take('events@group1')) { }
|
|
404
733
|
|
|
405
|
-
//
|
|
734
|
+
// Group 2 also gets message 1 (same message)
|
|
735
|
+
for await (const msg of client.take('events@group2')) { }
|
|
406
736
|
```
|
|
407
737
|
|
|
408
|
-
|
|
738
|
+
**Mixed Mode** (combine both):
|
|
739
|
+
```javascript
|
|
740
|
+
// Competitive workers process jobs
|
|
741
|
+
for await (const job of client.take('jobs')) {
|
|
742
|
+
await processJob(job.data);
|
|
743
|
+
await client.ack(job);
|
|
744
|
+
}
|
|
409
745
|
|
|
410
|
-
|
|
746
|
+
// Monitoring sees all jobs (bus mode)
|
|
747
|
+
for await (const job of client.take('jobs@monitoring')) {
|
|
748
|
+
await monitorJob(job.data);
|
|
749
|
+
await client.ack(job);
|
|
750
|
+
}
|
|
751
|
+
```
|
|
411
752
|
|
|
412
|
-
|
|
753
|
+
### Priority Processing
|
|
413
754
|
|
|
414
|
-
|
|
755
|
+
Configure priority at the **queue level**:
|
|
415
756
|
|
|
416
757
|
```javascript
|
|
417
|
-
|
|
418
|
-
|
|
758
|
+
await client.queue('urgent-orders', { priority: 100 });
|
|
759
|
+
await client.queue('normal-orders', { priority: 50 });
|
|
760
|
+
await client.queue('batch-jobs', { priority: 10 });
|
|
419
761
|
|
|
420
|
-
//
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
762
|
+
// Urgent orders processed first, then normal, then batch
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
### Lease-Based Processing
|
|
766
|
+
|
|
767
|
+
Messages are "leased" to workers for a specific duration. If not acknowledged within the lease time, they automatically return to pending status:
|
|
768
|
+
|
|
769
|
+
```javascript
|
|
770
|
+
// Configure lease time
|
|
771
|
+
await client.queue('long-tasks', {
|
|
772
|
+
leaseTime: 600 // 10 minutes to process
|
|
427
773
|
});
|
|
428
774
|
|
|
429
|
-
//
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
//
|
|
775
|
+
// If worker crashes or takes too long:
|
|
776
|
+
// - After 10 minutes, lease expires
|
|
777
|
+
// - Message returns to pending
|
|
778
|
+
// - Another worker can pick it up
|
|
433
779
|
```
|
|
434
780
|
|
|
435
|
-
|
|
781
|
+
### Delayed Processing
|
|
436
782
|
|
|
437
|
-
|
|
783
|
+
Schedule messages for future processing:
|
|
438
784
|
|
|
439
785
|
```javascript
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
queue: 'quick-tasks',
|
|
443
|
-
options: {
|
|
444
|
-
leaseTime: 30, // 30 seconds per message
|
|
445
|
-
retryLimit: 3 // Retry up to 3 times
|
|
446
|
-
}
|
|
786
|
+
await client.queue('scheduled-jobs', {
|
|
787
|
+
delayedProcessing: 3600 // 1 hour delay
|
|
447
788
|
});
|
|
448
789
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
queue: 'heavy-processing',
|
|
452
|
-
options: {
|
|
453
|
-
leaseTime: 600, // 10 minutes per message
|
|
454
|
-
retryLimit: 1 // Retry only once
|
|
455
|
-
}
|
|
790
|
+
await client.push('scheduled-jobs', {
|
|
791
|
+
reportType: 'daily-sales'
|
|
456
792
|
});
|
|
793
|
+
// Message won't be available for processing until 1 hour later
|
|
457
794
|
```
|
|
458
795
|
|
|
459
|
-
|
|
796
|
+
### Window Buffering
|
|
460
797
|
|
|
461
|
-
|
|
798
|
+
Batch messages within a time window:
|
|
462
799
|
|
|
463
800
|
```javascript
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
queue: 'tasks'
|
|
467
|
-
}, {
|
|
468
|
-
batch: 5 // Limit to 5 concurrent messages
|
|
801
|
+
await client.queue('analytics', {
|
|
802
|
+
windowBuffer: 60 // Wait 60 seconds to accumulate messages
|
|
469
803
|
});
|
|
470
804
|
|
|
471
|
-
//
|
|
472
|
-
for (const message of batch.messages) {
|
|
473
|
-
await processMessage(message);
|
|
474
|
-
await client.ack(message.transactionId, 'completed');
|
|
475
|
-
}
|
|
805
|
+
// Messages held for 60 seconds to allow efficient batching
|
|
476
806
|
```
|
|
477
807
|
|
|
478
|
-
###
|
|
808
|
+
### Retry and Dead Letter Queue
|
|
479
809
|
|
|
480
|
-
|
|
810
|
+
```javascript
|
|
811
|
+
await client.queue('payments', {
|
|
812
|
+
retryLimit: 3, // Retry up to 3 times
|
|
813
|
+
dlqAfterMaxRetries: true // Move to DLQ after max retries
|
|
814
|
+
});
|
|
481
815
|
|
|
482
|
-
|
|
816
|
+
// Failed messages automatically retry
|
|
817
|
+
await client.ack(message, false); // Will retry if retries < 3
|
|
483
818
|
|
|
484
|
-
|
|
485
|
-
// Without consumer group - competitive consumption
|
|
486
|
-
const consumer1 = await client.pop({ queue: 'tasks' });
|
|
487
|
-
const consumer2 = await client.pop({ queue: 'tasks' });
|
|
488
|
-
// Each consumer gets different messages
|
|
819
|
+
// After 3 failures, message moves to dead_letter status
|
|
489
820
|
```
|
|
490
821
|
|
|
491
|
-
|
|
822
|
+
### Enterprise Features
|
|
823
|
+
|
|
824
|
+
#### 1. Encryption (AES-256-GCM)
|
|
492
825
|
|
|
493
|
-
|
|
826
|
+
```bash
|
|
827
|
+
# Generate encryption key
|
|
828
|
+
export QUEEN_ENCRYPTION_KEY=$(openssl rand -hex 32)
|
|
829
|
+
```
|
|
494
830
|
|
|
495
831
|
```javascript
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
queue: 'events',
|
|
499
|
-
consumerGroup: 'service-1'
|
|
832
|
+
await client.queue('sensitive-data', {
|
|
833
|
+
encryptionEnabled: true
|
|
500
834
|
});
|
|
501
835
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
consumerGroup: 'service-2'
|
|
505
|
-
});
|
|
506
|
-
// Both services get the same messages
|
|
836
|
+
// Messages encrypted at rest in database
|
|
837
|
+
await client.push('sensitive-data', { ssn: '123-45-6789' });
|
|
507
838
|
```
|
|
508
839
|
|
|
509
|
-
####
|
|
510
|
-
|
|
511
|
-
You can combine both patterns in the same system:
|
|
840
|
+
#### 2. Message Retention
|
|
512
841
|
|
|
513
842
|
```javascript
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const monitor = await client.pop({
|
|
519
|
-
queue: 'jobs',
|
|
520
|
-
consumerGroup: 'monitoring'
|
|
843
|
+
await client.queue('temp-queue', {
|
|
844
|
+
retentionSeconds: 3600, // Delete pending after 1 hour
|
|
845
|
+
completedRetentionSeconds: 300, // Delete completed after 5 minutes
|
|
846
|
+
retentionEnabled: true
|
|
521
847
|
});
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
#### 3. Message Eviction (SLA Enforcement)
|
|
522
851
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
852
|
+
```javascript
|
|
853
|
+
await client.queue('time-sensitive', {
|
|
854
|
+
maxWaitTimeSeconds: 60 // Evict messages older than 1 minute
|
|
526
855
|
});
|
|
527
856
|
```
|
|
528
857
|
|
|
529
858
|
### Best Practices
|
|
530
859
|
|
|
531
|
-
|
|
860
|
+
**1. Partition Strategy**
|
|
861
|
+
- Use user IDs for per-user ordering
|
|
862
|
+
- Use resource IDs for per-resource ordering
|
|
863
|
+
- Use round-robin for load distribution
|
|
864
|
+
- Keep partition counts manageable (10-100s, not 1000s)
|
|
532
865
|
|
|
533
|
-
|
|
534
|
-
-
|
|
535
|
-
-
|
|
536
|
-
-
|
|
866
|
+
**2. Lease Management**
|
|
867
|
+
- Set lease time slightly longer than expected processing time
|
|
868
|
+
- Handle timeouts gracefully
|
|
869
|
+
- Acknowledge messages as soon as processing completes
|
|
537
870
|
|
|
538
|
-
|
|
871
|
+
**3. Consumer Group Design**
|
|
872
|
+
- One clear purpose per consumer group
|
|
873
|
+
- Design groups to be independent
|
|
874
|
+
- Ensure operations are idempotent
|
|
539
875
|
|
|
540
|
-
|
|
541
|
-
-
|
|
542
|
-
-
|
|
876
|
+
**4. Error Handling**
|
|
877
|
+
- Always wrap processing in try-catch
|
|
878
|
+
- Provide meaningful error messages in ack
|
|
879
|
+
- Use retry limits appropriately
|
|
880
|
+
- Monitor dead letter queue
|
|
543
881
|
|
|
544
|
-
|
|
882
|
+
---
|
|
545
883
|
|
|
546
|
-
|
|
547
|
-
- **Handle Timeouts**: Implement proper timeout handling and retries
|
|
548
|
-
- **Release Early**: Acknowledge messages as soon as processing completes
|
|
884
|
+
## 🔌 HTTP API Reference
|
|
549
885
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
```javascript
|
|
553
|
-
try {
|
|
554
|
-
const messages = await client.pop({ queue: 'tasks' });
|
|
555
|
-
|
|
556
|
-
for (const message of messages.messages) {
|
|
557
|
-
try {
|
|
558
|
-
await processMessage(message);
|
|
559
|
-
await client.ack(message.transactionId, 'completed');
|
|
560
|
-
} catch (error) {
|
|
561
|
-
// Log error but don't ack - message will retry
|
|
562
|
-
console.error('Processing failed:', error);
|
|
563
|
-
await client.ack(message.transactionId, 'failed', error.message);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
} catch (error) {
|
|
567
|
-
console.error('Pop failed:', error);
|
|
568
|
-
}
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
## 🔌 API Reference
|
|
572
|
-
|
|
573
|
-
### Base URL
|
|
574
|
-
```
|
|
575
|
-
http://localhost:6632/api/v1
|
|
576
|
-
```
|
|
886
|
+
Base URL: `http://localhost:6632/api/v1`
|
|
577
887
|
|
|
578
888
|
### Push Messages
|
|
579
889
|
|
|
580
890
|
**Endpoint:** `POST /api/v1/push`
|
|
581
891
|
|
|
582
|
-
|
|
892
|
+
**Request:**
|
|
893
|
+
```json
|
|
583
894
|
{
|
|
584
895
|
"items": [
|
|
585
896
|
{
|
|
586
|
-
"queue": "
|
|
587
|
-
"partition": "urgent",
|
|
588
|
-
"payload": {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
},
|
|
592
|
-
"transactionId": "uuid-here" // Optional: for idempotency
|
|
897
|
+
"queue": "orders",
|
|
898
|
+
"partition": "urgent",
|
|
899
|
+
"payload": { "orderId": 123, "amount": 99.99 },
|
|
900
|
+
"transactionId": "optional-idempotency-key",
|
|
901
|
+
"traceId": "550e8400-e29b-41d4-a716-446655440000"
|
|
593
902
|
}
|
|
594
903
|
]
|
|
595
904
|
}
|
|
596
905
|
```
|
|
597
906
|
|
|
598
907
|
**Response:**
|
|
599
|
-
```
|
|
908
|
+
```json
|
|
600
909
|
{
|
|
601
910
|
"messages": [
|
|
602
911
|
{
|
|
@@ -610,35 +919,40 @@ http://localhost:6632/api/v1
|
|
|
610
919
|
|
|
611
920
|
### Pop Messages
|
|
612
921
|
|
|
613
|
-
**From
|
|
922
|
+
**From specific partition:**
|
|
614
923
|
```
|
|
615
924
|
GET /api/v1/pop/queue/{queue}/partition/{partition}?wait=true&timeout=30000&batch=10
|
|
616
925
|
```
|
|
617
926
|
|
|
618
|
-
**From
|
|
927
|
+
**From any partition in queue:**
|
|
619
928
|
```
|
|
620
929
|
GET /api/v1/pop/queue/{queue}?wait=true&timeout=30000&batch=10
|
|
621
930
|
```
|
|
622
931
|
|
|
623
|
-
**With
|
|
932
|
+
**With namespace/task filter:**
|
|
933
|
+
```
|
|
934
|
+
GET /api/v1/pop?namespace=ecommerce&task=checkout&wait=true&timeout=30000&batch=10
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
**With consumer group (bus mode):**
|
|
624
938
|
```
|
|
625
|
-
GET /api/v1/pop?
|
|
939
|
+
GET /api/v1/pop/queue/{queue}?consumerGroup=analytics&subscriptionMode=all&wait=true&timeout=30000
|
|
626
940
|
```
|
|
627
941
|
|
|
628
942
|
**Response:**
|
|
629
|
-
```
|
|
943
|
+
```json
|
|
630
944
|
{
|
|
631
945
|
"messages": [
|
|
632
946
|
{
|
|
633
947
|
"id": "018e63b7-6165-453f-88ae-56effa177605",
|
|
634
948
|
"transactionId": "4dfb0478-655b-4c91-bcd9-b7acacf0400f",
|
|
635
|
-
"queue": "
|
|
949
|
+
"queue": "orders",
|
|
636
950
|
"partition": "urgent",
|
|
637
|
-
"data": { "
|
|
951
|
+
"data": { "orderId": 123, "amount": 99.99 },
|
|
638
952
|
"retryCount": 0,
|
|
639
953
|
"priority": 10,
|
|
640
|
-
"createdAt": "
|
|
641
|
-
"options": { "leaseTime": 300 }
|
|
954
|
+
"createdAt": "2024-10-08T12:00:00.000Z",
|
|
955
|
+
"options": { "leaseTime": 300, "retryLimit": 3 }
|
|
642
956
|
}
|
|
643
957
|
]
|
|
644
958
|
}
|
|
@@ -646,850 +960,967 @@ GET /api/v1/pop?namespace=my-app&task=emails&wait=true&timeout=30000&batch=10
|
|
|
646
960
|
|
|
647
961
|
### Acknowledge Messages
|
|
648
962
|
|
|
649
|
-
**Single
|
|
650
|
-
```
|
|
963
|
+
**Single:**
|
|
964
|
+
```json
|
|
651
965
|
POST /api/v1/ack
|
|
652
966
|
{
|
|
653
|
-
"transactionId": "
|
|
654
|
-
"status": "completed",
|
|
655
|
-
"
|
|
967
|
+
"transactionId": "4dfb0478-655b-4c91-bcd9-b7acacf0400f",
|
|
968
|
+
"status": "completed",
|
|
969
|
+
"consumerGroup": "analytics",
|
|
970
|
+
"error": null
|
|
656
971
|
}
|
|
657
972
|
```
|
|
658
973
|
|
|
659
|
-
**Batch
|
|
660
|
-
```
|
|
974
|
+
**Batch:**
|
|
975
|
+
```json
|
|
661
976
|
POST /api/v1/ack/batch
|
|
662
977
|
{
|
|
663
978
|
"acknowledgments": [
|
|
664
|
-
{ "transactionId": "
|
|
665
|
-
{ "transactionId": "
|
|
979
|
+
{ "transactionId": "uuid-1", "status": "completed" },
|
|
980
|
+
{ "transactionId": "uuid-2", "status": "failed", "error": "Processing error" }
|
|
666
981
|
]
|
|
667
982
|
}
|
|
668
983
|
```
|
|
669
984
|
|
|
670
|
-
### Queue
|
|
985
|
+
### Configure Queue
|
|
671
986
|
|
|
672
|
-
```
|
|
987
|
+
```json
|
|
673
988
|
POST /api/v1/configure
|
|
674
989
|
{
|
|
675
|
-
"queue": "
|
|
676
|
-
"
|
|
990
|
+
"queue": "orders",
|
|
991
|
+
"namespace": "ecommerce",
|
|
992
|
+
"task": "checkout",
|
|
677
993
|
"options": {
|
|
678
|
-
"leaseTime": 600,
|
|
679
|
-
"retryLimit": 5,
|
|
680
|
-
"priority": 10,
|
|
681
|
-
"
|
|
682
|
-
"
|
|
994
|
+
"leaseTime": 600,
|
|
995
|
+
"retryLimit": 5,
|
|
996
|
+
"priority": 10,
|
|
997
|
+
"maxSize": 10000,
|
|
998
|
+
"ttl": 3600,
|
|
999
|
+
"dlqAfterMaxRetries": true,
|
|
1000
|
+
"delayedProcessing": 0,
|
|
1001
|
+
"windowBuffer": 0,
|
|
1002
|
+
"retentionSeconds": 0,
|
|
1003
|
+
"completedRetentionSeconds": 0,
|
|
1004
|
+
"retentionEnabled": false,
|
|
1005
|
+
"encryptionEnabled": false,
|
|
1006
|
+
"maxWaitTimeSeconds": 0
|
|
683
1007
|
}
|
|
684
1008
|
}
|
|
685
1009
|
```
|
|
686
1010
|
|
|
687
1011
|
### Analytics
|
|
688
1012
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
GET /api/v1/analytics/
|
|
1013
|
+
**Queue statistics:**
|
|
1014
|
+
```
|
|
1015
|
+
GET /api/v1/analytics/queue/{queue}
|
|
1016
|
+
```
|
|
692
1017
|
|
|
693
|
-
|
|
694
|
-
|
|
1018
|
+
**All queues overview:**
|
|
1019
|
+
```
|
|
1020
|
+
GET /api/v1/analytics/queues
|
|
1021
|
+
```
|
|
695
1022
|
|
|
696
|
-
|
|
697
|
-
|
|
1023
|
+
**Queue depths:**
|
|
1024
|
+
```
|
|
1025
|
+
GET /api/v1/analytics/queue-depths
|
|
1026
|
+
```
|
|
698
1027
|
|
|
699
|
-
|
|
1028
|
+
**Throughput metrics:**
|
|
1029
|
+
```
|
|
700
1030
|
GET /api/v1/analytics/throughput
|
|
1031
|
+
```
|
|
701
1032
|
|
|
702
|
-
|
|
703
|
-
|
|
1033
|
+
**Queue lag analysis:**
|
|
1034
|
+
```
|
|
1035
|
+
GET /api/v1/analytics/queue-lag?queue=orders
|
|
704
1036
|
```
|
|
705
1037
|
|
|
706
|
-
|
|
1038
|
+
### Message Management
|
|
707
1039
|
|
|
708
|
-
|
|
1040
|
+
**List messages:**
|
|
1041
|
+
```
|
|
1042
|
+
GET /api/v1/messages?queue=orders&status=pending&limit=100
|
|
1043
|
+
```
|
|
709
1044
|
|
|
710
|
-
|
|
711
|
-
|
|
1045
|
+
**Get single message:**
|
|
1046
|
+
```
|
|
1047
|
+
GET /api/v1/messages/{transactionId}
|
|
1048
|
+
```
|
|
712
1049
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
retryAttempts: 3,
|
|
717
|
-
retryDelay: 1000
|
|
718
|
-
});
|
|
1050
|
+
**Delete message:**
|
|
1051
|
+
```
|
|
1052
|
+
DELETE /api/v1/messages/{transactionId}
|
|
719
1053
|
```
|
|
720
1054
|
|
|
721
|
-
|
|
1055
|
+
**Retry failed message:**
|
|
1056
|
+
```
|
|
1057
|
+
POST /api/v1/messages/{transactionId}/retry
|
|
1058
|
+
```
|
|
722
1059
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
options: {
|
|
728
|
-
priority: 10,
|
|
729
|
-
leaseTime: 600,
|
|
730
|
-
retryLimit: 3
|
|
731
|
-
}
|
|
732
|
-
});
|
|
1060
|
+
**Move to dead letter queue:**
|
|
1061
|
+
```
|
|
1062
|
+
POST /api/v1/messages/{transactionId}/dlq
|
|
1063
|
+
```
|
|
733
1064
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
partition: 'high-priority',
|
|
739
|
-
payload: { orderId: 123, amount: 99.99 }
|
|
740
|
-
}]
|
|
741
|
-
});
|
|
1065
|
+
**Clear queue:**
|
|
1066
|
+
```
|
|
1067
|
+
DELETE /api/v1/queues/{queue}/clear
|
|
1068
|
+
```
|
|
742
1069
|
|
|
743
|
-
|
|
744
|
-
await client.push({
|
|
745
|
-
items: [
|
|
746
|
-
{ queue: 'orders', payload: { orderId: 124 } },
|
|
747
|
-
{ queue: 'orders', payload: { orderId: 125 } },
|
|
748
|
-
{ queue: 'orders', payload: { orderId: 126 } }
|
|
749
|
-
]
|
|
750
|
-
});
|
|
1070
|
+
### System Health
|
|
751
1071
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
timeout: 30000,
|
|
757
|
-
batch: 10
|
|
758
|
-
});
|
|
1072
|
+
**Health check:**
|
|
1073
|
+
```
|
|
1074
|
+
GET /health
|
|
1075
|
+
```
|
|
759
1076
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
await processOrder(message.data);
|
|
764
|
-
await client.ack(message.transactionId, 'completed');
|
|
765
|
-
} catch (error) {
|
|
766
|
-
await client.ack(message.transactionId, 'failed', error.message);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
1077
|
+
**Detailed metrics:**
|
|
1078
|
+
```
|
|
1079
|
+
GET /metrics
|
|
769
1080
|
```
|
|
770
1081
|
|
|
771
|
-
###
|
|
1082
|
+
### WebSocket (Real-time Updates)
|
|
772
1083
|
|
|
773
|
-
|
|
1084
|
+
**Connect:**
|
|
1085
|
+
```javascript
|
|
1086
|
+
const ws = new WebSocket('ws://localhost:6632/ws/dashboard');
|
|
774
1087
|
|
|
775
|
-
|
|
1088
|
+
ws.onmessage = (event) => {
|
|
1089
|
+
const { event: eventType, data } = JSON.parse(event.data);
|
|
1090
|
+
// Handle events: message.pushed, message.completed, queue.depth, etc.
|
|
1091
|
+
};
|
|
1092
|
+
```
|
|
776
1093
|
|
|
777
|
-
|
|
1094
|
+
**Events:**
|
|
1095
|
+
- `message.pushed` - New message added
|
|
1096
|
+
- `message.processing` - Message being processed
|
|
1097
|
+
- `message.completed` - Message completed
|
|
1098
|
+
- `message.failed` - Message failed
|
|
1099
|
+
- `queue.created` - New queue created
|
|
1100
|
+
- `queue.depth` - Queue depth update (every 5s)
|
|
1101
|
+
- `system.stats` - System statistics (every 10s)
|
|
778
1102
|
|
|
779
|
-
|
|
780
|
-
const stopConsumer = client.consume({
|
|
781
|
-
queue: 'orders',
|
|
782
|
-
partition: 'high-priority',
|
|
783
|
-
handler: async (message) => {
|
|
784
|
-
console.log('Processing order:', message.data.orderId);
|
|
785
|
-
await processOrder(message.data);
|
|
786
|
-
// Message is automatically acknowledged on success
|
|
787
|
-
},
|
|
788
|
-
options: {
|
|
789
|
-
batch: 5,
|
|
790
|
-
wait: true,
|
|
791
|
-
timeout: 30000,
|
|
792
|
-
stopOnError: false
|
|
793
|
-
}
|
|
794
|
-
});
|
|
1103
|
+
See [API.md](API.md) for complete API documentation.
|
|
795
1104
|
|
|
796
|
-
|
|
797
|
-
// stopConsumer();
|
|
798
|
-
```
|
|
1105
|
+
---
|
|
799
1106
|
|
|
800
|
-
|
|
1107
|
+
## 📊 Dashboard
|
|
801
1108
|
|
|
802
|
-
|
|
1109
|
+
Queen includes a comprehensive web dashboard for monitoring and management.
|
|
803
1110
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1111
|
+
### Access
|
|
1112
|
+
|
|
1113
|
+
1. Start the server: `npm start`
|
|
1114
|
+
2. Open browser: `http://localhost:6632`
|
|
1115
|
+
3. WebSocket connection provides real-time updates
|
|
1116
|
+
|
|
1117
|
+
### Features
|
|
1118
|
+
|
|
1119
|
+
**System Overview**
|
|
1120
|
+
- Real-time metrics: total messages, processing rate, system health
|
|
1121
|
+
- Queue summary with pending/processing/completed counts
|
|
1122
|
+
- Performance indicators: throughput, latency, error rates
|
|
1123
|
+
|
|
1124
|
+
**Queue Management**
|
|
1125
|
+
- Queue list with status and message counts
|
|
1126
|
+
- Partition view with priority indicators
|
|
1127
|
+
- Message browser with search and filter
|
|
1128
|
+
- Retry and DLQ management
|
|
1129
|
+
|
|
1130
|
+
**Real-time Monitoring**
|
|
1131
|
+
- Live updates via WebSocket
|
|
1132
|
+
- Throughput charts (messages per second over time)
|
|
1133
|
+
- Queue depth graphs with trend analysis
|
|
1134
|
+
- Lag monitoring (processing time and backlog)
|
|
1135
|
+
|
|
1136
|
+
**Analytics Dashboard**
|
|
1137
|
+
- Performance metrics per queue
|
|
1138
|
+
- Historical trends and patterns
|
|
1139
|
+
- System health monitoring
|
|
1140
|
+
- Database connections and memory usage
|
|
1141
|
+
|
|
1142
|
+
**Message Browser**
|
|
1143
|
+
- Search by queue, partition, status, time range
|
|
1144
|
+
- View full payload and metadata
|
|
1145
|
+
- Manually retry failed messages
|
|
1146
|
+
- Dead letter queue management
|
|
1147
|
+
|
|
1148
|
+
### Dashboard Development
|
|
1149
|
+
|
|
1150
|
+
The dashboard is built with Vue.js and located in the `dashboard/` directory:
|
|
1151
|
+
|
|
1152
|
+
```bash
|
|
1153
|
+
cd dashboard
|
|
1154
|
+
npm install
|
|
1155
|
+
npm run dev # Development mode
|
|
1156
|
+
npm run build # Production build
|
|
831
1157
|
```
|
|
832
1158
|
|
|
833
|
-
|
|
834
|
-
- **Higher Throughput**: Process multiple messages simultaneously
|
|
835
|
-
- **Efficient Acknowledgments**: Single batch ACK instead of individual ACKs
|
|
836
|
-
- **Atomic Processing**: Either the entire batch succeeds or fails together
|
|
837
|
-
- **Reduced Network Overhead**: Fewer round trips to the server
|
|
1159
|
+
---
|
|
838
1160
|
|
|
839
|
-
|
|
840
|
-
- Use either `handler` OR `handlerBatch`, not both
|
|
841
|
-
- In batch mode, if processing fails, all messages in the batch are marked as failed
|
|
842
|
-
- Batch size is controlled by the `batch` option (default: 1)
|
|
1161
|
+
## ⚙️ Configuration
|
|
843
1162
|
|
|
844
|
-
|
|
1163
|
+
All configuration uses environment variables with sensible defaults. Configuration is centralized in `src/config.js`.
|
|
845
1164
|
|
|
846
|
-
|
|
847
|
-
// Pop with namespace filter (cross-queue priority)
|
|
848
|
-
const result = await client.pop({
|
|
849
|
-
namespace: 'ecommerce',
|
|
850
|
-
batch: 10,
|
|
851
|
-
wait: true
|
|
852
|
-
});
|
|
1165
|
+
### Server Configuration
|
|
853
1166
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1167
|
+
```bash
|
|
1168
|
+
PORT=6632 # Server port (default: 6632)
|
|
1169
|
+
HOST=0.0.0.0 # Server host (default: 0.0.0.0)
|
|
1170
|
+
WORKER_ID=worker-1 # Worker identifier
|
|
1171
|
+
APP_NAME=queen-mq # Application name
|
|
859
1172
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1173
|
+
# CORS
|
|
1174
|
+
CORS_MAX_AGE=86400
|
|
1175
|
+
CORS_ALLOWED_ORIGINS=*
|
|
1176
|
+
CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,OPTIONS
|
|
1177
|
+
CORS_ALLOWED_HEADERS=Content-Type,Authorization
|
|
1178
|
+
```
|
|
1179
|
+
|
|
1180
|
+
### Database Configuration
|
|
1181
|
+
|
|
1182
|
+
```bash
|
|
1183
|
+
# Connection
|
|
1184
|
+
PG_USER=postgres
|
|
1185
|
+
PG_HOST=localhost
|
|
1186
|
+
PG_DB=postgres
|
|
1187
|
+
PG_PASSWORD=postgres
|
|
1188
|
+
PG_PORT=5432
|
|
866
1189
|
|
|
867
|
-
|
|
868
|
-
|
|
1190
|
+
# Connection pool
|
|
1191
|
+
DB_POOL_SIZE=20 # Max connections
|
|
1192
|
+
DB_IDLE_TIMEOUT=30000 # Idle timeout (ms)
|
|
1193
|
+
DB_CONNECTION_TIMEOUT=2000 # Connection timeout (ms)
|
|
1194
|
+
DB_STATEMENT_TIMEOUT=30000 # Statement timeout (ms)
|
|
1195
|
+
DB_QUERY_TIMEOUT=30000 # Query timeout (ms)
|
|
1196
|
+
DB_MAX_RETRIES=3 # Max retry attempts
|
|
869
1197
|
```
|
|
870
1198
|
|
|
871
|
-
|
|
1199
|
+
### Queue Processing
|
|
872
1200
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
1
|
|
878
|
-
|
|
879
|
-
3. **WebSocket connection**: The dashboard connects via WebSocket for real-time updates
|
|
880
|
-
|
|
881
|
-
### Dashboard Features
|
|
882
|
-
|
|
883
|
-
#### 1. **System Overview**
|
|
884
|
-
- **Real-time Metrics**: Total messages, processing rate, system health
|
|
885
|
-
- **Queue Summary**: Active queues, pending messages, processing status
|
|
886
|
-
- **Performance Indicators**: Throughput, latency, error rates
|
|
887
|
-
|
|
888
|
-
#### 2. **Queue Management**
|
|
889
|
-
- **Queue List**: All queues with current status and message counts
|
|
890
|
-
- **Partition View**: Partitions within each queue with priority indicators
|
|
891
|
-
- **Message Counts**: Pending, processing, completed, failed, and dead letter counts
|
|
892
|
-
- **Priority Visualization**: Color-coded priority levels
|
|
893
|
-
|
|
894
|
-
#### 3. **Real-time Monitoring**
|
|
895
|
-
- **Live Updates**: WebSocket-powered real-time data updates
|
|
896
|
-
- **Throughput Charts**: Messages per second over time
|
|
897
|
-
- **Queue Depth Graphs**: Pending message counts with trend analysis
|
|
898
|
-
- **Lag Monitoring**: Processing time and queue lag metrics
|
|
899
|
-
|
|
900
|
-
#### 4. **Message Browser**
|
|
901
|
-
- **Message Search**: Filter by queue, partition, status, or time range
|
|
902
|
-
- **Message Details**: Full payload, metadata, and processing history
|
|
903
|
-
- **Retry Management**: Manually retry failed messages
|
|
904
|
-
- **Dead Letter Queue**: View and manage messages that exceeded retry limits
|
|
905
|
-
|
|
906
|
-
#### 5. **Analytics Dashboard**
|
|
907
|
-
- **Performance Metrics**: Detailed throughput and latency statistics
|
|
908
|
-
- **Queue Analytics**: Per-queue performance and usage patterns
|
|
909
|
-
- **Historical Data**: Trends and patterns over time
|
|
910
|
-
- **System Health**: Database connections, memory usage, error rates
|
|
911
|
-
|
|
912
|
-
#### 6. **Configuration Management**
|
|
913
|
-
- **Queue Configuration**: View and modify queue settings
|
|
914
|
-
- **Partition Settings**: Priority, lease time, retry limits
|
|
915
|
-
- **System Settings**: Global configuration options
|
|
916
|
-
|
|
917
|
-
### Dashboard Components
|
|
918
|
-
|
|
919
|
-
The dashboard is built with Vue.js and includes:
|
|
920
|
-
|
|
921
|
-
```
|
|
922
|
-
dashboard/
|
|
923
|
-
├── src/
|
|
924
|
-
│ ├── components/
|
|
925
|
-
│ │ ├── charts/ # Chart components
|
|
926
|
-
│ │ │ ├── QueueDepthChart.vue
|
|
927
|
-
│ │ │ ├── QueueLagChart.vue
|
|
928
|
-
│ │ │ └── ThroughputChart.vue
|
|
929
|
-
│ │ ├── cards/ # Metric cards
|
|
930
|
-
│ │ │ └── MetricCard.vue
|
|
931
|
-
│ │ ├── common/ # Shared components
|
|
932
|
-
│ │ │ └── ActivityFeed.vue
|
|
933
|
-
│ │ └── layout/ # Layout components
|
|
934
|
-
│ │ ├── AppHeader.vue
|
|
935
|
-
│ │ ├── AppLayout.vue
|
|
936
|
-
│ │ └── AppSidebar.vue
|
|
937
|
-
│ ├── views/ # Main pages
|
|
938
|
-
│ │ ├── Dashboard.vue # System overview
|
|
939
|
-
│ │ ├── Queues.vue # Queue management
|
|
940
|
-
│ │ ├── QueueDetail.vue # Individual queue details
|
|
941
|
-
│ │ ├── Messages.vue # Message browser
|
|
942
|
-
│ │ └── Analytics.vue # Analytics dashboard
|
|
943
|
-
│ └── services/
|
|
944
|
-
│ ├── api.js # API client
|
|
945
|
-
│ └── websocket.js # WebSocket connection
|
|
946
|
-
```
|
|
947
|
-
|
|
948
|
-
### WebSocket API
|
|
949
|
-
|
|
950
|
-
The dashboard connects via WebSocket for real-time updates:
|
|
1201
|
+
```bash
|
|
1202
|
+
# Pop defaults
|
|
1203
|
+
DEFAULT_TIMEOUT=30000 # Default pop timeout (ms)
|
|
1204
|
+
MAX_TIMEOUT=60000 # Maximum pop timeout (ms)
|
|
1205
|
+
DEFAULT_BATCH_SIZE=1 # Default batch size
|
|
1206
|
+
BATCH_INSERT_SIZE=1000 # Batch size for bulk inserts
|
|
951
1207
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1208
|
+
# Long polling
|
|
1209
|
+
QUEUE_POLL_INTERVAL=100 # Poll interval (ms)
|
|
1210
|
+
QUEUE_POLL_INTERVAL_FILTERED=1000 # Poll interval for filtered pops (ms)
|
|
955
1211
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
updateThroughput(data);
|
|
966
|
-
break;
|
|
967
|
-
case 'system.stats':
|
|
968
|
-
updateSystemStats(data);
|
|
969
|
-
break;
|
|
970
|
-
}
|
|
971
|
-
};
|
|
1212
|
+
# Queue defaults
|
|
1213
|
+
DEFAULT_LEASE_TIME=300 # Lease time (seconds)
|
|
1214
|
+
DEFAULT_RETRY_LIMIT=3 # Retry limit
|
|
1215
|
+
DEFAULT_RETRY_DELAY=1000 # Retry delay (ms)
|
|
1216
|
+
DEFAULT_MAX_SIZE=10000 # Max queue size
|
|
1217
|
+
DEFAULT_TTL=3600 # TTL (seconds)
|
|
1218
|
+
DEFAULT_PRIORITY=0 # Priority
|
|
1219
|
+
DEFAULT_DELAYED_PROCESSING=0 # Delayed processing (seconds)
|
|
1220
|
+
DEFAULT_WINDOW_BUFFER=0 # Window buffer (seconds)
|
|
972
1221
|
```
|
|
973
1222
|
|
|
974
|
-
|
|
1223
|
+
### Background Jobs
|
|
975
1224
|
|
|
976
|
-
|
|
1225
|
+
```bash
|
|
1226
|
+
LEASE_RECLAIM_INTERVAL=5000 # Lease reclamation (ms)
|
|
1227
|
+
RETENTION_INTERVAL=300000 # Retention checks (ms)
|
|
1228
|
+
RETENTION_BATCH_SIZE=1000 # Retention batch size
|
|
1229
|
+
PARTITION_CLEANUP_DAYS=7 # Days before cleaning empty partitions
|
|
1230
|
+
EVICTION_INTERVAL=60000 # Eviction checks (ms)
|
|
1231
|
+
EVICTION_BATCH_SIZE=1000 # Eviction batch size
|
|
1232
|
+
```
|
|
977
1233
|
|
|
978
|
-
|
|
979
|
-
// Configure email queue with priority
|
|
980
|
-
await client.configure({
|
|
981
|
-
queue: 'emails-urgent',
|
|
982
|
-
options: { priority: 10, leaseTime: 300 }
|
|
983
|
-
});
|
|
1234
|
+
### WebSocket
|
|
984
1235
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1236
|
+
```bash
|
|
1237
|
+
WS_COMPRESSION=0 # Compression level
|
|
1238
|
+
WS_MAX_PAYLOAD_LENGTH=16384 # Max payload (bytes)
|
|
1239
|
+
WS_IDLE_TIMEOUT=60 # Idle timeout (seconds)
|
|
1240
|
+
WS_MAX_CONNECTIONS=1000 # Max connections
|
|
1241
|
+
WS_HEARTBEAT_INTERVAL=30000 # Heartbeat (ms)
|
|
1242
|
+
```
|
|
989
1243
|
|
|
990
|
-
|
|
991
|
-
await client.push({
|
|
992
|
-
items: [{
|
|
993
|
-
queue: 'emails',
|
|
994
|
-
partition: 'urgent',
|
|
995
|
-
payload: {
|
|
996
|
-
to: 'admin@company.com',
|
|
997
|
-
subject: 'System Alert',
|
|
998
|
-
body: 'Critical system issue detected'
|
|
999
|
-
}
|
|
1000
|
-
}]
|
|
1001
|
-
});
|
|
1244
|
+
### Encryption
|
|
1002
1245
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
batch: 10,
|
|
1007
|
-
wait: true
|
|
1008
|
-
});
|
|
1246
|
+
```bash
|
|
1247
|
+
# Generate key: openssl rand -hex 32
|
|
1248
|
+
QUEEN_ENCRYPTION_KEY=<64-hex-chars> # AES-256-GCM encryption key
|
|
1009
1249
|
```
|
|
1010
1250
|
|
|
1011
|
-
###
|
|
1251
|
+
### Client SDK
|
|
1012
1252
|
|
|
1013
|
-
```
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
});
|
|
1253
|
+
```bash
|
|
1254
|
+
QUEEN_BASE_URL=http://localhost:6632
|
|
1255
|
+
CLIENT_RETRY_ATTEMPTS=3
|
|
1256
|
+
CLIENT_RETRY_DELAY=1000
|
|
1257
|
+
CLIENT_RETRY_BACKOFF=2
|
|
1258
|
+
CLIENT_POOL_SIZE=10
|
|
1259
|
+
CLIENT_REQUEST_TIMEOUT=30000
|
|
1260
|
+
```
|
|
1022
1261
|
|
|
1023
|
-
|
|
1024
|
-
await client.push({
|
|
1025
|
-
items: [{
|
|
1026
|
-
queue: 'scheduled-jobs',
|
|
1027
|
-
partition: 'daily-reports',
|
|
1028
|
-
payload: {
|
|
1029
|
-
reportType: 'daily-sales',
|
|
1030
|
-
date: '2023-10-08',
|
|
1031
|
-
recipients: ['manager@company.com']
|
|
1032
|
-
}
|
|
1033
|
-
}]
|
|
1034
|
-
});
|
|
1262
|
+
### Queue Options
|
|
1035
1263
|
|
|
1036
|
-
|
|
1264
|
+
```javascript
|
|
1265
|
+
{
|
|
1266
|
+
// Processing
|
|
1267
|
+
leaseTime: 300, // Seconds before lease expires
|
|
1268
|
+
retryLimit: 3, // Max retry attempts
|
|
1269
|
+
priority: 0, // Queue priority (higher = first)
|
|
1270
|
+
delayedProcessing: 0, // Delay in seconds
|
|
1271
|
+
windowBuffer: 0, // Buffer time for batching
|
|
1272
|
+
dlqAfterMaxRetries: true, // Move to DLQ after max retries
|
|
1273
|
+
|
|
1274
|
+
// Encryption (Queue-level)
|
|
1275
|
+
encryptionEnabled: false, // Enable AES-256-GCM encryption
|
|
1276
|
+
|
|
1277
|
+
// Retention (Partition-level)
|
|
1278
|
+
retentionSeconds: 0, // Delete pending messages after X seconds
|
|
1279
|
+
completedRetentionSeconds: 0, // Delete completed/failed after X seconds
|
|
1280
|
+
partitionRetentionSeconds: 0, // Delete empty partitions after X seconds
|
|
1281
|
+
retentionEnabled: false, // Enable retention
|
|
1282
|
+
|
|
1283
|
+
// Eviction (Queue-level)
|
|
1284
|
+
maxWaitTimeSeconds: 0 // Evict messages older than X seconds
|
|
1285
|
+
}
|
|
1037
1286
|
```
|
|
1038
1287
|
|
|
1039
|
-
|
|
1288
|
+
---
|
|
1289
|
+
|
|
1290
|
+
## 📚 Full Examples
|
|
1291
|
+
|
|
1292
|
+
### Example 1: Email Queue with Priority
|
|
1040
1293
|
|
|
1041
1294
|
```javascript
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
windowBuffer: 60, // Wait 60 seconds to batch messages
|
|
1047
|
-
priority: 3
|
|
1048
|
-
}
|
|
1295
|
+
import { Queen } from 'queen-mq';
|
|
1296
|
+
|
|
1297
|
+
const client = new Queen({
|
|
1298
|
+
baseUrls: ['http://localhost:6632']
|
|
1049
1299
|
});
|
|
1050
1300
|
|
|
1051
|
-
//
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
payload: { userId: i, action: 'page_view', timestamp: Date.now() }
|
|
1058
|
-
}]
|
|
1059
|
-
});
|
|
1060
|
-
}
|
|
1301
|
+
// Configure queues with different priorities
|
|
1302
|
+
await client.queue('emails-urgent', {
|
|
1303
|
+
priority: 10,
|
|
1304
|
+
leaseTime: 300,
|
|
1305
|
+
retryLimit: 5
|
|
1306
|
+
});
|
|
1061
1307
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1308
|
+
await client.queue('emails-normal', {
|
|
1309
|
+
priority: 5,
|
|
1310
|
+
leaseTime: 300,
|
|
1311
|
+
retryLimit: 3
|
|
1312
|
+
});
|
|
1065
1313
|
|
|
1066
|
-
|
|
1314
|
+
// Producer: Send emails
|
|
1315
|
+
async function sendEmails() {
|
|
1316
|
+
// Urgent email
|
|
1317
|
+
await client.push('emails-urgent', {
|
|
1318
|
+
to: 'admin@company.com',
|
|
1319
|
+
subject: 'Critical Alert',
|
|
1320
|
+
body: 'System issue detected',
|
|
1321
|
+
timestamp: Date.now()
|
|
1322
|
+
});
|
|
1067
1323
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
];
|
|
1075
|
-
|
|
1076
|
-
for (const queue of queues) {
|
|
1077
|
-
await client.configure({
|
|
1078
|
-
queue: queue.name,
|
|
1079
|
-
options: { priority: queue.priority }
|
|
1324
|
+
// Normal email
|
|
1325
|
+
await client.push('emails-normal', {
|
|
1326
|
+
to: 'user@example.com',
|
|
1327
|
+
subject: 'Welcome',
|
|
1328
|
+
body: 'Thanks for signing up',
|
|
1329
|
+
timestamp: Date.now()
|
|
1080
1330
|
});
|
|
1081
1331
|
}
|
|
1082
1332
|
|
|
1083
|
-
// Consumer
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1333
|
+
// Consumer: Process emails
|
|
1334
|
+
async function processEmails() {
|
|
1335
|
+
// Urgent emails processed first (higher priority)
|
|
1336
|
+
for await (const email of client.take('emails-urgent', {
|
|
1337
|
+
wait: true,
|
|
1338
|
+
timeout: 30000
|
|
1339
|
+
})) {
|
|
1340
|
+
try {
|
|
1341
|
+
console.log('Sending urgent email:', email.data.to);
|
|
1342
|
+
await sendEmail(email.data);
|
|
1343
|
+
await client.ack(email);
|
|
1344
|
+
} catch (error) {
|
|
1345
|
+
console.error('Failed to send email:', error);
|
|
1346
|
+
await client.ack(email, false, { error: error.message });
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// Send batch of emails
|
|
1352
|
+
await sendEmails();
|
|
1353
|
+
|
|
1354
|
+
// Start processing
|
|
1355
|
+
processEmails().catch(console.error);
|
|
1092
1356
|
```
|
|
1093
1357
|
|
|
1094
|
-
###
|
|
1358
|
+
### Example 2: Task Pipeline
|
|
1095
1359
|
|
|
1096
1360
|
```javascript
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
priority: 5,
|
|
1102
|
-
leaseTime: 600, // 10 minutes for batch processing
|
|
1103
|
-
windowBuffer: 30 // Buffer messages for 30 seconds
|
|
1104
|
-
}
|
|
1361
|
+
import { Queen } from 'queen-mq';
|
|
1362
|
+
|
|
1363
|
+
const client = new Queen({
|
|
1364
|
+
baseUrls: ['http://localhost:6632']
|
|
1105
1365
|
});
|
|
1106
1366
|
|
|
1107
|
-
//
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1367
|
+
// Configure pipeline stages
|
|
1368
|
+
await client.queue('stage-1-validate', { priority: 10 });
|
|
1369
|
+
await client.queue('stage-2-process', { priority: 9 });
|
|
1370
|
+
await client.queue('stage-3-finalize', { priority: 8 });
|
|
1371
|
+
|
|
1372
|
+
// Stage 1: Validate
|
|
1373
|
+
async function validateStage() {
|
|
1374
|
+
for await (const msg of client.take('stage-1-validate', { wait: true })) {
|
|
1115
1375
|
try {
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
id: msg.transactionId,
|
|
1119
|
-
...msg.data
|
|
1120
|
-
}));
|
|
1376
|
+
const validated = await validate(msg.data);
|
|
1377
|
+
await client.ack(msg);
|
|
1121
1378
|
|
|
1122
|
-
//
|
|
1123
|
-
await
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1379
|
+
// Pass to next stage
|
|
1380
|
+
await client.push('stage-2-process', validated);
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
await client.ack(msg, false, { error: error.message });
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// Stage 2: Process
|
|
1388
|
+
async function processStage() {
|
|
1389
|
+
for await (const msg of client.take('stage-2-process', { wait: true })) {
|
|
1390
|
+
try {
|
|
1391
|
+
const processed = await process(msg.data);
|
|
1392
|
+
await client.ack(msg);
|
|
1127
1393
|
|
|
1394
|
+
// Pass to next stage
|
|
1395
|
+
await client.push('stage-3-finalize', processed);
|
|
1128
1396
|
} catch (error) {
|
|
1129
|
-
|
|
1130
|
-
throw error; // Will mark all messages as failed
|
|
1397
|
+
await client.ack(msg, false, { error: error.message });
|
|
1131
1398
|
}
|
|
1132
|
-
},
|
|
1133
|
-
options: {
|
|
1134
|
-
batch: 50, // Process up to 50 messages at once
|
|
1135
|
-
wait: true, // Use long polling
|
|
1136
|
-
timeout: 30000,
|
|
1137
|
-
stopOnError: false
|
|
1138
1399
|
}
|
|
1139
|
-
}
|
|
1400
|
+
}
|
|
1140
1401
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
await
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1402
|
+
// Stage 3: Finalize
|
|
1403
|
+
async function finalizeStage() {
|
|
1404
|
+
for await (const msg of client.take('stage-3-finalize', { wait: true })) {
|
|
1405
|
+
try {
|
|
1406
|
+
await finalize(msg.data);
|
|
1407
|
+
await client.ack(msg);
|
|
1408
|
+
console.log('Pipeline complete:', msg.data.id);
|
|
1409
|
+
} catch (error) {
|
|
1410
|
+
await client.ack(msg, false, { error: error.message });
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1150
1413
|
}
|
|
1151
|
-
```
|
|
1152
1414
|
|
|
1153
|
-
|
|
1415
|
+
// Start pipeline
|
|
1416
|
+
Promise.all([
|
|
1417
|
+
validateStage(),
|
|
1418
|
+
processStage(),
|
|
1419
|
+
finalizeStage()
|
|
1420
|
+
]);
|
|
1154
1421
|
|
|
1155
|
-
|
|
1422
|
+
// Add work to pipeline
|
|
1423
|
+
await client.push('stage-1-validate', { id: 1, data: 'raw data' });
|
|
1424
|
+
```
|
|
1156
1425
|
|
|
1157
|
-
|
|
1158
|
-
- **Latency**: < 10ms for immediate pop operations
|
|
1159
|
-
- **Concurrent Connections**: 1,000+ long polling connections
|
|
1160
|
-
- **Database**: Optimized for PostgreSQL with proper indexing
|
|
1426
|
+
### Example 3: Event Streaming (Bus Mode)
|
|
1161
1427
|
|
|
1162
|
-
|
|
1428
|
+
```javascript
|
|
1429
|
+
import { Queen } from 'queen-mq';
|
|
1163
1430
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
- **Optimized Queries**: Carefully crafted SQL with proper indexes
|
|
1168
|
-
- **Event-Driven Architecture**: Minimal polling overhead
|
|
1431
|
+
const client = new Queen({
|
|
1432
|
+
baseUrls: ['http://localhost:6632']
|
|
1433
|
+
});
|
|
1169
1434
|
|
|
1170
|
-
|
|
1435
|
+
// Configure event queue
|
|
1436
|
+
await client.queue('events', {
|
|
1437
|
+
priority: 10,
|
|
1438
|
+
leaseTime: 60
|
|
1439
|
+
});
|
|
1171
1440
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1441
|
+
// Producer: Emit events
|
|
1442
|
+
async function emitEvents() {
|
|
1443
|
+
await client.push('events', {
|
|
1444
|
+
type: 'order.created',
|
|
1445
|
+
orderId: 12345,
|
|
1446
|
+
userId: 789,
|
|
1447
|
+
amount: 99.99,
|
|
1448
|
+
timestamp: Date.now()
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1178
1451
|
|
|
1179
|
-
|
|
1452
|
+
// Consumer 1: Analytics Service
|
|
1453
|
+
async function analyticsService() {
|
|
1454
|
+
for await (const event of client.take('events@analytics', {
|
|
1455
|
+
subscriptionMode: 'all', // Replay all messages
|
|
1456
|
+
wait: true
|
|
1457
|
+
})) {
|
|
1458
|
+
console.log('[Analytics] Processing event:', event.data.type);
|
|
1459
|
+
await updateAnalytics(event.data);
|
|
1460
|
+
await client.ack(event, true, { group: 'analytics' });
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1180
1463
|
|
|
1181
|
-
|
|
1464
|
+
// Consumer 2: Notification Service
|
|
1465
|
+
async function notificationService() {
|
|
1466
|
+
for await (const event of client.take('events@notifications', {
|
|
1467
|
+
subscriptionMode: 'new', // Only new messages
|
|
1468
|
+
wait: true
|
|
1469
|
+
})) {
|
|
1470
|
+
console.log('[Notifications] Processing event:', event.data.type);
|
|
1471
|
+
await sendNotification(event.data);
|
|
1472
|
+
await client.ack(event, true, { group: 'notifications' });
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1182
1475
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1476
|
+
// Consumer 3: Audit Service
|
|
1477
|
+
async function auditService() {
|
|
1478
|
+
for await (const event of client.take('events@audit', {
|
|
1479
|
+
subscriptionMode: 'all', // Log everything
|
|
1480
|
+
wait: true
|
|
1481
|
+
})) {
|
|
1482
|
+
console.log('[Audit] Logging event:', event.data.type);
|
|
1483
|
+
await logToAudit(event.data);
|
|
1484
|
+
await client.ack(event, true, { group: 'audit' });
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1185
1487
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1488
|
+
// Start all services (they all see the same events)
|
|
1489
|
+
Promise.all([
|
|
1490
|
+
analyticsService(),
|
|
1491
|
+
notificationService(),
|
|
1492
|
+
auditService()
|
|
1493
|
+
]);
|
|
1494
|
+
|
|
1495
|
+
// Emit events
|
|
1496
|
+
await emitEvents();
|
|
1190
1497
|
```
|
|
1191
1498
|
|
|
1192
|
-
|
|
1499
|
+
### Example 4: Batch Processing
|
|
1500
|
+
|
|
1193
1501
|
```javascript
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
}
|
|
1502
|
+
import { Queen } from 'queen-mq';
|
|
1503
|
+
|
|
1504
|
+
const client = new Queen({
|
|
1505
|
+
baseUrls: ['http://localhost:6632']
|
|
1199
1506
|
});
|
|
1200
|
-
```
|
|
1201
1507
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1508
|
+
// Configure for batch processing
|
|
1509
|
+
await client.queue('data-processing', {
|
|
1510
|
+
priority: 5,
|
|
1511
|
+
leaseTime: 600, // 10 minutes for batch
|
|
1512
|
+
windowBuffer: 30 // Buffer for 30 seconds
|
|
1513
|
+
});
|
|
1204
1514
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
retentionSeconds: 3600, // Delete pending after 1 hour
|
|
1211
|
-
completedRetentionSeconds: 300, // Delete completed after 5 minutes
|
|
1212
|
-
retentionEnabled: true
|
|
1515
|
+
// Producer: Send data
|
|
1516
|
+
async function sendData() {
|
|
1517
|
+
const records = [];
|
|
1518
|
+
for (let i = 0; i < 1000; i++) {
|
|
1519
|
+
records.push({ id: i, value: Math.random() });
|
|
1213
1520
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1521
|
+
|
|
1522
|
+
// Push in batches
|
|
1523
|
+
await client.push('data-processing/analytics', records);
|
|
1524
|
+
}
|
|
1216
1525
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1526
|
+
// Consumer: Batch processor
|
|
1527
|
+
async function batchProcessor() {
|
|
1528
|
+
const BATCH_SIZE = 100;
|
|
1529
|
+
const batch = [];
|
|
1530
|
+
|
|
1531
|
+
for await (const message of client.take('data-processing/analytics', {
|
|
1532
|
+
batch: BATCH_SIZE,
|
|
1533
|
+
wait: true,
|
|
1534
|
+
timeout: 30000
|
|
1535
|
+
})) {
|
|
1536
|
+
batch.push(message);
|
|
1537
|
+
|
|
1538
|
+
// Process when batch is full
|
|
1539
|
+
if (batch.length >= BATCH_SIZE) {
|
|
1540
|
+
try {
|
|
1541
|
+
console.log(`Processing batch of ${batch.length} records`);
|
|
1542
|
+
|
|
1543
|
+
// Extract data
|
|
1544
|
+
const records = batch.map(m => m.data);
|
|
1545
|
+
|
|
1546
|
+
// Bulk process
|
|
1547
|
+
await bulkInsertToDatabase(records);
|
|
1548
|
+
|
|
1549
|
+
// Acknowledge all
|
|
1550
|
+
for (const msg of batch) {
|
|
1551
|
+
await client.ack(msg);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
console.log(`✓ Batch complete`);
|
|
1555
|
+
batch.length = 0;
|
|
1556
|
+
} catch (error) {
|
|
1557
|
+
console.error('Batch processing failed:', error);
|
|
1558
|
+
|
|
1559
|
+
// Mark all as failed
|
|
1560
|
+
for (const msg of batch) {
|
|
1561
|
+
await client.ack(msg, false);
|
|
1562
|
+
}
|
|
1563
|
+
batch.length = 0;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
// Run
|
|
1570
|
+
await sendData();
|
|
1571
|
+
await batchProcessor();
|
|
1220
1572
|
```
|
|
1221
1573
|
|
|
1222
|
-
###
|
|
1223
|
-
Enforce SLAs by automatically evicting messages that wait too long.
|
|
1574
|
+
### Example 5: Scheduled Jobs
|
|
1224
1575
|
|
|
1225
|
-
**Configuration:**
|
|
1226
1576
|
```javascript
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
}
|
|
1577
|
+
import { Queen } from 'queen-mq';
|
|
1578
|
+
|
|
1579
|
+
const client = new Queen({
|
|
1580
|
+
baseUrls: ['http://localhost:6632']
|
|
1232
1581
|
});
|
|
1233
|
-
```
|
|
1234
1582
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1583
|
+
// Configure with delayed processing
|
|
1584
|
+
await client.queue('scheduled-jobs', {
|
|
1585
|
+
delayedProcessing: 3600, // 1 hour delay
|
|
1586
|
+
priority: 5
|
|
1587
|
+
});
|
|
1588
|
+
|
|
1589
|
+
// Schedule a job
|
|
1590
|
+
async function scheduleReport() {
|
|
1591
|
+
await client.push('scheduled-jobs/daily-reports', {
|
|
1592
|
+
reportType: 'daily-sales',
|
|
1593
|
+
date: new Date().toISOString().split('T')[0],
|
|
1594
|
+
recipients: ['manager@company.com'],
|
|
1595
|
+
scheduledAt: Date.now()
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
console.log('Report scheduled for processing in 1 hour');
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
// Process scheduled jobs
|
|
1602
|
+
async function processScheduledJobs() {
|
|
1603
|
+
for await (const job of client.take('scheduled-jobs/daily-reports', {
|
|
1604
|
+
wait: true
|
|
1605
|
+
})) {
|
|
1606
|
+
try {
|
|
1607
|
+
console.log('Generating report:', job.data.reportType);
|
|
1608
|
+
await generateReport(job.data);
|
|
1609
|
+
await client.ack(job);
|
|
1610
|
+
} catch (error) {
|
|
1611
|
+
await client.ack(job, false, { error: error.message });
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
await scheduleReport();
|
|
1617
|
+
processScheduledJobs().catch(console.error);
|
|
1238
1618
|
```
|
|
1239
1619
|
|
|
1240
|
-
###
|
|
1620
|
+
### Example 6: Rate Limiting
|
|
1621
|
+
|
|
1241
1622
|
```javascript
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1623
|
+
import { Queen } from 'queen-mq';
|
|
1624
|
+
|
|
1625
|
+
const client = new Queen({
|
|
1626
|
+
baseUrls: ['http://localhost:6632']
|
|
1627
|
+
});
|
|
1628
|
+
|
|
1629
|
+
await client.queue('api-calls', {
|
|
1630
|
+
priority: 5,
|
|
1631
|
+
leaseTime: 60
|
|
1632
|
+
});
|
|
1633
|
+
|
|
1634
|
+
// Producer: Queue API calls
|
|
1635
|
+
async function queueApiCalls(calls) {
|
|
1636
|
+
await client.push('api-calls', calls);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
// Consumer: Rate-limited processor (10 calls per second max)
|
|
1640
|
+
async function rateLimitedProcessor() {
|
|
1641
|
+
const RATE_LIMIT = 10; // calls per second
|
|
1642
|
+
const INTERVAL = 1000; // 1 second
|
|
1643
|
+
|
|
1644
|
+
let callsThisInterval = 0;
|
|
1645
|
+
let intervalStart = Date.now();
|
|
1646
|
+
|
|
1647
|
+
for await (const call of client.take('api-calls', { wait: true })) {
|
|
1648
|
+
// Check if we need to wait
|
|
1649
|
+
if (callsThisInterval >= RATE_LIMIT) {
|
|
1650
|
+
const elapsed = Date.now() - intervalStart;
|
|
1651
|
+
if (elapsed < INTERVAL) {
|
|
1652
|
+
await new Promise(r => setTimeout(r, INTERVAL - elapsed));
|
|
1653
|
+
}
|
|
1654
|
+
callsThisInterval = 0;
|
|
1655
|
+
intervalStart = Date.now();
|
|
1656
|
+
}
|
|
1255
1657
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1658
|
+
try {
|
|
1659
|
+
await makeApiCall(call.data);
|
|
1660
|
+
await client.ack(call);
|
|
1661
|
+
callsThisInterval++;
|
|
1662
|
+
} catch (error) {
|
|
1663
|
+
await client.ack(call, false, { error: error.message });
|
|
1664
|
+
}
|
|
1259
1665
|
}
|
|
1260
|
-
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
// Generate calls
|
|
1669
|
+
const calls = Array.from({ length: 100 }, (_, i) => ({
|
|
1670
|
+
id: i,
|
|
1671
|
+
endpoint: '/api/data',
|
|
1672
|
+
method: 'GET'
|
|
1673
|
+
}));
|
|
1674
|
+
|
|
1675
|
+
await queueApiCalls(calls);
|
|
1676
|
+
rateLimitedProcessor().catch(console.error);
|
|
1261
1677
|
```
|
|
1262
1678
|
|
|
1263
|
-
|
|
1679
|
+
### Example 7: Enterprise Features
|
|
1264
1680
|
|
|
1265
|
-
|
|
1681
|
+
```javascript
|
|
1682
|
+
import { Queen } from 'queen-mq';
|
|
1266
1683
|
|
|
1267
|
-
|
|
1684
|
+
const client = new Queen({
|
|
1685
|
+
baseUrls: ['http://localhost:6632']
|
|
1686
|
+
});
|
|
1268
1687
|
|
|
1269
|
-
|
|
1688
|
+
// Configure with all enterprise features
|
|
1689
|
+
await client.queue('production-queue', {
|
|
1690
|
+
// Encryption
|
|
1691
|
+
encryptionEnabled: true,
|
|
1692
|
+
|
|
1693
|
+
// Retention
|
|
1694
|
+
retentionSeconds: 86400, // Delete pending after 24 hours
|
|
1695
|
+
completedRetentionSeconds: 3600, // Delete completed after 1 hour
|
|
1696
|
+
retentionEnabled: true,
|
|
1697
|
+
|
|
1698
|
+
// Eviction (SLA enforcement)
|
|
1699
|
+
maxWaitTimeSeconds: 600, // Evict messages older than 10 minutes
|
|
1700
|
+
|
|
1701
|
+
// Standard options
|
|
1702
|
+
priority: 10,
|
|
1703
|
+
leaseTime: 300,
|
|
1704
|
+
retryLimit: 3,
|
|
1705
|
+
dlqAfterMaxRetries: true
|
|
1706
|
+
});
|
|
1270
1707
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1708
|
+
// Push sensitive data (will be encrypted)
|
|
1709
|
+
await client.push('production-queue', {
|
|
1710
|
+
userId: 123,
|
|
1711
|
+
creditCard: '4111-1111-1111-1111',
|
|
1712
|
+
amount: 99.99,
|
|
1713
|
+
timestamp: Date.now()
|
|
1714
|
+
});
|
|
1277
1715
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1716
|
+
// Process (data decrypted automatically)
|
|
1717
|
+
for await (const message of client.take('production-queue', { wait: true })) {
|
|
1718
|
+
console.log('Processing encrypted data:', message.data.userId);
|
|
1719
|
+
await processPayment(message.data);
|
|
1720
|
+
await client.ack(message);
|
|
1721
|
+
}
|
|
1283
1722
|
```
|
|
1284
1723
|
|
|
1285
|
-
|
|
1724
|
+
---
|
|
1286
1725
|
|
|
1287
|
-
|
|
1288
|
-
# Connection settings
|
|
1289
|
-
PG_USER=postgres # PostgreSQL user (default: postgres)
|
|
1290
|
-
PG_HOST=localhost # PostgreSQL host (default: localhost)
|
|
1291
|
-
PG_DB=postgres # PostgreSQL database (default: postgres)
|
|
1292
|
-
PG_PASSWORD=postgres # PostgreSQL password (default: postgres)
|
|
1293
|
-
PG_PORT=5432 # PostgreSQL port (default: 5432)
|
|
1726
|
+
## 🧪 Testing
|
|
1294
1727
|
|
|
1295
|
-
|
|
1296
|
-
DB_POOL_SIZE=20 # Max pool size (default: 20)
|
|
1297
|
-
DB_IDLE_TIMEOUT=30000 # Idle connection timeout in ms (default: 30000)
|
|
1298
|
-
DB_CONNECTION_TIMEOUT=2000 # Connection timeout in ms (default: 2000)
|
|
1299
|
-
DB_STATEMENT_TIMEOUT=30000 # Statement timeout in ms (default: 30000)
|
|
1300
|
-
DB_QUERY_TIMEOUT=30000 # Query timeout in ms (default: 30000)
|
|
1301
|
-
DB_MAX_RETRIES=3 # Max retry attempts for queries (default: 3)
|
|
1302
|
-
```
|
|
1728
|
+
Queen includes a comprehensive test suite covering all features.
|
|
1303
1729
|
|
|
1304
|
-
|
|
1730
|
+
### Run Tests
|
|
1305
1731
|
|
|
1306
1732
|
```bash
|
|
1307
|
-
#
|
|
1308
|
-
|
|
1309
|
-
MAX_TIMEOUT=60000 # Maximum pop timeout in ms (default: 60000)
|
|
1310
|
-
DEFAULT_BATCH_SIZE=1 # Default batch size for pop (default: 1)
|
|
1311
|
-
BATCH_INSERT_SIZE=1000 # Batch size for bulk inserts (default: 1000)
|
|
1733
|
+
# Start the server first
|
|
1734
|
+
npm start
|
|
1312
1735
|
|
|
1313
|
-
#
|
|
1314
|
-
|
|
1315
|
-
QUEUE_POLL_INTERVAL_FILTERED=1000 # Poll interval for filtered pops (default: 1000)
|
|
1736
|
+
# Run all tests
|
|
1737
|
+
node src/test/test-new.js
|
|
1316
1738
|
|
|
1317
|
-
#
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
DEFAULT_DELAYED_PROCESSING=0 # Default delayed processing in seconds (default: 0)
|
|
1325
|
-
DEFAULT_WINDOW_BUFFER=0 # Default window buffer in seconds (default: 0)
|
|
1739
|
+
# Run specific test categories
|
|
1740
|
+
node src/test/test-new.js core # Core features
|
|
1741
|
+
node src/test/test-new.js partition # Partition locking
|
|
1742
|
+
node src/test/test-new.js enterprise # Enterprise features
|
|
1743
|
+
node src/test/test-new.js bus # Bus mode
|
|
1744
|
+
node src/test/test-new.js edge # Edge cases
|
|
1745
|
+
node src/test/test-new.js advanced # Advanced patterns
|
|
1326
1746
|
|
|
1327
|
-
#
|
|
1328
|
-
|
|
1329
|
-
|
|
1747
|
+
# Show help
|
|
1748
|
+
node src/test/test-new.js help
|
|
1749
|
+
```
|
|
1330
1750
|
|
|
1331
|
-
|
|
1332
|
-
DEFAULT_RETENTION_SECONDS=0 # Default retention for all messages (default: 0 = disabled)
|
|
1333
|
-
DEFAULT_COMPLETED_RETENTION_SECONDS=0 # Retention for completed messages (default: 0)
|
|
1334
|
-
DEFAULT_RETENTION_ENABLED=false # Enable retention by default (default: false)
|
|
1751
|
+
### Test Coverage
|
|
1335
1752
|
|
|
1336
|
-
|
|
1337
|
-
DEFAULT_MAX_WAIT_TIME_SECONDS=0 # Max wait time before eviction (default: 0 = disabled)
|
|
1338
|
-
```
|
|
1753
|
+
The test suite verifies:
|
|
1339
1754
|
|
|
1340
|
-
|
|
1755
|
+
**Core Features:**
|
|
1756
|
+
- Queue creation and configuration
|
|
1757
|
+
- Single and batch message push
|
|
1758
|
+
- Message take and acknowledgment
|
|
1759
|
+
- Delayed processing
|
|
1760
|
+
- Partition FIFO ordering
|
|
1761
|
+
|
|
1762
|
+
**Partition Locking:**
|
|
1763
|
+
- Lock acquisition and release
|
|
1764
|
+
- Bus mode partition locking
|
|
1765
|
+
- Specific partition locking
|
|
1766
|
+
- Namespace/task filtering with locking
|
|
1767
|
+
|
|
1768
|
+
**Enterprise Features:**
|
|
1769
|
+
- AES-256-GCM encryption
|
|
1770
|
+
- Message retention policies
|
|
1771
|
+
- Message eviction
|
|
1772
|
+
- Combined enterprise features
|
|
1773
|
+
|
|
1774
|
+
**Bus Mode:**
|
|
1775
|
+
- Consumer groups
|
|
1776
|
+
- Subscription modes (all, new, from)
|
|
1777
|
+
- Consumer group isolation
|
|
1778
|
+
- Mixed mode (queue + bus)
|
|
1779
|
+
|
|
1780
|
+
**Edge Cases:**
|
|
1781
|
+
- Empty and null payloads
|
|
1782
|
+
- Very large payloads
|
|
1783
|
+
- Concurrent operations
|
|
1784
|
+
- Lease expiration
|
|
1785
|
+
- SQL injection prevention
|
|
1786
|
+
- XSS prevention
|
|
1787
|
+
|
|
1788
|
+
**Advanced Patterns:**
|
|
1789
|
+
- Multi-stage pipelines
|
|
1790
|
+
- Fan-out/fan-in
|
|
1791
|
+
- Priority scenarios
|
|
1792
|
+
- Dead letter queue
|
|
1793
|
+
- Circuit breaker
|
|
1794
|
+
- Message deduplication
|
|
1795
|
+
- Event sourcing
|
|
1341
1796
|
|
|
1342
|
-
|
|
1343
|
-
# Job intervals
|
|
1344
|
-
LEASE_RECLAIM_INTERVAL=5000 # Lease reclamation interval in ms (default: 5000)
|
|
1345
|
-
RETENTION_INTERVAL=300000 # Retention check interval in ms (default: 300000 = 5 minutes)
|
|
1346
|
-
RETENTION_BATCH_SIZE=1000 # Retention batch size (default: 1000)
|
|
1347
|
-
PARTITION_CLEANUP_DAYS=7 # Days before cleaning empty partitions (default: 7)
|
|
1348
|
-
EVICTION_INTERVAL=60000 # Eviction check interval in ms (default: 60000 = 1 minute)
|
|
1349
|
-
EVICTION_BATCH_SIZE=1000 # Eviction batch size (default: 1000)
|
|
1797
|
+
### Test Results
|
|
1350
1798
|
|
|
1351
|
-
|
|
1352
|
-
QUEUE_DEPTH_UPDATE_INTERVAL=5000 # Queue depth update interval (default: 5000)
|
|
1353
|
-
SYSTEM_STATS_UPDATE_INTERVAL=10000 # System stats update interval (default: 10000)
|
|
1799
|
+
Example output:
|
|
1354
1800
|
```
|
|
1801
|
+
🚀 Starting Queen Message Queue Test Suite
|
|
1802
|
+
Using the new minimalist Queen client interface
|
|
1803
|
+
================================================================================
|
|
1355
1804
|
|
|
1356
|
-
|
|
1805
|
+
📦 CORE FEATURES
|
|
1806
|
+
----------------------------------------
|
|
1807
|
+
✅ Queue Creation Policy
|
|
1808
|
+
✅ Single Message Push
|
|
1809
|
+
✅ Batch Message Push
|
|
1810
|
+
✅ Queue Configuration
|
|
1811
|
+
✅ Take and Acknowledgment
|
|
1812
|
+
✅ Delayed Processing
|
|
1813
|
+
✅ Partition FIFO Ordering
|
|
1357
1814
|
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1815
|
+
🔒 PARTITION LOCKING
|
|
1816
|
+
----------------------------------------
|
|
1817
|
+
✅ Partition Locking
|
|
1818
|
+
✅ Bus Partition Locking
|
|
1819
|
+
✅ Specific Partition Locking
|
|
1820
|
+
✅ Namespace Task Filtering
|
|
1821
|
+
✅ Namespace Task Bus Mode
|
|
1822
|
+
|
|
1823
|
+
📈 Test Summary
|
|
1824
|
+
================================================================================
|
|
1825
|
+
Total: 42 | Passed: 42 | Failed: 0 | Duration: 45.2s
|
|
1365
1826
|
```
|
|
1366
1827
|
|
|
1367
|
-
|
|
1828
|
+
---
|
|
1368
1829
|
|
|
1369
|
-
|
|
1370
|
-
# Encryption settings
|
|
1371
|
-
QUEEN_ENCRYPTION_KEY=<64-hex> # 32-byte key as 64 hex characters
|
|
1372
|
-
# Generate with: openssl rand -hex 32
|
|
1373
|
-
# Required for encryption features
|
|
1374
|
-
```
|
|
1830
|
+
## 🤝 Contributing
|
|
1375
1831
|
|
|
1376
|
-
|
|
1832
|
+
We welcome contributions! Here's how to get started:
|
|
1377
1833
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
CLIENT_REQUEST_TIMEOUT=30000 # Request timeout in ms (default: 30000)
|
|
1386
|
-
```
|
|
1834
|
+
1. **Fork the repository**
|
|
1835
|
+
2. **Create a feature branch**: `git checkout -b feature/amazing-feature`
|
|
1836
|
+
3. **Make your changes**
|
|
1837
|
+
4. **Run the test suite**: `node src/test/test-new.js`
|
|
1838
|
+
5. **Commit your changes**: `git commit -m 'Add amazing feature'`
|
|
1839
|
+
6. **Push to the branch**: `git push origin feature/amazing-feature`
|
|
1840
|
+
7. **Open a Pull Request**
|
|
1387
1841
|
|
|
1388
|
-
|
|
1842
|
+
### Development Setup
|
|
1389
1843
|
|
|
1390
1844
|
```bash
|
|
1391
|
-
#
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
API_DEFAULT_OFFSET=0 # Default offset (default: 0)
|
|
1395
|
-
```
|
|
1845
|
+
# Clone your fork
|
|
1846
|
+
git clone https://github.com/your-username/queen
|
|
1847
|
+
cd queen
|
|
1396
1848
|
|
|
1397
|
-
|
|
1849
|
+
# Install dependencies
|
|
1850
|
+
nvm use 22
|
|
1851
|
+
npm install
|
|
1398
1852
|
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
ANALYTICS_RECENT_HOURS=24 # Hours to consider for recent stats (default: 24)
|
|
1402
|
-
ANALYTICS_MIN_COMPLETED=5 # Min completed messages for stats (default: 5)
|
|
1403
|
-
RECENT_MESSAGE_WINDOW=60 # Recent message window in seconds (default: 60)
|
|
1404
|
-
RELATED_MESSAGE_WINDOW=3600 # Related message window in seconds (default: 3600)
|
|
1405
|
-
MAX_RELATED_MESSAGES=10 # Max related messages to return (default: 10)
|
|
1406
|
-
```
|
|
1853
|
+
# Initialize database
|
|
1854
|
+
node init-db.js
|
|
1407
1855
|
|
|
1408
|
-
|
|
1856
|
+
# Start server
|
|
1857
|
+
npm start
|
|
1409
1858
|
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
ENABLE_REQUEST_COUNTING=true # Enable request counting (default: true)
|
|
1413
|
-
ENABLE_MESSAGE_COUNTING=true # Enable message counting (default: true)
|
|
1414
|
-
METRICS_ENDPOINT_ENABLED=true # Enable /metrics endpoint (default: true)
|
|
1415
|
-
HEALTH_CHECK_ENABLED=true # Enable /health endpoint (default: true)
|
|
1859
|
+
# Run tests
|
|
1860
|
+
node src/test/test-new.js
|
|
1416
1861
|
```
|
|
1417
1862
|
|
|
1418
|
-
|
|
1863
|
+
### Code Style
|
|
1419
1864
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
LOG_FORMAT=json # Log format (default: json)
|
|
1425
|
-
LOG_TIMESTAMP=true # Include timestamps (default: true)
|
|
1426
|
-
```
|
|
1865
|
+
- Use ES6+ features
|
|
1866
|
+
- Follow existing code style
|
|
1867
|
+
- Add comments for complex logic
|
|
1868
|
+
- Write tests for new features
|
|
1427
1869
|
|
|
1428
|
-
|
|
1870
|
+
---
|
|
1429
1871
|
|
|
1430
|
-
|
|
1431
|
-
{
|
|
1432
|
-
// Standard Options
|
|
1433
|
-
"leaseTime": 300, // Seconds before message lease expires
|
|
1434
|
-
"retryLimit": 3, // Maximum retry attempts
|
|
1435
|
-
"priority": 0, // Queue/partition priority (higher = first)
|
|
1436
|
-
"delayedProcessing": 0, // Delay in seconds before message is available
|
|
1437
|
-
"windowBuffer": 0, // Buffer time in seconds for batching
|
|
1438
|
-
"dlqAfterMaxRetries": true, // Move to dead letter queue after max retries
|
|
1439
|
-
|
|
1440
|
-
// Encryption (Queue-level)
|
|
1441
|
-
"encryptionEnabled": false, // Enable AES-256-GCM encryption for this queue
|
|
1442
|
-
|
|
1443
|
-
// Retention (Partition-level)
|
|
1444
|
-
"retentionSeconds": 0, // Delete pending messages after X seconds (0 = disabled)
|
|
1445
|
-
"completedRetentionSeconds": 0, // Delete completed/failed messages after X seconds
|
|
1446
|
-
"partitionRetentionSeconds": 0, // Delete empty partitions after X seconds
|
|
1447
|
-
"retentionEnabled": false, // Enable retention for this partition
|
|
1448
|
-
|
|
1449
|
-
// Eviction (Queue-level)
|
|
1450
|
-
"maxWaitTimeSeconds": 0 // Evict messages older than X seconds (0 = disabled)
|
|
1451
|
-
}
|
|
1452
|
-
```
|
|
1872
|
+
## 📄 License
|
|
1453
1873
|
|
|
1454
|
-
|
|
1874
|
+
Apache License 2.0 - see [LICENSE.md](LICENSE.md) for details.
|
|
1455
1875
|
|
|
1456
|
-
|
|
1876
|
+
---
|
|
1457
1877
|
|
|
1458
|
-
|
|
1459
|
-
# Start the server
|
|
1460
|
-
npm start
|
|
1878
|
+
## 🔗 Links
|
|
1461
1879
|
|
|
1462
|
-
|
|
1463
|
-
|
|
1880
|
+
- **Repository**: [github.com/smartpricing/queen](https://github.com/smartpricing/queen)
|
|
1881
|
+
- **Documentation**: See `docs/` directory
|
|
1882
|
+
- **Issues**: [GitHub Issues](https://github.com/smartpricing/queen/issues)
|
|
1883
|
+
- **API Reference**: [API.md](API.md)
|
|
1464
1884
|
|
|
1465
|
-
|
|
1466
|
-
node src/test/comprehensive-test.js
|
|
1467
|
-
```
|
|
1885
|
+
---
|
|
1468
1886
|
|
|
1469
|
-
|
|
1887
|
+
## 📈 Performance
|
|
1470
1888
|
|
|
1471
|
-
|
|
1472
|
-
-
|
|
1473
|
-
-
|
|
1474
|
-
-
|
|
1475
|
-
-
|
|
1476
|
-
- ✅ Partition priority ordering
|
|
1477
|
-
- ✅ Consumer pattern with automatic acknowledgment
|
|
1478
|
-
- ✅ Message acknowledgment and retry logic
|
|
1479
|
-
- ✅ FIFO ordering within partitions
|
|
1889
|
+
**Benchmarks** (PostgreSQL 16, Node.js 22):
|
|
1890
|
+
- **Throughput**: 10,000+ messages/second
|
|
1891
|
+
- **Latency**: < 10ms for immediate pop operations
|
|
1892
|
+
- **Concurrent Connections**: 1,000+ long polling connections
|
|
1893
|
+
- **Database**: Optimized with proper indexing and connection pooling
|
|
1480
1894
|
|
|
1481
|
-
|
|
1895
|
+
**Optimization Features:**
|
|
1896
|
+
- Connection pooling with configurable size
|
|
1897
|
+
- Resource caching for queue/partition lookups
|
|
1898
|
+
- Batch operations for bulk inserts/updates
|
|
1899
|
+
- Optimized SQL queries with proper indexes
|
|
1900
|
+
- Event-driven architecture for minimal polling overhead
|
|
1901
|
+
- Long polling for real-time message delivery
|
|
1482
1902
|
|
|
1483
|
-
|
|
1484
|
-
2. Create a feature branch
|
|
1485
|
-
3. Make your changes
|
|
1486
|
-
4. Run the test suite
|
|
1487
|
-
5. Submit a pull request
|
|
1903
|
+
---
|
|
1488
1904
|
|
|
1489
|
-
##
|
|
1905
|
+
## 🎯 Roadmap
|
|
1490
1906
|
|
|
1491
|
-
|
|
1907
|
+
- [ ] **Horizontal Scaling**: Better support for multiple server instances
|
|
1908
|
+
- [ ] **Message Scheduling**: Cron-like scheduling for recurring jobs
|
|
1909
|
+
- [ ] **Priority Lanes**: Dynamic priority adjustment based on load
|
|
1910
|
+
- [ ] **Metrics Export**: Prometheus/Grafana integration
|
|
1911
|
+
- [ ] **Admin API**: REST API for queue management
|
|
1912
|
+
- [ ] **Client Libraries**: Python, Go, Java clients
|
|
1913
|
+
- [ ] **Message Tracing**: Distributed tracing integration
|
|
1914
|
+
- [ ] **Queue Templates**: Pre-configured queue patterns
|
|
1915
|
+
- [ ] **GraphQL API**: Alternative to REST API
|
|
1916
|
+
- [ ] **Kubernetes Operator**: Native K8s support
|
|
1492
1917
|
|
|
1493
1918
|
---
|
|
1494
1919
|
|
|
1495
|
-
|
|
1920
|
+
<div align="center">
|
|
1921
|
+
|
|
1922
|
+
**Queen Message Queue System** - Built for performance, reliability, and developer happiness 🚀
|
|
1923
|
+
|
|
1924
|
+
Made with ❤️ by [Smartpricing](https://github.com/smartpricing)
|
|
1925
|
+
|
|
1926
|
+
</div>
|