queen-mq 0.3.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +309 -167
  2. package/client-js/client-v2/Queen.js +66 -0
  3. package/client-js/client-v2/README.md +67 -10
  4. package/client-js/client-v2/builders/QueueBuilder.js +7 -1
  5. package/client-js/client-v2/builders/TransactionBuilder.js +20 -4
  6. package/client-js/client-v2/stream/StreamBuilder.js +72 -0
  7. package/client-js/client-v2/stream/StreamConsumer.js +156 -0
  8. package/client-js/client-v2/stream/Window.js +140 -0
  9. package/client-js/test-v2/GETTING_STARTED.md +27 -0
  10. package/client-js/test-v2/MAINTENANCE_TEST.md +148 -0
  11. package/client-js/test-v2/README_SUBSCRIPTION_TESTS.md +201 -0
  12. package/client-js/test-v2/consume.js +11 -0
  13. package/client-js/test-v2/dlq.js +1 -1
  14. package/client-js/test-v2/load.js +2 -0
  15. package/client-js/test-v2/maintenance.js +261 -0
  16. package/client-js/test-v2/retention.js +69 -0
  17. package/client-js/test-v2/run.js +7 -1
  18. package/client-js/test-v2/subscription.js +198 -18
  19. package/client-js/test-v2/transaction.js +66 -4
  20. package/package.json +2 -2
  21. package/client-js/client/client.js +0 -1536
  22. package/client-js/client/index.js +0 -4
  23. package/client-js/client/utils/http.js +0 -173
  24. package/client-js/client/utils/loadBalancer.js +0 -152
  25. package/client-js/client/utils/retry.js +0 -41
  26. package/client-js/services/encryptionService.js +0 -82
  27. package/client-js/services/evictionService.js +0 -160
  28. package/client-js/services/retentionService.js +0 -162
  29. package/client-js/services/startupSync.js +0 -35
  30. package/client-js/test/README.md +0 -224
  31. package/client-js/test/advanced-client-tests.js +0 -761
  32. package/client-js/test/advanced-pattern-tests.js +0 -1137
  33. package/client-js/test/bus-mode-tests.js +0 -361
  34. package/client-js/test/core-tests.js +0 -457
  35. package/client-js/test/edge-case-tests.js +0 -562
  36. package/client-js/test/enterprise-tests.js +0 -637
  37. package/client-js/test/human.js +0 -162
  38. package/client-js/test/partition-locking-tests.js +0 -545
  39. package/client-js/test/partition-transaction-tests.js +0 -482
  40. package/client-js/test/qos0-tests.js +0 -334
  41. package/client-js/test/test-new.js +0 -370
  42. package/client-js/test/utils.js +0 -169
  43. package/client-js/test/window-buffer-test.js +0 -114
  44. package/client-js/utils/logger.js +0 -44
  45. 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, high-performance message queue system built on PostgreSQL**
5
+ **A modern, performant message queue system built on PostgreSQL**
6
6
 
7
7
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE.md)
8
8
  [![Node](https://img.shields.io/badge/node-%3E%3D22.0.0-brightgreen.svg)](https://nodejs.org/)
9
+ [![C++](https://img.shields.io/badge/C%2B%2B-17-blue.svg)](https://en.cppreference.com/w/cpp/17)
9
10
 
10
- [Quick Start](#js-client-usage-v2) • [Complete V2 Guide](client-js/client-v2/README.md) • [Examples](#-examples) • [Webapp](#webapp) • [Server Setup](#install-server-and-configure-it) • [HTTP API](#raw-http-api)
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-rose.svg" alt="Queen Logo" width="120" />
14
+ <img src="assets/queen-logo.svg" alt="Queen Logo" width="120" />
14
15
  </p>
15
16
 
16
17
  </div>
@@ -21,6 +22,12 @@ Why "Queen"? Because years ago, when I first read the word "queue" in my mind, I
21
22
 
22
23
  ---
23
24
 
25
+ [QUICKSTART](docs/QUICKSTART.md)
26
+
27
+ Latest server production version is **0.6.3**.
28
+
29
+ ---
30
+
24
31
  ## Introduction
25
32
 
26
33
  QueenMQ is a queue system written in C++ and backed by PostgreSQL, born from the need to manage many FIFO partitions for Smartchat with solid guarantees around delivery and failure handling. During the initial development, I realized that with a few simple additions to the original design, I could build a very powerful and flexible queue system. This project is almost entirely written by AI, with my supervision—only the test files (or a good part of them) are manually written.
@@ -29,6 +36,7 @@ Here are the main features:
29
36
  - Unlimited FIFO partitions within queues
30
37
  - Queue semantics like RabbitMQ
31
38
  - Consumer groups over queues like Kafka
39
+ - Allows for all the patterns you need, from simple queues to complex workflows and request/response patterns
32
40
  - QoS levels: Exactly-once delivery (with transactionId), at-least-once delivery, and at-most-once delivery
33
41
  - Subscription modes for replay (new messages only or from a specific timestamp) and message history control
34
42
  - Transactions between operations (push and ack mainly) for atomicity
@@ -36,19 +44,97 @@ Here are the main features:
36
44
  - Lease renewal for long-running tasks
37
45
  - Message tracing for debugging workflows
38
46
  - Encryption of messages at DB level
47
+ - Automatic message retention and cleanup - Configurable per-queue retention policies
48
+ - Streaming capabilities for real-time aggregation and processing of messages
49
+ - A nice webapp for monitoring and managing the system
50
+ - 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
51
 
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 is written in JavaScript (support for other languages is planned). There's also a modern Vue 3 web app for monitoring and managing the system.
52
+ 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
53
 
42
- With proper batching, the system can handle +100k **messages** per second (not req/s) on modest hardware.
54
+ With proper batching, the system can handle +200k **messages** per second (not req/s) on modest hardware.
43
55
 
44
56
  Main documentation:
45
- - [Client Guide](client-js/client-v2/README.md)
57
+ - [Client Guide JS](client-js/client-v2/README.md)
58
+ - [Client Guide C++](client-cpp/README.md)
46
59
  - [Server Guide](server/README.md)
47
- - [API Reference](API.md)
60
+ - [Streaming Guide](docs/STREAMING_USAGE.md)
61
+ - [API Reference](server/API.md)
62
+ - [Message Retention & Cleanup](docs/RETENTION.md)
48
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.
69
+
70
+ ### Queues
71
+
72
+ 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.
73
+
74
+ ### Partitions
75
+
76
+ 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.
77
+
78
+ ### Default partition and consumer group
79
+
80
+ 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.
81
+
82
+ ### Consumer groups
83
+
84
+ 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.
85
+
86
+ ### Subscription modes
87
+
88
+ 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.
89
+
90
+ **Server Default:** By default, new consumer groups process all historical messages. You can change this server-wide:
91
+
92
+ ```bash
93
+ export DEFAULT_SUBSCRIPTION_MODE="new" # Skip historical messages by default
94
+ ./bin/queen-server
95
+ ```
96
+
97
+ This is useful for real-time systems where only new messages matter, or to prevent accidental processing of large backlogs. Clients can still override with `.subscriptionMode()` if needed.
98
+
99
+ ### Long polling (waiting for messages)
100
+
101
+ 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.
102
+
103
+ ### Lease renewal
104
+
105
+ 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.
106
+
107
+ ### Ack and Nack
108
+
109
+ 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.
110
+
111
+ ### Transactions
112
+
113
+ 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.
49
114
 
115
+ ### Dead letter queue
50
116
 
51
- ## JS Client Usage
117
+ 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.
118
+
119
+ ## Comparison with RabbitMQ, Kafka, and NATS
120
+
121
+ For users familiar with existing message queue systems, here's how Queen's semantics and usage patterns compare:
122
+
123
+ ### Conceptual Mapping
124
+
125
+ | Queen Concept | RabbitMQ Equivalent | Kafka Equivalent | NATS Equivalent |
126
+ |---------------|---------------------|------------------|-----------------|
127
+ | Queue | Queue | Topic | Stream (JetStream) |
128
+ | Partition | N/A (queues are single-consumer by default) | Partition | N/A |
129
+ | Consumer Group | Competing Consumers pattern | Consumer Group | Queue Group |
130
+ | Queue Mode (no group) | Exclusive consumer | N/A (always uses groups) | Single subscriber |
131
+ | Lease | Message TTL / Visibility timeout | N/A (commit-based) | Ack wait / nak delay |
132
+ | Ack/Nack | Ack/Nack | Commit offset | Ack/Nak |
133
+ | Transaction | Publisher confirms + consumer acks | Transactional producer/consumer | N/A |
134
+ | Dead Letter Queue | Dead Letter Exchange | N/A (manual) | N/A (manual) |
135
+
136
+
137
+ ## One single example
52
138
 
53
139
  > 📖 **[Complete Guide: client-js/client-v2/README.md](client-js/client-v2/README.md)** - Full tutorial with all features!
54
140
 
@@ -59,162 +145,191 @@ import { Queen } from 'queen-mq'
59
145
  const queen = new Queen('http://localhost:6632')
60
146
 
61
147
  // Create a queue
62
- await queen.queue('tasks').create()
148
+ await queen
149
+ .queue('critical-task')
150
+ .config({
151
+ leaseTime: 10, // 10 seconds to process the messages (seconds)
152
+ })
153
+ .create()
63
154
 
64
155
  // Push messages
65
- await queen.queue('tasks').push([
66
- { data: { job: 'send-email', to: 'alice@example.com' } },
67
- { data: { job: 'process-image', id: 123 } }
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
+ },
68
164
  ])
69
-
70
- // Consume messages (auto-ack on success, auto-nack on error)
71
- await queen.queue('tasks').consume(async (message) => {
72
- console.log('Processing:', message.data)
73
- // If this succeeds → message completed ✅
74
- // If this throws → message retried 🔄
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)
75
173
  })
76
174
 
77
- // Pop messages manually
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()
175
+ // Consume messages
125
176
  await queen
177
+ .queue('critical-task')
178
+ // The consumer group name, without this, the messages are processed in queue mode
179
+ .group('processor-consumer-group')
180
+ // 10 parallel workers
181
+ .concurrency(10)
182
+ // I want to manually ack/nack messages
183
+ .autoAck(false)
184
+ // 10 messages per batch, prefetch them
185
+ .batch(10)
186
+ // Auto-renew the lease for the messages every 2 seconds
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
126
197
  .transaction()
127
- .ack(msg)
128
- .queue('output')
129
- .push([{ data: { processed: true } }])
130
- .commit()
131
-
132
- // Client-side buffering (for high throughput)
133
- await queen
134
- .queue('logs')
135
- .buffer({ messageCount: 100, timeMillis: 1000 })
136
- .push([{ data: { level: 'info', message: 'Server started' } }])
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
- })
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)
151
208
  })
209
+ ```
210
+
211
+ ## Webapp
212
+
213
+ A modern Vue 3 web interface for managing and monitoring Queen MQ.
214
+
215
+ ![Queen MQ Dashboard](./assets/dashboard.png)
216
+
217
+ ![Queen MQ Queues](./assets/queues.png)
218
+
219
+ ![Queen MQ Messages](./assets/messages.png)
152
220
 
153
- // Graceful shutdown
154
- await queen.close()
221
+ ![Queen MQ Traces](./assets/traces.png)
222
+
223
+ ![Queen MQ Analytics](./assets/analytics.png)
224
+
225
+ ![Queen MQ System Metrics](./assets/systemmetrics.png)
226
+
227
+ **Features:**
228
+ - 📊 Real-time dashboard with system metrics
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
236
+
237
+ **Quick Start:**
238
+ ```bash
239
+ cd webapp
240
+ npm install
241
+ npm run dev
155
242
  ```
156
243
 
157
- **Key Features:**
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/)
244
+ The dashboard will be available at `http://localhost:4000` or at `http://localhost:6632` directly from the server.
186
245
 
246
+ See [webapp/README.md](webapp/README.md) for more details.
187
247
 
188
248
  ## Architecture
189
249
 
190
- Queen uses a high-performance **acceptor/worker pattern** with uWebSockets, combining non-blocking I/O for HTTP/WebSocket with a dedicated thread pool for database operations.
250
+ Queen uses a high-performance **acceptor/worker pattern** with uWebSockets, featuring a **fully asynchronous, non-blocking PostgreSQL architecture** for maximum throughput and minimal latency.
251
+
252
+ ### Core Components
253
+
254
+ **Network Layer:**
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
257
+
258
+ **Database Layer:**
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
191
269
 
192
- **View the interactive architecture diagram:** [architecture.svg](./assets/architecture.svg)
270
+ **Background Services:**
271
+ - **Poll Workers**: 4 dedicated threads for long-polling operations
272
+ - Non-blocking I/O with exponential backoff (100ms→2000ms)
273
+ - Intention registry for efficient request grouping
274
+ - Rate-limited queries to prevent database overload
275
+ - **Background Pool**: 8 connections for metrics, retention, eviction, and stream management
276
+
277
+ ### Request Flow
278
+
279
+ **Standard Operations (PUSH/POP/ACK/TRANSACTION):**
280
+ ```
281
+ Client Request
282
+ ↓
283
+ Acceptor (port 6632)
284
+ ↓
285
+ Worker (event loop) → AsyncQueueManager → AsyncDbPool → PostgreSQL
286
+ ↓ (non-blocking) (socket I/O)
287
+ Response sent immediately
288
+ ```
289
+
290
+ **Long-Polling Operations (wait=true):**
291
+ ```
292
+ Client Request
293
+ ↓
294
+ Worker registers intention in Registry
295
+ ↓
296
+ Poll Worker (50ms interval)
297
+ ↓
298
+ Non-blocking query via AsyncDbPool
299
+ ↓
300
+ Messages distributed to waiting clients
301
+ ```
193
302
 
194
- **Key Components:**
195
- - **UWS Acceptor**: Single thread listening on port 6632, round-robin distributes to workers
196
- - **UWS Workers**: N event loop threads (default: 10) handling HTTP routes and WebSocket
197
- - **Response Timers**: Per-worker timers (25ms tick) drain response queue back to clients
198
- - **DB ThreadPool**: Separate pool for blocking PostgreSQL operations
199
- - **Poll Workers**: 2 reserved threads for long-polling with adaptive backoff (100ms→2000ms)
200
- - **Poll Intention Registry**: Thread-safe store for long-poll requests
201
- - **Database Pool**: 150 shared PostgreSQL connections (libpq) with mutex/condition variable
202
- - **Response Queue**: Thread-safe queue decoupling DB results from event loop responses
303
+ ### Performance Characteristics
203
304
 
204
- **Request Flow:**
205
- 1. Client → Acceptor → Worker (event loop)
206
- 2. Worker registers response, submits job to DB ThreadPool
207
- 3. DB thread executes query, pushes result to Response Queue
208
- 4. Worker's response timer drains queue, sends HTTP response
305
+ **Latency:**
306
+ - **POP (immediate)**: 10-50ms
307
+ - **ACK**: 10-50ms
308
+ - **TRANSACTION**: 50-200ms
309
+ - **Long-polling**: Configurable (50ms-2000ms backoff)
209
310
 
210
- **Long-Polling Flow:**
211
- 1. No immediate messages? Register intention in Registry
212
- 2. Poll Workers wake every 50ms, group intentions by queue/partition/consumer
213
- 3. Rate-limited DB queries (100ms initial, exponential backoff to 2s)
214
- 4. Messages distributed to waiting clients via Response Queue
215
- 5. Timeouts detected by Poll Workers, send 204 No Content
311
+ **Throughput:**
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)
216
315
 
217
- This architecture provides high concurrency, efficient connection pooling, and minimal latency for both immediate and long-polling requests.
316
+ **Resource Usage:**
317
+ - **Database connections**: 150 total (142 async + 8 background)
318
+ - **Threads**: 14 total (10 workers + 4 poll workers)
319
+ - **Memory**: ~80MB for thread stacks + connection overhead
320
+
321
+ ### Scalability
322
+
323
+ The event-driven architecture enables:
324
+ - ✅ Unlimited concurrent requests (limited only by connection pool)
325
+ - ✅ Horizontal scaling (multiple server instances)
326
+ - ✅ Efficient resource utilization (non-blocking I/O)
327
+ - ✅ Low latency under high load
328
+ - ✅ Automatic load distribution across workers
329
+
330
+ **📚 Technical Documentation:**
331
+ - [Server Architecture Guide](server/README.md) - Complete server setup and configuration
332
+ - [Architecture Diagrams](assets/architecture.svg) - Visual architecture overview
218
333
 
219
334
  ### PostgreSQL Failover
220
335
 
@@ -233,32 +348,59 @@ Queen automatically buffers messages to disk when PostgreSQL is unavailable - **
233
348
  FILE_BUFFER_DIR=/custom/path ./bin/queen-server
234
349
  ```
235
350
 
236
- ## Webapp
351
+ ## Performance Benchmarks
237
352
 
238
- A modern Vue 3 web interface for managing and monitoring Queen MQ.
353
+ **Preliminary results from C++ client benchmark** (detailed benchmarks on dedicated hardware coming soon)
239
354
 
240
- **Features:**
241
- - 📊 Real-time dashboard with system metrics
242
- - 📈 Message throughput visualization
243
- - 🔍 Queue management and monitoring
244
- - 👥 Consumer group tracking
245
- - 💬 Message browser with trace timeline
246
- - 🔎 **Trace explorer for debugging distributed workflows**
247
- - 📉 Analytics and insights
248
- - 🌓 Dark/light theme support
355
+ ### Test Environment
356
+ - **Hardware:** Apple M4 Air (all components on same machine, 10 processors available)
357
+ - **Server:** 1 server, 4 workers, 95 total DB connections/threads
358
+ - **Database:** PostgreSQL in Docker
359
+ - **Client:** C++ benchmark tool (`benchmark/bin/benchmark`)
360
+
361
+ ### Results
362
+
363
+ All tests run with: `--threads 10 --partitions 10 --mode single-queue`
364
+
365
+ | Test | Mode | Messages | Batch Size | Throughput | Bandwidth |
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 |
377
+
378
+ ### Key Observations
379
+
380
+ - ✅ **Batch size matters:** Larger batches (1,000) dramatically improve throughput
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)
385
+
386
+ **Note:** All timing metrics are based on processing time (excludes idle time) and measure actual message processing (first message → last message).
387
+
388
+ ### Run Your Own Benchmarks
249
389
 
250
- **Quick Start:**
251
390
  ```bash
252
- cd webapp
253
- npm install
254
- npm run dev
255
- ```
391
+ cd benchmark
392
+ make
256
393
 
257
- The dashboard will be available at `http://localhost:4000`
394
+ # Producer
395
+ ./bin/benchmark producer --threads 10 --count 1000000 --batch 1000 --partitions 100 --mode single-queue
258
396
 
259
- See [webapp/README.md](webapp/README.md) for more details.
397
+ # Consumer
398
+ ./bin/benchmark consumer --threads 10 --batch 1000 --partitions 100 --mode single-queue
399
+ ```
400
+
401
+ See [benchmark/README.md](benchmark/README.md) for detailed usage.
260
402
 
261
- ## Install server and configure it
403
+ ## Install the server and configure it
262
404
 
263
405
  ### Quick Start
264
406
 
@@ -296,7 +438,7 @@ Includes:
296
438
 
297
439
  You can use Queen directly from HTTP without the JS client.
298
440
 
299
- [Here the complete list of API endpoints](API.md)
441
+ [Here the complete list of API endpoints](server/API.md)
300
442
 
301
443
  ## ⚠️ Known Issues & Roadmap
302
444
 
@@ -312,6 +454,6 @@ You can use Queen directly from HTTP without the JS client.
312
454
 
313
455
 
314
456
  ### Other TODO Items
315
- - retention jobs
316
- - auth
317
- - streaming engine
457
+ - Proper concurrency on clients
458
+ - Check client failover
459
+ - 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,70 @@ export class Queen {
355
357
  return stats
356
358
  }
357
359
 
360
+ // ===========================
361
+ // Consumer Group Management
362
+ // ===========================
363
+
364
+ /**
365
+ * Delete a consumer group and optionally its subscription metadata
366
+ * @param {string} consumerGroup - Consumer group name
367
+ * @param {boolean} deleteMetadata - Whether to delete subscription metadata (default: true)
368
+ * @returns {Promise<object>}
369
+ */
370
+ async deleteConsumerGroup(consumerGroup, deleteMetadata = true) {
371
+ logger.log('Queen.deleteConsumerGroup', { consumerGroup, deleteMetadata })
372
+
373
+ const url = `/api/v1/consumer-groups/${encodeURIComponent(consumerGroup)}?deleteMetadata=${deleteMetadata}`
374
+ const response = await this.#httpClient.delete(url)
375
+
376
+ logger.log('Queen.deleteConsumerGroup', { success: true, consumerGroup })
377
+ return response
378
+ }
379
+
380
+ /**
381
+ * Update subscription timestamp for a consumer group
382
+ * @param {string} consumerGroup - Consumer group name
383
+ * @param {string} timestamp - New subscription timestamp (ISO 8601)
384
+ * @returns {Promise<object>}
385
+ */
386
+ async updateConsumerGroupTimestamp(consumerGroup, timestamp) {
387
+ logger.log('Queen.updateConsumerGroupTimestamp', { consumerGroup, timestamp })
388
+
389
+ const url = `/api/v1/consumer-groups/${encodeURIComponent(consumerGroup)}/subscription`
390
+ const response = await this.#httpClient.post(url, {
391
+ subscriptionTimestamp: timestamp
392
+ })
393
+
394
+ logger.log('Queen.updateConsumerGroupTimestamp', { success: true, consumerGroup })
395
+ return response
396
+ }
397
+
398
+ // ===========================
399
+ // Streaming API
400
+ // ===========================
401
+
402
+ /**
403
+ * Define a stream for windowed processing
404
+ * @param {string} name - Stream name
405
+ * @param {string} namespace - Stream namespace
406
+ * @returns {StreamBuilder}
407
+ */
408
+ stream(name, namespace) {
409
+ logger.log('Queen.stream', { name, namespace })
410
+ return new StreamBuilder(this.#httpClient, this, name, namespace)
411
+ }
412
+
413
+ /**
414
+ * Create a consumer for a stream
415
+ * @param {string} streamName - Stream name
416
+ * @param {string} consumerGroup - Consumer group
417
+ * @returns {StreamConsumer}
418
+ */
419
+ consumer(streamName, consumerGroup) {
420
+ logger.log('Queen.consumer', { streamName, consumerGroup })
421
+ return new StreamConsumer(this.#httpClient, this, streamName, consumerGroup)
422
+ }
423
+
358
424
  // ===========================
359
425
  // Graceful Shutdown
360
426
  // ===========================