queen-mq 0.6.3 → 0.7.4
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/README.md +500 -327
- package/{client-js/client-v2 → client-v2}/Queen.js +5 -2
- package/{client-js/client-v2 → client-v2}/README.md +53 -2
- package/{client-js/client-v2 → client-v2}/builders/QueueBuilder.js +24 -1
- package/{client-js/client-v2 → client-v2}/consumer/ConsumerManager.js +25 -4
- package/{client-js/client-v2 → client-v2}/http/HttpClient.js +24 -12
- package/client-v2/http/LoadBalancer.js +271 -0
- package/{client-js/client-v2 → client-v2}/utils/defaults.js +4 -2
- package/package.json +6 -8
- package/{client-js/test-v2 → test-v2}/maintenance.js +1 -1
- package/LICENSE.md +0 -202
- package/client-js/benchmark/consumer.js +0 -209
- package/client-js/benchmark/consumer_multi.js +0 -216
- package/client-js/benchmark/producer.js +0 -80
- package/client-js/benchmark/producer_multi.js +0 -115
- package/client-js/client-v2/http/LoadBalancer.js +0 -50
- /package/{client-js/client-v2 → client-v2}/LOGGING.md +0 -0
- /package/{client-js/client-v2 → client-v2}/buffer/BufferManager.js +0 -0
- /package/{client-js/client-v2 → client-v2}/buffer/MessageBuffer.js +0 -0
- /package/{client-js/client-v2 → client-v2}/builders/TransactionBuilder.js +0 -0
- /package/{client-js/client-v2 → client-v2}/index.js +0 -0
- /package/{client-js/client-v2 → client-v2}/stream/StreamBuilder.js +0 -0
- /package/{client-js/client-v2 → client-v2}/stream/StreamConsumer.js +0 -0
- /package/{client-js/client-v2 → client-v2}/stream/Window.js +0 -0
- /package/{client-js/client-v2 → client-v2}/utils/logger.js +0 -0
- /package/{client-js/client-v2 → client-v2}/utils/validation.js +0 -0
- /package/{client-js/test-v2 → test-v2}/AI_TEST_SUMMARY.md +0 -0
- /package/{client-js/test-v2 → test-v2}/GETTING_STARTED.md +0 -0
- /package/{client-js/test-v2 → test-v2}/MAINTENANCE_TEST.md +0 -0
- /package/{client-js/test-v2 → test-v2}/README_SUBSCRIPTION_TESTS.md +0 -0
- /package/{client-js/test-v2 → test-v2}/ai_buffering.js +0 -0
- /package/{client-js/test-v2 → test-v2}/ai_error_handling.js +0 -0
- /package/{client-js/test-v2 → test-v2}/ai_lease_renewal.js +0 -0
- /package/{client-js/test-v2 → test-v2}/ai_mixed_scenarios.js +0 -0
- /package/{client-js/test-v2 → test-v2}/ai_priority.js +0 -0
- /package/{client-js/test-v2 → test-v2}/ai_resources.js +0 -0
- /package/{client-js/test-v2 → test-v2}/ai_ttl_retention.js +0 -0
- /package/{client-js/test-v2 → test-v2}/complete.js +0 -0
- /package/{client-js/test-v2 → test-v2}/consume.js +0 -0
- /package/{client-js/test-v2 → test-v2}/dlq.js +0 -0
- /package/{client-js/test-v2 → test-v2}/load.js +0 -0
- /package/{client-js/test-v2 → test-v2}/pop.js +0 -0
- /package/{client-js/test-v2 → test-v2}/push.js +0 -0
- /package/{client-js/test-v2 → test-v2}/queue.js +0 -0
- /package/{client-js/test-v2 → test-v2}/retention.js +0 -0
- /package/{client-js/test-v2 → test-v2}/run.js +0 -0
- /package/{client-js/test-v2 → test-v2}/subscription.js +0 -0
- /package/{client-js/test-v2 → test-v2}/transaction.js +0 -0
package/README.md
CHANGED
|
@@ -1,459 +1,632 @@
|
|
|
1
|
-
# Queen MQ -
|
|
1
|
+
# Queen MQ - JavaScript Client
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Modern, high-performance message queue client for Node.js**
|
|
6
6
|
|
|
7
|
+
[](https://www.npmjs.com/package/queen-mq)
|
|
7
8
|
[](LICENSE.md)
|
|
8
9
|
[](https://nodejs.org/)
|
|
9
|
-
[](https://en.cppreference.com/w/cpp/17)
|
|
10
10
|
|
|
11
|
-
[Quick Start](#
|
|
12
|
-
|
|
13
|
-
<p align="center">
|
|
14
|
-
<img src="assets/queen-logo.svg" alt="Queen Logo" width="120" />
|
|
15
|
-
</p>
|
|
11
|
+
[Quick Start](#quick-start) • [Complete Guide](client-v2/README.md) • [Examples](#examples) • [API Reference](#api-reference)
|
|
16
12
|
|
|
17
13
|
</div>
|
|
18
14
|
|
|
19
15
|
---
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
## What is Queen MQ?
|
|
18
|
+
|
|
19
|
+
Queen MQ is a PostgreSQL-backed message queue system with a powerful feature set:
|
|
20
|
+
|
|
21
|
+
- **FIFO Partitions** - Unlimited ordered partitions within queues
|
|
22
|
+
- **Consumer Groups** - Kafka-style consumer groups for scalability
|
|
23
|
+
- **Flexible Semantics** - Exactly-once, at-least-once, and at-most-once delivery
|
|
24
|
+
- **Transactions** - Atomic operations across push and ack
|
|
25
|
+
- **High Performance** - 200K+ messages/sec with proper batching
|
|
26
|
+
- **Subscription Modes** - Process from beginning, new messages only, or from timestamp
|
|
27
|
+
- **Dead Letter Queue** - Automatic failure handling and monitoring
|
|
28
|
+
- **Message Tracing** - Debug distributed workflows with trace timelines
|
|
29
|
+
- **Client-Side Buffering** - 10x-100x throughput boost for high-volume pushes
|
|
30
|
+
- **Real-time Streaming** - Windowed aggregation and processing
|
|
31
|
+
|
|
32
|
+
This client provides a fluent, promise-based API for Node.js applications.
|
|
22
33
|
|
|
23
34
|
---
|
|
24
35
|
|
|
25
|
-
|
|
36
|
+
## Installation
|
|
26
37
|
|
|
27
|
-
|
|
38
|
+
```bash
|
|
39
|
+
npm install queen-mq
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Requirements:** Node.js 22+
|
|
28
43
|
|
|
29
44
|
---
|
|
30
45
|
|
|
31
|
-
##
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
- [Client Guide JS](client-js/client-v2/README.md)
|
|
58
|
-
- [Client Guide C++](client-cpp/README.md)
|
|
59
|
-
- [Server Guide](server/README.md)
|
|
60
|
-
- [Streaming Guide](docs/STREAMING_USAGE.md)
|
|
61
|
-
- [API Reference](server/API.md)
|
|
62
|
-
- [Message Retention & Cleanup](docs/RETENTION.md)
|
|
63
|
-
- [Webapp](webapp/README.md)
|
|
64
|
-
- [Expose the Webapp behind a proxy](proxy/README.md)
|
|
65
|
-
|
|
66
|
-
## Concepts
|
|
67
|
-
|
|
68
|
-
Although the system is designed to be simple to use (not really), there are some concepts that are important to understand to use the system effectively.
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
import { Queen } from 'queen-mq'
|
|
50
|
+
|
|
51
|
+
// Connect to Queen server
|
|
52
|
+
const queen = new Queen('http://localhost:6632')
|
|
53
|
+
|
|
54
|
+
// Create a queue
|
|
55
|
+
await queen.queue('tasks').create()
|
|
56
|
+
|
|
57
|
+
// Push messages
|
|
58
|
+
await queen.queue('tasks').push([
|
|
59
|
+
{ data: { task: 'send-email', to: 'alice@example.com' } }
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
// Consume messages
|
|
63
|
+
await queen.queue('tasks').consume(async (message) => {
|
|
64
|
+
console.log('Processing:', message.data)
|
|
65
|
+
// Auto-ack on success, auto-retry on error
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Core Concepts
|
|
69
72
|
|
|
70
73
|
### Queues
|
|
71
74
|
|
|
72
|
-
|
|
75
|
+
Logical containers for messages with configurable settings:
|
|
76
|
+
- **Lease time** - How long a consumer has to process a message
|
|
77
|
+
- **Retry limit** - Number of retry attempts before DLQ
|
|
78
|
+
- **Priority** - Queue priority for multi-queue consumers
|
|
79
|
+
- **Encryption** - Message payload encryption at rest
|
|
80
|
+
- **Retention** - Automatic cleanup policies
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
await queen.queue('orders')
|
|
84
|
+
.config({
|
|
85
|
+
leaseTime: 300, // 5 minutes
|
|
86
|
+
retryLimit: 3,
|
|
87
|
+
priority: 5,
|
|
88
|
+
encryptionEnabled: false
|
|
89
|
+
})
|
|
90
|
+
.create()
|
|
91
|
+
```
|
|
73
92
|
|
|
74
93
|
### Partitions
|
|
75
94
|
|
|
76
|
-
|
|
95
|
+
Ordered lanes within a queue. Messages in the same partition are processed sequentially:
|
|
77
96
|
|
|
78
|
-
|
|
97
|
+
```javascript
|
|
98
|
+
// All messages for user-123 are processed in order
|
|
99
|
+
await queen.queue('user-events')
|
|
100
|
+
.partition('user-123')
|
|
101
|
+
.push([
|
|
102
|
+
{ data: { event: 'login' } },
|
|
103
|
+
{ data: { event: 'view-page' } },
|
|
104
|
+
{ data: { event: 'logout' } }
|
|
105
|
+
])
|
|
106
|
+
```
|
|
79
107
|
|
|
80
|
-
|
|
108
|
+
**Use cases:**
|
|
109
|
+
- Per-user ordering
|
|
110
|
+
- Per-tenant isolation
|
|
111
|
+
- Sharding for parallelism
|
|
81
112
|
|
|
82
|
-
### Consumer
|
|
113
|
+
### Consumer Groups
|
|
83
114
|
|
|
84
|
-
|
|
115
|
+
Multiple consumers sharing work, with independent progress tracking:
|
|
85
116
|
|
|
86
|
-
|
|
117
|
+
```javascript
|
|
118
|
+
// Worker 1 & 2 share the load
|
|
119
|
+
await queen.queue('emails')
|
|
120
|
+
.group('processors')
|
|
121
|
+
.consume(async (message) => {
|
|
122
|
+
await sendEmail(message.data)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// Separate group processes same messages independently
|
|
126
|
+
await queen.queue('emails')
|
|
127
|
+
.group('analytics')
|
|
128
|
+
.consume(async (message) => {
|
|
129
|
+
await logMetrics(message.data)
|
|
130
|
+
})
|
|
131
|
+
```
|
|
87
132
|
|
|
88
|
-
|
|
133
|
+
### Subscription Modes
|
|
89
134
|
|
|
90
|
-
|
|
135
|
+
Control whether consumer groups process historical messages:
|
|
91
136
|
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
|
|
137
|
+
```javascript
|
|
138
|
+
// Default: Process ALL messages (including backlog)
|
|
139
|
+
await queen.queue('events')
|
|
140
|
+
.group('batch-analytics')
|
|
141
|
+
.consume(async (message) => { /* all messages */ })
|
|
142
|
+
|
|
143
|
+
// Skip history, only new messages
|
|
144
|
+
await queen.queue('events')
|
|
145
|
+
.group('realtime-monitor')
|
|
146
|
+
.subscriptionMode('new')
|
|
147
|
+
.consume(async (message) => { /* new only */ })
|
|
148
|
+
|
|
149
|
+
// Start from specific timestamp
|
|
150
|
+
await queen.queue('events')
|
|
151
|
+
.group('replay')
|
|
152
|
+
.subscriptionFrom('2025-10-28T10:00:00.000Z')
|
|
153
|
+
.consume(async (message) => { /* from timestamp */ })
|
|
95
154
|
```
|
|
96
155
|
|
|
97
|
-
|
|
156
|
+
---
|
|
98
157
|
|
|
99
|
-
|
|
158
|
+
## Connection Options
|
|
100
159
|
|
|
101
|
-
|
|
160
|
+
### Single Server
|
|
102
161
|
|
|
103
|
-
|
|
162
|
+
```javascript
|
|
163
|
+
const queen = new Queen('http://localhost:6632')
|
|
164
|
+
```
|
|
104
165
|
|
|
105
|
-
|
|
166
|
+
### Multiple Servers (High Availability)
|
|
106
167
|
|
|
107
|
-
|
|
168
|
+
```javascript
|
|
169
|
+
const queen = new Queen([
|
|
170
|
+
'http://server1:6632',
|
|
171
|
+
'http://server2:6632'
|
|
172
|
+
])
|
|
173
|
+
```
|
|
108
174
|
|
|
109
|
-
|
|
175
|
+
### Full Configuration
|
|
110
176
|
|
|
111
|
-
|
|
177
|
+
```javascript
|
|
178
|
+
const queen = new Queen({
|
|
179
|
+
urls: ['http://server1:6632', 'http://server2:6632'],
|
|
180
|
+
timeoutMillis: 30000,
|
|
181
|
+
retryAttempts: 3,
|
|
182
|
+
loadBalancingStrategy: 'round-robin', // or 'session'
|
|
183
|
+
enableFailover: true
|
|
184
|
+
})
|
|
185
|
+
```
|
|
112
186
|
|
|
113
|
-
|
|
187
|
+
---
|
|
114
188
|
|
|
115
|
-
|
|
189
|
+
## Basic Usage Patterns
|
|
116
190
|
|
|
117
|
-
|
|
191
|
+
### Push Messages
|
|
118
192
|
|
|
119
|
-
|
|
193
|
+
```javascript
|
|
194
|
+
// Simple push
|
|
195
|
+
await queen.queue('tasks').push([
|
|
196
|
+
{ data: { job: 'resize-image', imageId: 123 } }
|
|
197
|
+
])
|
|
120
198
|
|
|
121
|
-
|
|
199
|
+
// With partition
|
|
200
|
+
await queen.queue('tasks')
|
|
201
|
+
.partition('tenant-456')
|
|
202
|
+
.push([{ data: { action: 'process' } }])
|
|
203
|
+
|
|
204
|
+
// With custom transaction ID (for exactly-once)
|
|
205
|
+
await queen.queue('tasks').push([
|
|
206
|
+
{
|
|
207
|
+
transactionId: 'unique-id-123',
|
|
208
|
+
data: { value: 42 }
|
|
209
|
+
}
|
|
210
|
+
])
|
|
211
|
+
```
|
|
122
212
|
|
|
123
|
-
###
|
|
213
|
+
### Consume Messages (Long-Running Workers)
|
|
124
214
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
215
|
+
```javascript
|
|
216
|
+
// Runs forever, processes messages as they arrive
|
|
217
|
+
await queen.queue('tasks')
|
|
218
|
+
.concurrency(10) // 10 parallel workers
|
|
219
|
+
.batch(20) // Fetch 20 at a time
|
|
220
|
+
.consume(async (message) => {
|
|
221
|
+
await processTask(message.data)
|
|
222
|
+
// Auto-ack on success, auto-retry on error
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
// Process with limit and stop
|
|
226
|
+
await queen.queue('tasks')
|
|
227
|
+
.limit(100)
|
|
228
|
+
.consume(async (message) => {
|
|
229
|
+
await processTask(message.data)
|
|
230
|
+
})
|
|
231
|
+
```
|
|
135
232
|
|
|
233
|
+
### Pop Messages (On-Demand Processing)
|
|
136
234
|
|
|
137
|
-
|
|
235
|
+
```javascript
|
|
236
|
+
// Grab messages manually
|
|
237
|
+
const messages = await queen.queue('tasks')
|
|
238
|
+
.batch(10)
|
|
239
|
+
.wait(true) // Long polling
|
|
240
|
+
.pop()
|
|
241
|
+
|
|
242
|
+
// Manual acknowledgment
|
|
243
|
+
for (const message of messages) {
|
|
244
|
+
try {
|
|
245
|
+
await processMessage(message.data)
|
|
246
|
+
await queen.ack(message, true) // Success
|
|
247
|
+
} catch (error) {
|
|
248
|
+
await queen.ack(message, false) // Retry
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
138
252
|
|
|
139
|
-
|
|
253
|
+
### Transactions (Atomic Operations)
|
|
140
254
|
|
|
141
255
|
```javascript
|
|
142
|
-
|
|
256
|
+
// Pop from queue A
|
|
257
|
+
const messages = await queen.queue('input').pop()
|
|
143
258
|
|
|
144
|
-
//
|
|
145
|
-
|
|
259
|
+
// Atomically: ack input AND push output
|
|
260
|
+
await queen.transaction()
|
|
261
|
+
.ack(messages[0])
|
|
262
|
+
.queue('output')
|
|
263
|
+
.push([{ data: processedResult }])
|
|
264
|
+
.commit()
|
|
146
265
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
.queue('critical-task')
|
|
150
|
-
.config({
|
|
151
|
-
leaseTime: 10, // 10 seconds to process the messages (seconds)
|
|
152
|
-
})
|
|
153
|
-
.create()
|
|
266
|
+
// If commit fails, nothing happens - message stays in input queue
|
|
267
|
+
```
|
|
154
268
|
|
|
155
|
-
|
|
156
|
-
await queen
|
|
157
|
-
.queue('critical-task')
|
|
158
|
-
.partition('tenant-123')
|
|
159
|
-
.push([
|
|
160
|
-
{
|
|
161
|
-
transactionId: 'my-id', // This is autogenerated, but you can set your own. Unique per queue partition.
|
|
162
|
-
data: { id: 123, description: 'Critical task' } // The message payload
|
|
163
|
-
},
|
|
164
|
-
])
|
|
165
|
-
.onSuccess(async (messages) => { // Not mandatory
|
|
166
|
-
console.log('Messages pushed successfully:', messages)
|
|
167
|
-
})
|
|
168
|
-
.onDuplicate(async (messages) => { // Not mandatory, triggered when a message with the same transactionId is pushed
|
|
169
|
-
console.warn('Duplicate transaction IDs detected')
|
|
170
|
-
})
|
|
171
|
-
.onError(async (messages, error) => { // Without callbacks, push throws an error if some messages are not pushed
|
|
172
|
-
console.error('Error pushing messages:', error)
|
|
173
|
-
})
|
|
269
|
+
### Client-Side Buffering (High Throughput)
|
|
174
270
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
//
|
|
187
|
-
.renewLease(true, 2000)
|
|
188
|
-
// Process each message individually
|
|
189
|
-
.each()
|
|
190
|
-
// Do your work here
|
|
191
|
-
.consume(async (message) => {
|
|
192
|
-
console.log('Processing:', message.data)
|
|
193
|
-
})
|
|
194
|
-
// Ack the messages if you processed them successfully
|
|
195
|
-
.onSuccess(async (message) => {
|
|
196
|
-
await queen
|
|
197
|
-
.transaction()
|
|
198
|
-
.queue('critical-task-next')
|
|
199
|
-
.partition('XXX')
|
|
200
|
-
.push([{ data: { message: 'Final', count: 3 } }])
|
|
201
|
-
.ack(message)
|
|
202
|
-
.commit()
|
|
203
|
-
})
|
|
204
|
-
// Nack the messages if you failed to process them
|
|
205
|
-
.onError(async (message, error) => {
|
|
206
|
-
console.error('Error processing messages:', error)
|
|
207
|
-
await queen.ack(message, false)
|
|
208
|
-
})
|
|
271
|
+
```javascript
|
|
272
|
+
// Buffer messages locally, batch to server
|
|
273
|
+
for (let i = 0; i < 10000; i++) {
|
|
274
|
+
await queen.queue('events')
|
|
275
|
+
.buffer({ messageCount: 500, timeMillis: 1000 })
|
|
276
|
+
.push([{ data: { id: i } }])
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Flush remaining buffered messages
|
|
280
|
+
await queen.flushAllBuffers()
|
|
281
|
+
|
|
282
|
+
// Result: 10x-100x faster than individual pushes
|
|
209
283
|
```
|
|
210
284
|
|
|
211
|
-
|
|
285
|
+
### Dead Letter Queue
|
|
212
286
|
|
|
213
|
-
|
|
287
|
+
```javascript
|
|
288
|
+
// Enable DLQ on queue
|
|
289
|
+
await queen.queue('risky')
|
|
290
|
+
.config({ retryLimit: 3, dlqAfterMaxRetries: true })
|
|
291
|
+
.create()
|
|
292
|
+
|
|
293
|
+
// Query failed messages
|
|
294
|
+
const dlq = await queen.queue('risky')
|
|
295
|
+
.dlq()
|
|
296
|
+
.limit(10)
|
|
297
|
+
.get()
|
|
298
|
+
|
|
299
|
+
console.log(`Found ${dlq.total} failed messages`)
|
|
300
|
+
for (const msg of dlq.messages) {
|
|
301
|
+
console.log('Error:', msg.errorMessage)
|
|
302
|
+
}
|
|
303
|
+
```
|
|
214
304
|
|
|
215
|
-
|
|
305
|
+
### Message Tracing
|
|
216
306
|
|
|
217
|
-
|
|
307
|
+
```javascript
|
|
308
|
+
await queen.queue('orders').consume(async (msg) => {
|
|
309
|
+
const orderId = msg.data.orderId
|
|
310
|
+
|
|
311
|
+
// Record trace with name for cross-service correlation
|
|
312
|
+
await msg.trace({
|
|
313
|
+
traceName: `order-${orderId}`,
|
|
314
|
+
eventType: 'info',
|
|
315
|
+
data: { text: 'Order processing started' }
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
await processOrder(msg.data)
|
|
319
|
+
|
|
320
|
+
await msg.trace({
|
|
321
|
+
traceName: `order-${orderId}`,
|
|
322
|
+
eventType: 'processing',
|
|
323
|
+
data: {
|
|
324
|
+
text: 'Order completed',
|
|
325
|
+
total: msg.data.total
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
})
|
|
218
329
|
|
|
219
|
-
|
|
330
|
+
// View traces in webapp: Traces → Search "order-12345"
|
|
331
|
+
```
|
|
220
332
|
|
|
221
|
-
|
|
333
|
+
---
|
|
222
334
|
|
|
223
|
-
|
|
335
|
+
## Examples
|
|
224
336
|
|
|
225
|
-
|
|
337
|
+
### Complete Pipeline with Consumer Groups
|
|
226
338
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
- 📈 Message throughput visualization
|
|
230
|
-
- 🔍 Queue management and monitoring
|
|
231
|
-
- 👥 Consumer group tracking
|
|
232
|
-
- 💬 Message browser with trace timeline
|
|
233
|
-
- 🔎 **Trace explorer for debugging distributed workflows**
|
|
234
|
-
- 📉 Analytics and insights
|
|
235
|
-
- 🌓 Dark/light theme support
|
|
339
|
+
```javascript
|
|
340
|
+
import { Queen } from 'queen-mq'
|
|
236
341
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
342
|
+
const queen = new Queen('http://localhost:6632')
|
|
343
|
+
|
|
344
|
+
// Stage 1: Ingest with buffering
|
|
345
|
+
async function ingestEvents() {
|
|
346
|
+
for (let i = 0; i < 10000; i++) {
|
|
347
|
+
await queen.queue('raw-events')
|
|
348
|
+
.partition(`user-${i % 100}`)
|
|
349
|
+
.buffer({ messageCount: 500, timeMillis: 1000 })
|
|
350
|
+
.push([{ data: { userId: i % 100, event: 'page_view' } }])
|
|
351
|
+
}
|
|
352
|
+
await queen.flushAllBuffers()
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Stage 2: Process with transactions
|
|
356
|
+
async function processEvents() {
|
|
357
|
+
await queen.queue('raw-events')
|
|
358
|
+
.group('processors')
|
|
359
|
+
.concurrency(5)
|
|
360
|
+
.batch(10)
|
|
361
|
+
.autoAck(false)
|
|
362
|
+
.consume(async (messages) => {
|
|
363
|
+
const results = messages.map(m => process(m.data))
|
|
364
|
+
|
|
365
|
+
// Atomic: ack all inputs, push all outputs
|
|
366
|
+
const txn = queen.transaction()
|
|
367
|
+
for (const msg of messages) txn.ack(msg)
|
|
368
|
+
txn.queue('processed-events').push(results.map(r => ({ data: r })))
|
|
369
|
+
await txn.commit()
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Stage 3: Separate analytics consumer (fan-out)
|
|
374
|
+
async function analytics() {
|
|
375
|
+
await queen.queue('raw-events')
|
|
376
|
+
.group('analytics')
|
|
377
|
+
.subscriptionMode('new') // Skip backlog
|
|
378
|
+
.consume(async (message) => {
|
|
379
|
+
await logMetrics(message.data)
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
await ingestEvents()
|
|
384
|
+
await Promise.all([processEvents(), analytics()])
|
|
242
385
|
```
|
|
243
386
|
|
|
244
|
-
|
|
387
|
+
### Long-Running Tasks with Lease Renewal
|
|
245
388
|
|
|
246
|
-
|
|
389
|
+
```javascript
|
|
390
|
+
await queen.queue('video-processing')
|
|
391
|
+
.renewLease(true, 60000) // Renew every 60 seconds
|
|
392
|
+
.consume(async (message) => {
|
|
393
|
+
// Can take hours - lease keeps renewing automatically
|
|
394
|
+
await processVideo(message.data)
|
|
395
|
+
})
|
|
396
|
+
```
|
|
247
397
|
|
|
248
|
-
|
|
398
|
+
### Error Handling with Callbacks
|
|
249
399
|
|
|
250
|
-
|
|
400
|
+
```javascript
|
|
401
|
+
await queen.queue('tasks')
|
|
402
|
+
.autoAck(false)
|
|
403
|
+
.consume(async (message) => {
|
|
404
|
+
return await riskyOperation(message.data)
|
|
405
|
+
})
|
|
406
|
+
.onSuccess(async (message, result) => {
|
|
407
|
+
console.log('Success:', result)
|
|
408
|
+
await queen.ack(message, true)
|
|
409
|
+
})
|
|
410
|
+
.onError(async (message, error) => {
|
|
411
|
+
console.error('Failed:', error.message)
|
|
412
|
+
|
|
413
|
+
// Custom retry logic
|
|
414
|
+
if (error.message.includes('temporary')) {
|
|
415
|
+
await queen.ack(message, false) // Retry
|
|
416
|
+
} else {
|
|
417
|
+
await queen.ack(message, 'failed', { error: error.message })
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
```
|
|
251
421
|
|
|
252
|
-
|
|
422
|
+
---
|
|
253
423
|
|
|
254
|
-
|
|
255
|
-
- **UWS Acceptor**: Single thread listening on port 6632, distributes connections round-robin to workers
|
|
256
|
-
- **UWS Workers**: Configurable event loop threads (default: 10) handling HTTP routes and WebSocket connections
|
|
424
|
+
## API Reference
|
|
257
425
|
|
|
258
|
-
|
|
259
|
-
- **AsyncDbPool**: Non-blocking PostgreSQL connection pool (142 connections) using libpq async API
|
|
260
|
-
- Socket-based I/O with `select()` for non-blocking operations
|
|
261
|
-
- RAII-based resource management with automatic connection cleanup
|
|
262
|
-
- Connection health monitoring and automatic reset
|
|
263
|
-
- Thread-safe with mutex/condition variable synchronization
|
|
264
|
-
- **AsyncQueueManager**: Event-loop-based queue operations
|
|
265
|
-
- Direct execution in worker threads for PUSH, POP, ACK, and TRANSACTION operations
|
|
266
|
-
- Batch processing with dynamic sizing
|
|
267
|
-
- Encryption support with status checks
|
|
268
|
-
- Automatic failover to file buffer when database unavailable
|
|
426
|
+
### Queue Operations
|
|
269
427
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
- Rate-limited queries to prevent database overload
|
|
275
|
-
- **Background Pool**: 8 connections for metrics, retention, eviction, and stream management
|
|
428
|
+
```javascript
|
|
429
|
+
// Create
|
|
430
|
+
await queen.queue('my-queue').create()
|
|
431
|
+
await queen.queue('my-queue').config({ priority: 5 }).create()
|
|
276
432
|
|
|
277
|
-
|
|
433
|
+
// Delete
|
|
434
|
+
await queen.queue('my-queue').delete()
|
|
278
435
|
|
|
279
|
-
|
|
436
|
+
// Get info
|
|
437
|
+
const info = await queen.getQueueInfo('my-queue')
|
|
280
438
|
```
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
439
|
+
|
|
440
|
+
### Push
|
|
441
|
+
|
|
442
|
+
```javascript
|
|
443
|
+
await queen.queue('q').push([{ data: { value: 1 } }])
|
|
444
|
+
await queen.queue('q').partition('p1').push([{ data: { value: 1 } }])
|
|
445
|
+
await queen.queue('q').buffer({ messageCount: 100, timeMillis: 1000 }).push([...])
|
|
288
446
|
```
|
|
289
447
|
|
|
290
|
-
|
|
448
|
+
### Pop
|
|
449
|
+
|
|
450
|
+
```javascript
|
|
451
|
+
const msgs = await queen.queue('q').pop()
|
|
452
|
+
const msgs = await queen.queue('q').batch(10).pop()
|
|
453
|
+
const msgs = await queen.queue('q').batch(10).wait(true).pop()
|
|
291
454
|
```
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
Messages distributed to waiting clients
|
|
455
|
+
|
|
456
|
+
### Consume
|
|
457
|
+
|
|
458
|
+
```javascript
|
|
459
|
+
await queen.queue('q').consume(async (msg) => { /* process */ })
|
|
460
|
+
await queen.queue('q').limit(10).consume(async (msg) => { /* process */ })
|
|
461
|
+
await queen.queue('q').concurrency(5).consume(async (msg) => { /* 5 workers */ })
|
|
462
|
+
await queen.queue('q').group('my-group').consume(async (msg) => { /* consumer group */ })
|
|
301
463
|
```
|
|
302
464
|
|
|
303
|
-
###
|
|
465
|
+
### Acknowledgment
|
|
304
466
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
467
|
+
```javascript
|
|
468
|
+
await queen.ack(message, true) // Success
|
|
469
|
+
await queen.ack(message, false) // Retry
|
|
470
|
+
await queen.ack(message, false, { error: 'reason' })
|
|
471
|
+
await queen.ack([msg1, msg2], true) // Batch ack
|
|
472
|
+
```
|
|
310
473
|
|
|
311
|
-
|
|
312
|
-
- **Peak**: 148,000+ msg/s
|
|
313
|
-
- **Sustained**: 130,000+ msg/s
|
|
314
|
-
- **Batch push**: 5,000-8,000 msg/s (with batches of 100)
|
|
474
|
+
### Transactions
|
|
315
475
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
476
|
+
```javascript
|
|
477
|
+
await queen.transaction()
|
|
478
|
+
.ack(message)
|
|
479
|
+
.queue('output')
|
|
480
|
+
.push([{ data: { result: 'processed' } }])
|
|
481
|
+
.commit()
|
|
482
|
+
```
|
|
320
483
|
|
|
321
|
-
###
|
|
484
|
+
### Lease Renewal
|
|
322
485
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
- ✅ Automatic load distribution across workers
|
|
486
|
+
```javascript
|
|
487
|
+
await queen.renew(message)
|
|
488
|
+
await queen.renew([msg1, msg2, msg3])
|
|
489
|
+
await queen.queue('q').renewLease(true, 60000).consume(async (msg) => { /* auto-renew */ })
|
|
490
|
+
```
|
|
329
491
|
|
|
330
|
-
|
|
331
|
-
- [Server Architecture Guide](server/README.md) - Complete server setup and configuration
|
|
332
|
-
- [Architecture Diagrams](assets/architecture.svg) - Visual architecture overview
|
|
492
|
+
### Buffering
|
|
333
493
|
|
|
334
|
-
|
|
494
|
+
```javascript
|
|
495
|
+
await queen.flushAllBuffers()
|
|
496
|
+
await queen.queue('q').flushBuffer()
|
|
497
|
+
const stats = queen.getBufferStats()
|
|
498
|
+
```
|
|
335
499
|
|
|
336
|
-
|
|
500
|
+
### Dead Letter Queue
|
|
337
501
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
502
|
+
```javascript
|
|
503
|
+
const dlq = await queen.queue('q').dlq().limit(10).get()
|
|
504
|
+
const dlq = await queen.queue('q').dlq('consumer-group').limit(10).get()
|
|
505
|
+
const dlq = await queen.queue('q').dlq().from('2025-01-01').to('2025-01-31').get()
|
|
506
|
+
```
|
|
343
507
|
|
|
344
|
-
|
|
508
|
+
### Shutdown
|
|
345
509
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
FILE_BUFFER_DIR=/custom/path ./bin/queen-server
|
|
510
|
+
```javascript
|
|
511
|
+
await queen.close() // Flush buffers and close connections
|
|
349
512
|
```
|
|
350
513
|
|
|
351
|
-
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## Configuration Defaults
|
|
352
517
|
|
|
353
|
-
|
|
518
|
+
### Client Defaults
|
|
354
519
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
520
|
+
```javascript
|
|
521
|
+
{
|
|
522
|
+
timeoutMillis: 30000,
|
|
523
|
+
retryAttempts: 3,
|
|
524
|
+
retryDelayMillis: 1000,
|
|
525
|
+
loadBalancingStrategy: 'round-robin',
|
|
526
|
+
enableFailover: true
|
|
527
|
+
}
|
|
528
|
+
```
|
|
360
529
|
|
|
361
|
-
###
|
|
530
|
+
### Queue Defaults
|
|
362
531
|
|
|
363
|
-
|
|
532
|
+
```javascript
|
|
533
|
+
{
|
|
534
|
+
leaseTime: 300, // 5 minutes
|
|
535
|
+
retryLimit: 3,
|
|
536
|
+
priority: 0,
|
|
537
|
+
delayedProcessing: 0,
|
|
538
|
+
windowBuffer: 0,
|
|
539
|
+
maxSize: 0, // Unlimited
|
|
540
|
+
retentionSeconds: 0, // Keep forever
|
|
541
|
+
encryptionEnabled: false
|
|
542
|
+
}
|
|
543
|
+
```
|
|
364
544
|
|
|
365
|
-
|
|
366
|
-
|------|------|----------|------------|------------|-----------|
|
|
367
|
-
| **T1** | Producer | 10,000 | 1 | 785 msg/sec | 0.22 MB/sec |
|
|
368
|
-
| **T1** | Consumer | 10,000 | 1 | 456 msg/sec | 0.23 MB/sec |
|
|
369
|
-
| **T2** | Producer | 10,000 | 10 | 7,677 msg/sec | 2.20 MB/sec |
|
|
370
|
-
| **T2** | Consumer | 10,000 | 10 | 4,989 msg/sec | 2.53 MB/sec |
|
|
371
|
-
| **T3** | Producer | 10,000 | 100 | 39,065 msg/sec | 11.18 MB/sec |
|
|
372
|
-
| **T3** | Consumer | 10,000 | 100 | 30,079 msg/sec | 15.26 MB/sec |
|
|
373
|
-
| **T4** | Producer | 10,000 | 1,000 | 85,862 msg/sec | 24.57 MB/sec |
|
|
374
|
-
| **T4** | Consumer | 10,000 | 1,000 | **488,650 msg/sec** | **247.87 MB/sec** |
|
|
375
|
-
| **T5** | Producer | 100,000 | 1,000 | **90,601 msg/sec** | **25.92 MB/sec** |
|
|
376
|
-
| **T5** | Consumer | 100,000 | 1,000 | 84,530 msg/sec | 42.96 MB/sec |
|
|
545
|
+
### Consume Defaults
|
|
377
546
|
|
|
378
|
-
|
|
547
|
+
```javascript
|
|
548
|
+
{
|
|
549
|
+
concurrency: 1,
|
|
550
|
+
batch: 1,
|
|
551
|
+
autoAck: true,
|
|
552
|
+
wait: true, // Long polling
|
|
553
|
+
timeoutMillis: 30000,
|
|
554
|
+
limit: null, // Run forever
|
|
555
|
+
renewLease: false
|
|
556
|
+
}
|
|
557
|
+
```
|
|
379
558
|
|
|
380
|
-
|
|
381
|
-
- ✅ **Consumer performance:** Peaks at 488K msg/sec with batch size 1,000 (247 MB/sec bandwidth)
|
|
382
|
-
- ✅ **Producer peak:** 90K msg/sec with batch size 1,000 on 100K messages
|
|
383
|
-
- ⚠️ **Small batches:** Performance drops significantly with batch=1 (lock contention)
|
|
384
|
-
- 📈 **Scalability:** Performance improves with larger message volumes (T4 vs T5)
|
|
559
|
+
---
|
|
385
560
|
|
|
386
|
-
|
|
561
|
+
## Logging
|
|
387
562
|
|
|
388
|
-
|
|
563
|
+
Enable detailed logging for debugging:
|
|
389
564
|
|
|
390
565
|
```bash
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
# Producer
|
|
395
|
-
./bin/benchmark producer --threads 10 --count 1000000 --batch 1000 --partitions 100 --mode single-queue
|
|
566
|
+
export QUEEN_CLIENT_LOG=true
|
|
567
|
+
node your-app.js
|
|
568
|
+
```
|
|
396
569
|
|
|
397
|
-
|
|
398
|
-
|
|
570
|
+
Example output:
|
|
571
|
+
```
|
|
572
|
+
[2025-10-28T10:30:45.123Z] [INFO] [Queen.constructor] {"status":"initialized","urls":1}
|
|
573
|
+
[2025-10-28T10:30:45.234Z] [INFO] [QueueBuilder.push] {"queue":"tasks","partition":"Default","count":5}
|
|
399
574
|
```
|
|
400
575
|
|
|
401
|
-
|
|
576
|
+
---
|
|
402
577
|
|
|
403
|
-
##
|
|
578
|
+
## Best Practices
|
|
404
579
|
|
|
405
|
-
|
|
580
|
+
1. ✅ **Use `consume()` for workers** - Simpler API, handles retries automatically
|
|
581
|
+
2. ✅ **Use `pop()` for control** - When you need precise control over acking
|
|
582
|
+
3. ✅ **Buffer for speed** - Always use buffering when pushing many messages
|
|
583
|
+
4. ✅ **Partitions for order** - Use partitions when message order matters
|
|
584
|
+
5. ✅ **Consumer groups for scale** - Run multiple workers in the same group
|
|
585
|
+
6. ✅ **Transactions for consistency** - Use transactions for atomic operations
|
|
586
|
+
7. ✅ **Enable DLQ** - Always enable DLQ in production
|
|
587
|
+
8. ✅ **Renew long leases** - Use auto-renewal for long-running tasks
|
|
588
|
+
9. ✅ **Graceful shutdown** - Always call `queen.close()` before exiting
|
|
589
|
+
10. ✅ **Monitor DLQ** - Regularly check for failed messages
|
|
406
590
|
|
|
407
|
-
|
|
408
|
-
cd server
|
|
409
|
-
make clean
|
|
410
|
-
make deps
|
|
411
|
-
make build-only
|
|
412
|
-
DB_POOL_SIZE=50 ./bin/queen-server
|
|
413
|
-
```
|
|
591
|
+
---
|
|
414
592
|
|
|
415
|
-
|
|
593
|
+
## TypeScript Support
|
|
416
594
|
|
|
417
|
-
|
|
418
|
-
- Build instructions and optimization
|
|
419
|
-
- Performance tuning (worker threads, database pool)
|
|
420
|
-
- Production deployment (systemd, Docker, load balancing)
|
|
421
|
-
- Troubleshooting common issues
|
|
422
|
-
- Benchmarking guides
|
|
595
|
+
Full TypeScript definitions included:
|
|
423
596
|
|
|
424
|
-
|
|
597
|
+
```typescript
|
|
598
|
+
import { Queen, Message, QueueConfig } from 'queen-mq'
|
|
425
599
|
|
|
426
|
-
|
|
600
|
+
const queen: Queen = new Queen('http://localhost:6632')
|
|
427
601
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
602
|
+
interface OrderData {
|
|
603
|
+
orderId: number
|
|
604
|
+
amount: number
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const messages: Message<OrderData>[] = await queen.queue('orders').pop()
|
|
431
608
|
```
|
|
432
609
|
|
|
433
|
-
|
|
610
|
+
---
|
|
434
611
|
|
|
435
|
-
|
|
612
|
+
## Documentation
|
|
436
613
|
|
|
437
|
-
|
|
614
|
+
- **[Complete V2 Guide](client-v2/README.md)** - Full tutorial with all features (94 test examples)
|
|
615
|
+
- **[HTTP API Reference](https://github.com/smartpricing/queen/blob/master/server/API.md)** - Raw HTTP endpoints
|
|
616
|
+
- **[Server Guide](https://github.com/smartpricing/queen/blob/master/server/README.md)** - Server setup and configuration
|
|
617
|
+
- **[Architecture Guide](https://github.com/smartpricing/queen/blob/master/documentation/ARCHITECTURE.md)** - Deep dive into internals
|
|
438
618
|
|
|
439
|
-
|
|
619
|
+
---
|
|
440
620
|
|
|
441
|
-
|
|
621
|
+
## Support
|
|
442
622
|
|
|
443
|
-
|
|
623
|
+
- **GitHub:** [smartpricing/queen](https://github.com/smartpricing/queen)
|
|
624
|
+
- **Issues:** [GitHub Issues](https://github.com/smartpricing/queen/issues)
|
|
625
|
+
- **LinkedIn:** [Smartness](https://www.linkedin.com/company/smartness-com/)
|
|
444
626
|
|
|
445
|
-
|
|
446
|
-
**Issue:** Worker initialization timeout (30s → 3600s) now matches file buffer recovery timeout. This is a temporary fix.
|
|
627
|
+
---
|
|
447
628
|
|
|
448
|
-
|
|
449
|
-
- Make recovery non-blocking while preserving FIFO ordering guarantees
|
|
450
|
-
- Implement progressive readiness with memory-buffered queue during recovery
|
|
451
|
-
- Add configurable recovery timeout with graceful degradation
|
|
452
|
-
- See: `server/src/services/file_buffer.cpp:212` (MAX_STARTUP_RECOVERY_SECONDS)
|
|
453
|
-
- See: `server/src/acceptor_server.cpp:1876` (worker initialization timeout)
|
|
629
|
+
## License
|
|
454
630
|
|
|
631
|
+
Apache 2.0 - See [LICENSE.md](../LICENSE.md)
|
|
455
632
|
|
|
456
|
-
### Other TODO Items
|
|
457
|
-
- Proper concurrency on clients
|
|
458
|
-
- Check client failover
|
|
459
|
-
- Py client
|