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