queen-mq 0.3.0 → 0.4.0
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 +212 -144
- package/client-js/client-v2/Queen.js +28 -0
- package/client-js/client-v2/README.md +6 -2
- package/client-js/client-v2/builders/QueueBuilder.js +7 -1
- package/client-js/client-v2/builders/TransactionBuilder.js +20 -4
- package/client-js/client-v2/stream/StreamBuilder.js +72 -0
- package/client-js/client-v2/stream/StreamConsumer.js +156 -0
- package/client-js/client-v2/stream/Window.js +140 -0
- package/client-js/test-v2/MAINTENANCE_TEST.md +148 -0
- package/client-js/test-v2/consume.js +1 -0
- package/client-js/test-v2/maintenance.js +261 -0
- package/client-js/test-v2/retention.js +69 -0
- package/client-js/test-v2/run.js +7 -1
- package/package.json +2 -2
- package/client-js/client/client.js +0 -1536
- package/client-js/client/index.js +0 -4
- package/client-js/client/utils/http.js +0 -173
- package/client-js/client/utils/loadBalancer.js +0 -152
- package/client-js/client/utils/retry.js +0 -41
- package/client-js/services/encryptionService.js +0 -82
- package/client-js/services/evictionService.js +0 -160
- package/client-js/services/retentionService.js +0 -162
- package/client-js/services/startupSync.js +0 -35
- package/client-js/test/README.md +0 -224
- package/client-js/test/advanced-client-tests.js +0 -761
- package/client-js/test/advanced-pattern-tests.js +0 -1137
- package/client-js/test/bus-mode-tests.js +0 -361
- package/client-js/test/core-tests.js +0 -457
- package/client-js/test/edge-case-tests.js +0 -562
- package/client-js/test/enterprise-tests.js +0 -637
- package/client-js/test/human.js +0 -162
- package/client-js/test/partition-locking-tests.js +0 -545
- package/client-js/test/partition-transaction-tests.js +0 -482
- package/client-js/test/qos0-tests.js +0 -334
- package/client-js/test/test-new.js +0 -370
- package/client-js/test/utils.js +0 -169
- package/client-js/test/window-buffer-test.js +0 -114
- package/client-js/utils/logger.js +0 -44
- package/client-js/utils/uuid.js +0 -5
package/README.md
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-
**A modern,
|
|
5
|
+
**A modern, performant message queue system built on PostgreSQL**
|
|
6
6
|
|
|
7
7
|
[](LICENSE.md)
|
|
8
8
|
[](https://nodejs.org/)
|
|
9
|
+
[](https://en.cppreference.com/w/cpp/17)
|
|
9
10
|
|
|
10
|
-
[Quick Start](#
|
|
11
|
+
[Quick Start](#one-single-example) • [Complete V2 Guide](client-js/client-v2/README.md) • [Webapp](#webapp) • [Server Setup](#install-the-server-and-configure-it) • [HTTP API](#raw-http-api)
|
|
11
12
|
|
|
12
13
|
<p align="center">
|
|
13
|
-
<img src="assets/queen-logo
|
|
14
|
+
<img src="assets/queen-logo.svg" alt="Queen Logo" width="120" />
|
|
14
15
|
</p>
|
|
15
16
|
|
|
16
17
|
</div>
|
|
@@ -29,6 +30,7 @@ Here are the main features:
|
|
|
29
30
|
- Unlimited FIFO partitions within queues
|
|
30
31
|
- Queue semantics like RabbitMQ
|
|
31
32
|
- Consumer groups over queues like Kafka
|
|
33
|
+
- Allows for all the patterns you need, from simple queues to complex workflows and request/response patterns
|
|
32
34
|
- QoS levels: Exactly-once delivery (with transactionId), at-least-once delivery, and at-most-once delivery
|
|
33
35
|
- Subscription modes for replay (new messages only or from a specific timestamp) and message history control
|
|
34
36
|
- Transactions between operations (push and ack mainly) for atomicity
|
|
@@ -36,19 +38,88 @@ Here are the main features:
|
|
|
36
38
|
- Lease renewal for long-running tasks
|
|
37
39
|
- Message tracing for debugging workflows
|
|
38
40
|
- Encryption of messages at DB level
|
|
41
|
+
- Automatic message retention and cleanup - Configurable per-queue retention policies
|
|
42
|
+
- Streaming capabilities for real-time aggregation and processing of messages
|
|
43
|
+
- A nice webapp for monitoring and managing the system
|
|
44
|
+
- Maintenace mode that allows to continue pushing messages even when the database is down, and to drain the messages to the database when the maintenance mode is disabled
|
|
39
45
|
|
|
40
|
-
The system consists of a PostgreSQL database, a replicated server that can be scaled horizontally (though it won't be the bottleneck), and a client library for interacting with the server. All client-server communication happens over HTTP, and the client library
|
|
46
|
+
The system consists of a PostgreSQL database, a replicated server that can be scaled horizontally (though it won't be the bottleneck), and a client library for interacting with the server. All client-server communication happens over HTTP, and the client library are available for JavaScript and C++ (Other languages like Python are planned). There's also a modern Vue 3 web app for monitoring and managing the system.
|
|
41
47
|
|
|
42
|
-
With proper batching, the system can handle +
|
|
48
|
+
With proper batching, the system can handle +200k **messages** per second (not req/s) on modest hardware.
|
|
43
49
|
|
|
44
50
|
Main documentation:
|
|
45
|
-
- [Client Guide](client-js/client-v2/README.md)
|
|
51
|
+
- [Client Guide JS](client-js/client-v2/README.md)
|
|
52
|
+
- [Client Guide C++](client-cpp/README.md)
|
|
46
53
|
- [Server Guide](server/README.md)
|
|
47
|
-
- [
|
|
54
|
+
- [Streaming Guide](docs/STREAMING_USAGE.md)
|
|
55
|
+
- [API Reference](server/API.md)
|
|
56
|
+
- [Message Retention & Cleanup](docs/RETENTION.md)
|
|
48
57
|
- [Webapp](webapp/README.md)
|
|
58
|
+
- [Expose the Webapp behind a proxy](proxy/README.md)
|
|
49
59
|
|
|
60
|
+
## Concepts
|
|
50
61
|
|
|
51
|
-
|
|
62
|
+
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.
|
|
63
|
+
|
|
64
|
+
### Queues
|
|
65
|
+
|
|
66
|
+
Queues are a way to organize your messages into logical groups. Each queue is like a container for messages. You can have as many queues as you want, and you can configure each queue with different settings, like the lease time, retry limit, encryption, retention, priority, delayed processing, window buffer and retention.
|
|
67
|
+
|
|
68
|
+
### Partitions
|
|
69
|
+
|
|
70
|
+
Partitions are a way to organize your messages into logical ordered groups. Each partition is like a separate queue inside a queue, and messages in the same partition are guaranteed to be processed in order. Only one consumer/client can acquire the lock for a partition at a time. You can have as many partitions as you want, and you can have as many consumers/clients as you want. If the lease in not acknowledged in time, the message is released and can be acquired by another consumer/client. Use renewLease() to renew the lease during long-running tasks. Each different consumer group can acquire the lock for a partition independently, so you can have as many consumer groups as you want.
|
|
71
|
+
|
|
72
|
+
### Default partition and consumer group
|
|
73
|
+
|
|
74
|
+
The default partition (if not specified) is `Default`. If you don't specify a consumer group, the messages are processed in queue mode, that is creating a default consumer group named `__QUEUE_MODE__`. Each queue can be used at the same time by multiple consumer groups, and each consumer group can process messages from multiple partitions.
|
|
75
|
+
|
|
76
|
+
### Consumer groups
|
|
77
|
+
|
|
78
|
+
Consumer groups are a way to process messages for different purposes. Each consumer group is like a separate queue, and messages in the same consumer group partition are guaranteed to be processed in order. Each consumer group tracks its own position in the queue. You can start a consumer group from the beginning of the queue, from a specific timestamp, or only new messages.
|
|
79
|
+
|
|
80
|
+
### Subscription modes
|
|
81
|
+
|
|
82
|
+
Subscription modes are a way to control the message history that is processed. You can choose to process all messages (including historical ones), only new messages, or messages from a specific timestamp. Subscription modes are only available when using consumer groups.
|
|
83
|
+
|
|
84
|
+
### Long polling (waiting for messages)
|
|
85
|
+
|
|
86
|
+
The client works with the pull model for pop operations, meaning that you need to explicitly request messages from the queue. Pop and consume mehtods can "wait" server side for messages to be available. When the method is called with wait=true, the method will block until messages are available or the timeout is reached. When the timeout is reached, the method returns an empty array. Long polling is a very efficient way to wait for messages, and it is the recommended way to consume messages.
|
|
87
|
+
|
|
88
|
+
### Lease renewal
|
|
89
|
+
|
|
90
|
+
Lease renewal is a way to keep the lock for a partition or consumer group alive. You can use lease renewal to prevent the lock from expiring and being acquired by another consumer/client.
|
|
91
|
+
|
|
92
|
+
### Ack and Nack
|
|
93
|
+
|
|
94
|
+
Ack and Nack are the way to acknowledge or not a message. Ack means that the message has been processed successfully, and Nack means that the message has not been processed successfully. Ack/Nack requires the partitionId, the transactionId and the leaseId to be specified. If the ack is coming from a consumer group, the consumer group name is also required. This logic is handled automatically by the client library, you don't need to worry about it. If the message has already been acknowledged, the ack will be ignored. Based on the configuration, the message can be automatically acknowledged or manually acknowledged.
|
|
95
|
+
|
|
96
|
+
### Transactions
|
|
97
|
+
|
|
98
|
+
Transactions are a way to ensure that a group of operations are atomic. You can use transactions to ensure that a group of operations are executed together, and if one of the operations fails, the entire transaction is rolled back. Transactions are useful to achieve the exactly-once guarantee. You can process a message on a queue, and forward to the next queue only if the consumer lease is still valid, concatenating ack and push into a single transaction.
|
|
99
|
+
|
|
100
|
+
### Dead letter queue
|
|
101
|
+
|
|
102
|
+
The dead letter queue is a way to handle messages that are not processed successfully. When a message is not processed successfully, it is moved to the dead letter queue. Messages goes in the DLQ when they are NACK after the retry limit is reached. You configure the retry limit for each queue at queue config.
|
|
103
|
+
|
|
104
|
+
## Comparison with RabbitMQ, Kafka, and NATS
|
|
105
|
+
|
|
106
|
+
For users familiar with existing message queue systems, here's how Queen's semantics and usage patterns compare:
|
|
107
|
+
|
|
108
|
+
### Conceptual Mapping
|
|
109
|
+
|
|
110
|
+
| Queen Concept | RabbitMQ Equivalent | Kafka Equivalent | NATS Equivalent |
|
|
111
|
+
|---------------|---------------------|------------------|-----------------|
|
|
112
|
+
| Queue | Queue | Topic | Stream (JetStream) |
|
|
113
|
+
| Partition | N/A (queues are single-consumer by default) | Partition | N/A |
|
|
114
|
+
| Consumer Group | Competing Consumers pattern | Consumer Group | Queue Group |
|
|
115
|
+
| Queue Mode (no group) | Exclusive consumer | N/A (always uses groups) | Single subscriber |
|
|
116
|
+
| Lease | Message TTL / Visibility timeout | N/A (commit-based) | Ack wait / nak delay |
|
|
117
|
+
| Ack/Nack | Ack/Nack | Commit offset | Ack/Nak |
|
|
118
|
+
| Transaction | Publisher confirms + consumer acks | Transactional producer/consumer | N/A |
|
|
119
|
+
| Dead Letter Queue | Dead Letter Exchange | N/A (manual) | N/A (manual) |
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
## One single example
|
|
52
123
|
|
|
53
124
|
> 📖 **[Complete Guide: client-js/client-v2/README.md](client-js/client-v2/README.md)** - Full tutorial with all features!
|
|
54
125
|
|
|
@@ -59,131 +130,105 @@ import { Queen } from 'queen-mq'
|
|
|
59
130
|
const queen = new Queen('http://localhost:6632')
|
|
60
131
|
|
|
61
132
|
// Create a queue
|
|
62
|
-
await queen
|
|
133
|
+
await queen
|
|
134
|
+
.queue('critical-task')
|
|
135
|
+
.config({
|
|
136
|
+
leaseTime: 10, // 10 seconds to process the messages (seconds)
|
|
137
|
+
})
|
|
138
|
+
.create()
|
|
63
139
|
|
|
64
140
|
// Push messages
|
|
65
|
-
await queen
|
|
66
|
-
|
|
67
|
-
|
|
141
|
+
await queen
|
|
142
|
+
.queue('critical-task')
|
|
143
|
+
.partition('tenant-123')
|
|
144
|
+
.push([
|
|
145
|
+
{
|
|
146
|
+
transactionId: 'my-id', // This is autogenerated, but you can set your own. Unique per queue partition.
|
|
147
|
+
data: { id: 123, description: 'Critical task' } // The message payload
|
|
148
|
+
},
|
|
68
149
|
])
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
150
|
+
.onSuccess(async (messages) => { // Not mandatory
|
|
151
|
+
console.log('Messages pushed successfully:', messages)
|
|
152
|
+
})
|
|
153
|
+
.onDuplicate(async (messages) => { // Not mandatory, triggered when a message with the same transactionId is pushed
|
|
154
|
+
console.warn('Duplicate transaction IDs detected')
|
|
155
|
+
})
|
|
156
|
+
.onError(async (messages, error) => { // Without callbacks, push throws an error if some messages are not pushed
|
|
157
|
+
console.error('Error pushing messages:', error)
|
|
75
158
|
})
|
|
76
159
|
|
|
77
|
-
//
|
|
78
|
-
const messages = await queen.queue('tasks').batch(10).pop()
|
|
79
|
-
for (const msg of messages) {
|
|
80
|
-
try {
|
|
81
|
-
await processMessage(msg.data)
|
|
82
|
-
await queen.ack(msg, true) // Success
|
|
83
|
-
} catch (error) {
|
|
84
|
-
await queen.ack(msg, false) // Retry
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Partitions (for ordering)
|
|
89
|
-
await queen
|
|
90
|
-
.queue('user-events')
|
|
91
|
-
.partition('user-123')
|
|
92
|
-
.push([{ data: { event: 'login' } }])
|
|
93
|
-
|
|
94
|
-
// Consumer groups (for scaling)
|
|
95
|
-
await queen
|
|
96
|
-
.queue('emails')
|
|
97
|
-
.group('processors')
|
|
98
|
-
.concurrency(5)
|
|
99
|
-
.consume(async (message) => {
|
|
100
|
-
await sendEmail(message.data)
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
// Subscription modes (control message history)
|
|
104
|
-
// Skip historical messages, only process new ones
|
|
105
|
-
await queen
|
|
106
|
-
.queue('events')
|
|
107
|
-
.group('realtime-monitor')
|
|
108
|
-
.subscriptionMode('new')
|
|
109
|
-
.consume(async (message) => {
|
|
110
|
-
console.log('New event only:', message.data)
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
// Start from a specific timestamp
|
|
114
|
-
const timestamp = '2025-10-28T10:00:00.000Z'
|
|
115
|
-
await queen
|
|
116
|
-
.queue('events')
|
|
117
|
-
.group('replay-from-timestamp')
|
|
118
|
-
.subscriptionFrom(timestamp)
|
|
119
|
-
.consume(async (message) => {
|
|
120
|
-
console.log('Replaying from 10am:', message.data)
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
// Transactions (atomic ack + push)
|
|
124
|
-
const [msg] = await queen.queue('input').pop()
|
|
160
|
+
// Consume messages
|
|
125
161
|
await queen
|
|
162
|
+
.queue('critical-task')
|
|
163
|
+
// The consumer group name, without this, the messages are processed in queue mode
|
|
164
|
+
.group('processor-consumer-group')
|
|
165
|
+
// 10 parallel workers
|
|
166
|
+
.concurrency(10)
|
|
167
|
+
// I want to manually ack/nack messages
|
|
168
|
+
.autoAck(false)
|
|
169
|
+
// 10 messages per batch, prefetch them
|
|
170
|
+
.batch(10)
|
|
171
|
+
// Auto-renew the lease for the messages every 2 seconds
|
|
172
|
+
.renewLease(true, 2000)
|
|
173
|
+
// Process each message individually
|
|
174
|
+
.each()
|
|
175
|
+
// Do your work here
|
|
176
|
+
.consume(async (message) => {
|
|
177
|
+
console.log('Processing:', message.data)
|
|
178
|
+
})
|
|
179
|
+
// Ack the messages if you processed them successfully
|
|
180
|
+
.onSuccess(async (message) => {
|
|
181
|
+
await queen
|
|
126
182
|
.transaction()
|
|
127
|
-
.
|
|
128
|
-
.
|
|
129
|
-
.push([{ data: {
|
|
130
|
-
.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
.
|
|
136
|
-
.
|
|
137
|
-
|
|
138
|
-
// Message tracing (for debugging and monitoring)
|
|
139
|
-
await queen.queue('orders').consume(async (msg) => {
|
|
140
|
-
await msg.trace({
|
|
141
|
-
traceName: ['tenant-acme', 'order-flow-123'],
|
|
142
|
-
data: { text: 'Processing started', orderId: msg.data.id }
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
// Process order...
|
|
146
|
-
|
|
147
|
-
await msg.trace({
|
|
148
|
-
traceName: ['tenant-acme', 'order-flow-123'],
|
|
149
|
-
data: { text: 'Order completed' }
|
|
150
|
-
})
|
|
183
|
+
.queue('critical-task-next')
|
|
184
|
+
.partition('XXX')
|
|
185
|
+
.push([{ data: { message: 'Final', count: 3 } }])
|
|
186
|
+
.ack(message)
|
|
187
|
+
.commit()
|
|
188
|
+
})
|
|
189
|
+
// Nack the messages if you failed to process them
|
|
190
|
+
.onError(async (message, error) => {
|
|
191
|
+
console.error('Error processing messages:', error)
|
|
192
|
+
await queen.ack(message, false)
|
|
151
193
|
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Webapp
|
|
197
|
+
|
|
198
|
+
A modern Vue 3 web interface for managing and monitoring Queen MQ.
|
|
199
|
+
|
|
200
|
+

|
|
152
201
|
|
|
153
|
-
|
|
154
|
-
|
|
202
|
+

|
|
203
|
+
|
|
204
|
+

|
|
205
|
+
|
|
206
|
+

|
|
207
|
+
|
|
208
|
+

|
|
209
|
+
|
|
210
|
+

|
|
211
|
+
|
|
212
|
+
**Features:**
|
|
213
|
+
- 📊 Real-time dashboard with system metrics
|
|
214
|
+
- 📈 Message throughput visualization
|
|
215
|
+
- 🔍 Queue management and monitoring
|
|
216
|
+
- 👥 Consumer group tracking
|
|
217
|
+
- 💬 Message browser with trace timeline
|
|
218
|
+
- 🔎 **Trace explorer for debugging distributed workflows**
|
|
219
|
+
- 📉 Analytics and insights
|
|
220
|
+
- 🌓 Dark/light theme support
|
|
221
|
+
|
|
222
|
+
**Quick Start:**
|
|
223
|
+
```bash
|
|
224
|
+
cd webapp
|
|
225
|
+
npm install
|
|
226
|
+
npm run dev
|
|
155
227
|
```
|
|
156
228
|
|
|
157
|
-
|
|
158
|
-
- ✅ Fluent, chainable API
|
|
159
|
-
- ✅ Auto-acknowledgment (or manual control)
|
|
160
|
-
- ✅ Partitions for ordered processing
|
|
161
|
-
- ✅ Consumer groups for scaling
|
|
162
|
-
- ✅ **Subscription modes (new messages only or from timestamp)**
|
|
163
|
-
- ✅ Transactions for atomicity
|
|
164
|
-
- ✅ Client-side buffering for speed
|
|
165
|
-
- ✅ Dead letter queue for failures
|
|
166
|
-
- ✅ Lease renewal for long tasks
|
|
167
|
-
- ✅ **Message tracing for debugging workflows**
|
|
168
|
-
- ✅ Graceful shutdown with buffer flush
|
|
169
|
-
|
|
170
|
-
## 📚 Examples
|
|
171
|
-
|
|
172
|
-
### Client
|
|
173
|
-
|
|
174
|
-
See the **[Complete V2 Guide](client-js/client-v2/README.md)** with 14 parts covering everything from basics to advanced features:
|
|
175
|
-
- Queue creation, push, and consume
|
|
176
|
-
- Partitions and consumer groups
|
|
177
|
-
- **Subscription modes (new messages, timestamps)**
|
|
178
|
-
- Transactions and buffering
|
|
179
|
-
- Dead letter queues
|
|
180
|
-
- Lease renewal
|
|
181
|
-
- Message tracing
|
|
182
|
-
- Complete real-world pipeline example
|
|
183
|
-
- And much more!
|
|
184
|
-
|
|
185
|
-
**Test Files** (94 working examples): [client-js/test-v2/](client-js/test-v2/)
|
|
229
|
+
The dashboard will be available at `http://localhost:4000` or at `http://localhost:6632` directly from the server.
|
|
186
230
|
|
|
231
|
+
See [webapp/README.md](webapp/README.md) for more details.
|
|
187
232
|
|
|
188
233
|
## Architecture
|
|
189
234
|
|
|
@@ -233,32 +278,54 @@ Queen automatically buffers messages to disk when PostgreSQL is unavailable - **
|
|
|
233
278
|
FILE_BUFFER_DIR=/custom/path ./bin/queen-server
|
|
234
279
|
```
|
|
235
280
|
|
|
236
|
-
##
|
|
281
|
+
## Performance Benchmarks
|
|
237
282
|
|
|
238
|
-
|
|
283
|
+
**Preliminary results from C++ client benchmark** (detailed benchmarks on dedicated hardware coming soon)
|
|
239
284
|
|
|
240
|
-
|
|
241
|
-
-
|
|
242
|
-
-
|
|
243
|
-
-
|
|
244
|
-
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
285
|
+
### Test Environment
|
|
286
|
+
- **Hardware:** Apple M4 Air (all components on same machine, 10 processors available)
|
|
287
|
+
- **Server:** 1 server, 4 workers, 95 total DB connections/threads
|
|
288
|
+
- **Database:** PostgreSQL in Docker
|
|
289
|
+
- **Client:** C++ benchmark tool (`benchmark/bin/benchmark`)
|
|
290
|
+
|
|
291
|
+
### Results
|
|
292
|
+
|
|
293
|
+
| Mode | Threads | Messages | Batch Size | Partitions | Queue Mode | Throughput | Bandwidth |
|
|
294
|
+
|------|---------|----------|------------|------------|------------|------------|-----------|
|
|
295
|
+
| **Producer** | 100 | 10K | 1 | 100 | single-queue | **1,431 msg/sec** | 0.41 MB/sec |
|
|
296
|
+
| **Producer** | 100 | 10K | 1 | 100 | multi-queue | 527 msg/sec | 0.15 MB/sec |
|
|
297
|
+
| **Producer** | 10 | 1M | 1,000 | 100 | single-queue | **62,927 msg/sec** | 18.00 MB/sec |
|
|
298
|
+
| **Producer** | 10 | 1M | 1,000 | 100 | multi-queue | 49,663 msg/sec | 14.21 MB/sec |
|
|
299
|
+
| **Producer** | 10 | 1M | 10,000 | 10 | single-queue | 30,351 msg/sec | 8.68 MB/sec |
|
|
300
|
+
| **Consumer** | 10 | 1M | 1,000 | 100 | single-queue | **31,922 msg/sec** | 16.25 MB/sec |
|
|
301
|
+
| **Consumer** | 10 | 1M | 10,000 | 10 | single-queue | **82,974 msg/sec** | 42.25 MB/sec |
|
|
302
|
+
|
|
303
|
+
### Key Observations
|
|
304
|
+
|
|
305
|
+
- ✅ **Batch size matters:** Larger batches (1,000-10,000) dramatically improve throughput
|
|
306
|
+
- ✅ **Single-queue mode faster:** Better partition-level parallelism than multi-queue
|
|
307
|
+
- ✅ **Consumer performance:** Scales well with batch size (82K msg/sec with 10K batches)
|
|
308
|
+
- ✅ **Producer peak:** 62K msg/sec with optimal batch size (1,000)
|
|
309
|
+
- ⚠️ **Small batches:** Performance drops significantly with batch=1 (lock contention)
|
|
310
|
+
|
|
311
|
+
**Note:** All timing metrics exclude idle timeouts and measure actual message processing time (first message → last message).
|
|
312
|
+
|
|
313
|
+
### Run Your Own Benchmarks
|
|
249
314
|
|
|
250
|
-
**Quick Start:**
|
|
251
315
|
```bash
|
|
252
|
-
cd
|
|
253
|
-
|
|
254
|
-
npm run dev
|
|
255
|
-
```
|
|
316
|
+
cd benchmark
|
|
317
|
+
make
|
|
256
318
|
|
|
257
|
-
|
|
319
|
+
# Producer
|
|
320
|
+
./bin/benchmark producer --threads 10 --count 1000000 --batch 1000 --partitions 100 --mode single-queue
|
|
258
321
|
|
|
259
|
-
|
|
322
|
+
# Consumer
|
|
323
|
+
./bin/benchmark consumer --threads 10 --batch 1000 --partitions 100 --mode single-queue
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
See [benchmark/README.md](benchmark/README.md) for detailed usage.
|
|
260
327
|
|
|
261
|
-
## Install server and configure it
|
|
328
|
+
## Install the server and configure it
|
|
262
329
|
|
|
263
330
|
### Quick Start
|
|
264
331
|
|
|
@@ -296,7 +363,7 @@ Includes:
|
|
|
296
363
|
|
|
297
364
|
You can use Queen directly from HTTP without the JS client.
|
|
298
365
|
|
|
299
|
-
[Here the complete list of API endpoints](API.md)
|
|
366
|
+
[Here the complete list of API endpoints](server/API.md)
|
|
300
367
|
|
|
301
368
|
## ⚠️ Known Issues & Roadmap
|
|
302
369
|
|
|
@@ -312,6 +379,7 @@ You can use Queen directly from HTTP without the JS client.
|
|
|
312
379
|
|
|
313
380
|
|
|
314
381
|
### Other TODO Items
|
|
315
|
-
-
|
|
316
|
-
-
|
|
317
|
-
-
|
|
382
|
+
- Mini streaming engine
|
|
383
|
+
- Proper concurrency on clients
|
|
384
|
+
- Check client failover
|
|
385
|
+
- Py client
|
|
@@ -8,6 +8,8 @@ import { LoadBalancer } from './http/LoadBalancer.js'
|
|
|
8
8
|
import { BufferManager } from './buffer/BufferManager.js'
|
|
9
9
|
import { QueueBuilder } from './builders/QueueBuilder.js'
|
|
10
10
|
import { TransactionBuilder } from './builders/TransactionBuilder.js'
|
|
11
|
+
import { StreamBuilder } from './stream/StreamBuilder.js'
|
|
12
|
+
import { StreamConsumer } from './stream/StreamConsumer.js'
|
|
11
13
|
import { CLIENT_DEFAULTS } from './utils/defaults.js'
|
|
12
14
|
import { validateUrl, validateUrls } from './utils/validation.js'
|
|
13
15
|
import * as logger from './utils/logger.js'
|
|
@@ -355,6 +357,32 @@ export class Queen {
|
|
|
355
357
|
return stats
|
|
356
358
|
}
|
|
357
359
|
|
|
360
|
+
// ===========================
|
|
361
|
+
// Streaming API
|
|
362
|
+
// ===========================
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Define a stream for windowed processing
|
|
366
|
+
* @param {string} name - Stream name
|
|
367
|
+
* @param {string} namespace - Stream namespace
|
|
368
|
+
* @returns {StreamBuilder}
|
|
369
|
+
*/
|
|
370
|
+
stream(name, namespace) {
|
|
371
|
+
logger.log('Queen.stream', { name, namespace })
|
|
372
|
+
return new StreamBuilder(this.#httpClient, this, name, namespace)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Create a consumer for a stream
|
|
377
|
+
* @param {string} streamName - Stream name
|
|
378
|
+
* @param {string} consumerGroup - Consumer group
|
|
379
|
+
* @returns {StreamConsumer}
|
|
380
|
+
*/
|
|
381
|
+
consumer(streamName, consumerGroup) {
|
|
382
|
+
logger.log('Queen.consumer', { streamName, consumerGroup })
|
|
383
|
+
return new StreamConsumer(this.#httpClient, this, streamName, consumerGroup)
|
|
384
|
+
}
|
|
385
|
+
|
|
358
386
|
// ===========================
|
|
359
387
|
// Graceful Shutdown
|
|
360
388
|
// ===========================
|
|
@@ -28,8 +28,12 @@ Welcome to Queen client! This is your friendly guide to mastering message queues
|
|
|
28
28
|
|
|
29
29
|
First, install and import:
|
|
30
30
|
|
|
31
|
+
```sh
|
|
32
|
+
npm install queen-mq
|
|
33
|
+
```
|
|
34
|
+
|
|
31
35
|
```javascript
|
|
32
|
-
import { Queen } from '
|
|
36
|
+
import { Queen } from 'queen-mq'
|
|
33
37
|
|
|
34
38
|
// Connect to your Queen server
|
|
35
39
|
const queen = new Queen('http://localhost:6632')
|
|
@@ -1874,7 +1878,7 @@ process.on('SIGINT', async () => {
|
|
|
1874
1878
|
You now know everything about Queen v2! 🎉
|
|
1875
1879
|
|
|
1876
1880
|
**Additional resources:**
|
|
1877
|
-
- [API Documentation](../../API.md) - Complete API reference
|
|
1881
|
+
- [API Documentation](../../server/API.md) - Complete API reference
|
|
1878
1882
|
- [Test Examples](../test-v2/) - 94 working test cases
|
|
1879
1883
|
- [Architecture Guide](../../docs/) - Deep dive into Queen's internals
|
|
1880
1884
|
|
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
* Queue builder for fluent API
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { v7 as uuidv7 } from 'uuid';
|
|
6
|
+
|
|
7
|
+
export const generateUUID = () => {
|
|
8
|
+
return uuidv7();
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
//import { generateUUID } from '../../utils/uuid.js'
|
|
6
12
|
import { isValidUUID } from '../utils/validation.js'
|
|
7
13
|
import { QUEUE_DEFAULTS, CONSUME_DEFAULTS, POP_DEFAULTS } from '../utils/defaults.js'
|
|
8
14
|
import * as logger from '../utils/logger.js'
|
|
@@ -48,12 +48,19 @@ export class TransactionBuilder {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
queue(queueName) {
|
|
51
|
-
// Return a sub-builder for push operations
|
|
52
|
-
|
|
51
|
+
// Return a sub-builder for push operations with partition support
|
|
52
|
+
let partition = null
|
|
53
|
+
|
|
54
|
+
const subBuilder = {
|
|
55
|
+
partition: (partitionKey) => {
|
|
56
|
+
partition = partitionKey
|
|
57
|
+
return subBuilder
|
|
58
|
+
},
|
|
59
|
+
|
|
53
60
|
push: (items) => {
|
|
54
61
|
const itemArray = Array.isArray(items) ? items : [items]
|
|
55
62
|
|
|
56
|
-
logger.log('TransactionBuilder.queue.push', { queue: queueName, count: itemArray.length })
|
|
63
|
+
logger.log('TransactionBuilder.queue.push', { queue: queueName, partition, count: itemArray.length })
|
|
57
64
|
|
|
58
65
|
this.#operations.push({
|
|
59
66
|
type: 'push',
|
|
@@ -68,16 +75,25 @@ export class TransactionBuilder {
|
|
|
68
75
|
payloadValue = item
|
|
69
76
|
}
|
|
70
77
|
|
|
71
|
-
|
|
78
|
+
const result = {
|
|
72
79
|
queue: queueName,
|
|
73
80
|
payload: payloadValue
|
|
74
81
|
}
|
|
82
|
+
|
|
83
|
+
// Add partition if set
|
|
84
|
+
if (partition !== null) {
|
|
85
|
+
result.partition = partition
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return result
|
|
75
89
|
})
|
|
76
90
|
})
|
|
77
91
|
|
|
78
92
|
return this
|
|
79
93
|
}
|
|
80
94
|
}
|
|
95
|
+
|
|
96
|
+
return subBuilder
|
|
81
97
|
}
|
|
82
98
|
|
|
83
99
|
async commit() {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StreamBuilder - Fluent API for defining streams
|
|
3
|
+
*/
|
|
4
|
+
export class StreamBuilder {
|
|
5
|
+
constructor(httpClient, queen, name, namespace) {
|
|
6
|
+
this.httpClient = httpClient;
|
|
7
|
+
this.queen = queen;
|
|
8
|
+
this.config = {
|
|
9
|
+
name,
|
|
10
|
+
namespace,
|
|
11
|
+
source_queue_names: [],
|
|
12
|
+
partitioned: false,
|
|
13
|
+
window_type: 'tumbling',
|
|
14
|
+
window_duration_ms: 60000, // 1 minute default
|
|
15
|
+
window_grace_period_ms: 30000, // 30 seconds default
|
|
16
|
+
window_lease_timeout_ms: 60000 // 1 minute default
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Set the source queues for this stream
|
|
22
|
+
* @param {string[]} queueNames - Array of queue names
|
|
23
|
+
*/
|
|
24
|
+
sources(queueNames = []) {
|
|
25
|
+
this.config.source_queue_names = queueNames;
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Enable partitioned processing (group by partition_id)
|
|
31
|
+
*/
|
|
32
|
+
partitioned() {
|
|
33
|
+
this.config.partitioned = true;
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Configure tumbling time window
|
|
39
|
+
* @param {number} seconds - Window duration in seconds
|
|
40
|
+
*/
|
|
41
|
+
tumblingTime(seconds) {
|
|
42
|
+
this.config.window_type = 'tumbling';
|
|
43
|
+
this.config.window_duration_ms = seconds * 1000;
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Configure grace period for late-arriving messages
|
|
49
|
+
* @param {number} seconds - Grace period in seconds
|
|
50
|
+
*/
|
|
51
|
+
gracePeriod(seconds) {
|
|
52
|
+
this.config.window_grace_period_ms = seconds * 1000;
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Configure lease timeout
|
|
58
|
+
* @param {number} seconds - Lease timeout in seconds
|
|
59
|
+
*/
|
|
60
|
+
leaseTimeout(seconds) {
|
|
61
|
+
this.config.window_lease_timeout_ms = seconds * 1000;
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Define/create the stream on the server
|
|
67
|
+
*/
|
|
68
|
+
async define() {
|
|
69
|
+
return this.httpClient.post('/api/v1/stream/define', this.config);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|