queen-mq 0.1.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/API.md +1116 -0
- package/CACHE.md +519 -0
- package/DASHBOARD-V3.md +478 -0
- package/DASHBOARD.md +382 -0
- package/MOD_QUEUE.md +453 -0
- package/PARTITION_LOCKING_DESIGN.md +989 -0
- package/PLAN.md +707 -0
- package/QUERY_ANALSYS.md +72 -0
- package/QUEUE_BUS.md +334 -0
- package/README.md +1495 -0
- package/V2-PLAN.md +236 -0
- package/assets/dashboard.png +0 -0
- package/dashboard/.vscode/extensions.json +3 -0
- package/dashboard/README.md +5 -0
- package/dashboard/index.html +14 -0
- package/dashboard/package-lock.json +1458 -0
- package/dashboard/package.json +25 -0
- package/dashboard/public/vite.svg +1 -0
- package/dashboard/src/App.vue +29 -0
- package/dashboard/src/assets/styles/main.css +908 -0
- package/dashboard/src/assets/vue.svg +1 -0
- package/dashboard/src/components/cards/MetricCard.vue +298 -0
- package/dashboard/src/components/charts/QueueDepthChart.vue +276 -0
- package/dashboard/src/components/charts/QueueLagChart.vue +436 -0
- package/dashboard/src/components/charts/ThroughputChart.vue +302 -0
- package/dashboard/src/components/common/ActivityFeed.vue +251 -0
- package/dashboard/src/components/layout/AppHeader.vue +208 -0
- package/dashboard/src/components/layout/AppLayout.vue +88 -0
- package/dashboard/src/components/layout/AppSidebar.vue +261 -0
- package/dashboard/src/main.js +44 -0
- package/dashboard/src/router.js +54 -0
- package/dashboard/src/services/api.js +187 -0
- package/dashboard/src/services/websocket.js +167 -0
- package/dashboard/src/utils/constants.js +56 -0
- package/dashboard/src/utils/helpers.js +118 -0
- package/dashboard/src/views/Analytics.vue +912 -0
- package/dashboard/src/views/Dashboard.vue +906 -0
- package/dashboard/src/views/Messages.vue +437 -0
- package/dashboard/src/views/QueueDetail.vue +501 -0
- package/dashboard/src/views/Queues.vue +333 -0
- package/dashboard/vite.config.js +30 -0
- package/debug-namespace.js +110 -0
- package/docs/long-polling.md +159 -0
- package/docs/multi-server-cache-solutions.md +185 -0
- package/docs/performance-tuning.md +222 -0
- package/examples/bus-mode.js +239 -0
- package/examples/continuous-consumer-optimized.js +215 -0
- package/examples/continuous-consumer.js +159 -0
- package/examples/continuous-producer.js +343 -0
- package/examples/mixed-mode.js +277 -0
- package/examples/multi-server-test.js +305 -0
- package/examples/single.js +64 -0
- package/examples/smartchat-dealyed.js +42 -0
- package/examples/smartchat.js +52 -0
- package/examples/test-cache-invalidation.js +119 -0
- package/examples/test-cache-multi-server.js +245 -0
- package/examples/test-minimal-client.js +112 -0
- package/examples/test-queue-creation-policy.js +137 -0
- package/init-db.js +20 -0
- package/package.json +36 -0
- package/src/client/client.js +291 -0
- package/src/client/index.js +6 -0
- package/src/client/queenClient.js +513 -0
- package/src/client/utils/http.js +172 -0
- package/src/client/utils/loadBalancer.js +152 -0
- package/src/client/utils/retry.js +35 -0
- package/src/config.js +215 -0
- package/src/database/connection.js +103 -0
- package/src/database/poolManager.js +192 -0
- package/src/database/schema-v2.sql +214 -0
- package/src/managers/eventManager.js +59 -0
- package/src/managers/queueManagerOptimized.js +1512 -0
- package/src/managers/resourceCache.js +96 -0
- package/src/managers/systemEventManager.js +127 -0
- package/src/routes/ack.js +26 -0
- package/src/routes/analytics.js +812 -0
- package/src/routes/configure.js +46 -0
- package/src/routes/messages.js +298 -0
- package/src/routes/pop.js +85 -0
- package/src/routes/push.js +28 -0
- package/src/routes/resources.js +296 -0
- package/src/server.js +1286 -0
- package/src/services/encryptionService.js +82 -0
- package/src/services/evictionService.js +131 -0
- package/src/services/retentionService.js +129 -0
- package/src/services/startupSync.js +35 -0
- package/src/test/test.js +4521 -0
- package/src/utils/logger.js +44 -0
- package/src/utils/uuid.js +5 -0
- package/src/websocket/wsServer.js +221 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
export const createResourcesRoutes = (pool) => {
|
|
2
|
+
|
|
3
|
+
// Get all queues with their partitions
|
|
4
|
+
const getQueues = async (filters = {}) => {
|
|
5
|
+
const { namespace, task } = filters;
|
|
6
|
+
|
|
7
|
+
let query = `
|
|
8
|
+
SELECT DISTINCT
|
|
9
|
+
q.id as queue_id,
|
|
10
|
+
q.name as queue_name,
|
|
11
|
+
q.namespace,
|
|
12
|
+
q.task,
|
|
13
|
+
q.created_at as queue_created_at,
|
|
14
|
+
COUNT(DISTINCT p.id) as partition_count,
|
|
15
|
+
COUNT(DISTINCT m.id) as message_count,
|
|
16
|
+
COUNT(DISTINCT CASE WHEN ms.status = 'pending' THEN m.id END) as pending_count,
|
|
17
|
+
COUNT(DISTINCT CASE WHEN ms.status = 'processing' THEN m.id END) as processing_count
|
|
18
|
+
FROM queen.queues q
|
|
19
|
+
LEFT JOIN queen.partitions p ON p.queue_id = q.id
|
|
20
|
+
LEFT JOIN queen.messages m ON m.partition_id = p.id
|
|
21
|
+
LEFT JOIN queen.messages_status ms ON ms.message_id = m.id AND ms.consumer_group IS NULL
|
|
22
|
+
WHERE 1=1
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
const params = [];
|
|
26
|
+
if (namespace) {
|
|
27
|
+
params.push(namespace);
|
|
28
|
+
query += ` AND q.namespace = $${params.length}`;
|
|
29
|
+
}
|
|
30
|
+
if (task) {
|
|
31
|
+
params.push(task);
|
|
32
|
+
query += ` AND q.task = $${params.length}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
query += ` GROUP BY q.id, q.name, q.namespace, q.task, q.created_at
|
|
36
|
+
ORDER BY q.created_at DESC`;
|
|
37
|
+
|
|
38
|
+
const result = await pool.query(query, params);
|
|
39
|
+
|
|
40
|
+
return result.rows.map(row => ({
|
|
41
|
+
id: row.queue_id,
|
|
42
|
+
name: row.queue_name,
|
|
43
|
+
namespace: row.namespace,
|
|
44
|
+
task: row.task,
|
|
45
|
+
createdAt: row.queue_created_at,
|
|
46
|
+
partitions: parseInt(row.partition_count),
|
|
47
|
+
messages: {
|
|
48
|
+
total: parseInt(row.message_count),
|
|
49
|
+
pending: parseInt(row.pending_count),
|
|
50
|
+
processing: parseInt(row.processing_count)
|
|
51
|
+
}
|
|
52
|
+
}));
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Get single queue with detailed partition information
|
|
56
|
+
const getQueue = async (queueName) => {
|
|
57
|
+
// Get queue info
|
|
58
|
+
const queueResult = await pool.query(`
|
|
59
|
+
SELECT
|
|
60
|
+
q.id,
|
|
61
|
+
q.name,
|
|
62
|
+
q.namespace,
|
|
63
|
+
q.task,
|
|
64
|
+
q.created_at
|
|
65
|
+
FROM queen.queues q
|
|
66
|
+
WHERE q.name = $1
|
|
67
|
+
`, [queueName]);
|
|
68
|
+
|
|
69
|
+
if (queueResult.rows.length === 0) {
|
|
70
|
+
throw new Error('Queue not found');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const queue = queueResult.rows[0];
|
|
74
|
+
|
|
75
|
+
// Get partitions with stats (partitions no longer have options or priority)
|
|
76
|
+
const partitionsResult = await pool.query(`
|
|
77
|
+
SELECT
|
|
78
|
+
p.id,
|
|
79
|
+
p.name,
|
|
80
|
+
p.created_at,
|
|
81
|
+
COUNT(DISTINCT m.id) as message_count,
|
|
82
|
+
COUNT(DISTINCT CASE WHEN ms.status = 'pending' THEN m.id END) as pending,
|
|
83
|
+
COUNT(DISTINCT CASE WHEN ms.status = 'processing' THEN m.id END) as processing,
|
|
84
|
+
COUNT(DISTINCT CASE WHEN ms.status = 'completed' THEN m.id END) as completed,
|
|
85
|
+
COUNT(DISTINCT CASE WHEN ms.status = 'failed' THEN m.id END) as failed,
|
|
86
|
+
COUNT(DISTINCT CASE WHEN ms.status = 'dead_letter' THEN m.id END) as dead_letter,
|
|
87
|
+
MIN(m.created_at) as oldest_message,
|
|
88
|
+
MAX(m.created_at) as newest_message
|
|
89
|
+
FROM queen.partitions p
|
|
90
|
+
LEFT JOIN queen.messages m ON m.partition_id = p.id
|
|
91
|
+
LEFT JOIN queen.messages_status ms ON ms.message_id = m.id AND ms.consumer_group IS NULL
|
|
92
|
+
WHERE p.queue_id = $1
|
|
93
|
+
GROUP BY p.id, p.name, p.created_at
|
|
94
|
+
ORDER BY p.name
|
|
95
|
+
`, [queue.id]);
|
|
96
|
+
|
|
97
|
+
const partitions = partitionsResult.rows.map(row => ({
|
|
98
|
+
id: row.id,
|
|
99
|
+
name: row.name,
|
|
100
|
+
createdAt: row.created_at,
|
|
101
|
+
stats: {
|
|
102
|
+
total: parseInt(row.message_count),
|
|
103
|
+
pending: parseInt(row.pending),
|
|
104
|
+
processing: parseInt(row.processing),
|
|
105
|
+
completed: parseInt(row.completed),
|
|
106
|
+
failed: parseInt(row.failed),
|
|
107
|
+
deadLetter: parseInt(row.dead_letter)
|
|
108
|
+
},
|
|
109
|
+
oldestMessage: row.oldest_message,
|
|
110
|
+
newestMessage: row.newest_message
|
|
111
|
+
}));
|
|
112
|
+
|
|
113
|
+
// Calculate totals
|
|
114
|
+
const totals = partitions.reduce((acc, p) => ({
|
|
115
|
+
total: acc.total + p.stats.total,
|
|
116
|
+
pending: acc.pending + p.stats.pending,
|
|
117
|
+
processing: acc.processing + p.stats.processing,
|
|
118
|
+
completed: acc.completed + p.stats.completed,
|
|
119
|
+
failed: acc.failed + p.stats.failed,
|
|
120
|
+
deadLetter: acc.deadLetter + p.stats.deadLetter
|
|
121
|
+
}), {
|
|
122
|
+
total: 0,
|
|
123
|
+
pending: 0,
|
|
124
|
+
processing: 0,
|
|
125
|
+
completed: 0,
|
|
126
|
+
failed: 0,
|
|
127
|
+
deadLetter: 0
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
id: queue.id,
|
|
132
|
+
name: queue.name,
|
|
133
|
+
namespace: queue.namespace,
|
|
134
|
+
task: queue.task,
|
|
135
|
+
createdAt: queue.created_at,
|
|
136
|
+
partitions,
|
|
137
|
+
totals
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Get all partitions across all queues
|
|
142
|
+
const getPartitions = async (filters = {}) => {
|
|
143
|
+
const { queue, minDepth } = filters;
|
|
144
|
+
|
|
145
|
+
let query = `
|
|
146
|
+
SELECT
|
|
147
|
+
p.id,
|
|
148
|
+
p.name as partition_name,
|
|
149
|
+
p.created_at,
|
|
150
|
+
q.name as queue_name,
|
|
151
|
+
q.namespace,
|
|
152
|
+
q.task,
|
|
153
|
+
q.priority,
|
|
154
|
+
COUNT(DISTINCT CASE WHEN ms.status = 'pending' THEN m.id END) as pending,
|
|
155
|
+
COUNT(DISTINCT CASE WHEN ms.status = 'processing' THEN m.id END) as processing,
|
|
156
|
+
COUNT(DISTINCT m.id) as total
|
|
157
|
+
FROM queen.partitions p
|
|
158
|
+
JOIN queen.queues q ON q.id = p.queue_id
|
|
159
|
+
LEFT JOIN queen.messages m ON m.partition_id = p.id
|
|
160
|
+
LEFT JOIN queen.messages_status ms ON ms.message_id = m.id AND ms.consumer_group IS NULL
|
|
161
|
+
WHERE 1=1
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
const params = [];
|
|
165
|
+
if (queue) {
|
|
166
|
+
params.push(queue);
|
|
167
|
+
query += ` AND q.name = $${params.length}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
query += ` GROUP BY p.id, p.name, p.created_at,
|
|
171
|
+
q.name, q.namespace, q.task, q.priority`;
|
|
172
|
+
|
|
173
|
+
if (minDepth) {
|
|
174
|
+
params.push(minDepth);
|
|
175
|
+
query += ` HAVING COUNT(DISTINCT CASE WHEN ms.status = 'pending' THEN m.id END) >= $${params.length}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
query += ` ORDER BY q.name, q.priority DESC, p.name`;
|
|
179
|
+
|
|
180
|
+
const result = await pool.query(query, params);
|
|
181
|
+
|
|
182
|
+
return result.rows.map(row => ({
|
|
183
|
+
id: row.id,
|
|
184
|
+
name: row.partition_name,
|
|
185
|
+
queue: row.queue_name,
|
|
186
|
+
namespace: row.namespace,
|
|
187
|
+
task: row.task,
|
|
188
|
+
queuePriority: row.priority, // Priority is now at queue level
|
|
189
|
+
createdAt: row.created_at,
|
|
190
|
+
depth: parseInt(row.pending),
|
|
191
|
+
processing: parseInt(row.processing),
|
|
192
|
+
total: parseInt(row.total)
|
|
193
|
+
}));
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Get namespace/task groupings
|
|
197
|
+
const getNamespaces = async () => {
|
|
198
|
+
const result = await pool.query(`
|
|
199
|
+
SELECT DISTINCT
|
|
200
|
+
q.namespace,
|
|
201
|
+
COUNT(DISTINCT q.id) as queue_count,
|
|
202
|
+
COUNT(DISTINCT p.id) as partition_count,
|
|
203
|
+
COUNT(DISTINCT m.id) as message_count,
|
|
204
|
+
COUNT(DISTINCT CASE WHEN ms.status = 'pending' THEN m.id END) as pending_count
|
|
205
|
+
FROM queen.queues q
|
|
206
|
+
LEFT JOIN queen.partitions p ON p.queue_id = q.id
|
|
207
|
+
LEFT JOIN queen.messages m ON m.partition_id = p.id
|
|
208
|
+
LEFT JOIN queen.messages_status ms ON ms.message_id = m.id AND ms.consumer_group IS NULL
|
|
209
|
+
WHERE q.namespace IS NOT NULL
|
|
210
|
+
GROUP BY q.namespace
|
|
211
|
+
ORDER BY q.namespace
|
|
212
|
+
`);
|
|
213
|
+
|
|
214
|
+
return result.rows.map(row => ({
|
|
215
|
+
namespace: row.namespace,
|
|
216
|
+
queues: parseInt(row.queue_count),
|
|
217
|
+
partitions: parseInt(row.partition_count),
|
|
218
|
+
messages: {
|
|
219
|
+
total: parseInt(row.message_count),
|
|
220
|
+
pending: parseInt(row.pending_count)
|
|
221
|
+
}
|
|
222
|
+
}));
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const getTasks = async () => {
|
|
226
|
+
const result = await pool.query(`
|
|
227
|
+
SELECT DISTINCT
|
|
228
|
+
q.task,
|
|
229
|
+
COUNT(DISTINCT q.id) as queue_count,
|
|
230
|
+
COUNT(DISTINCT p.id) as partition_count,
|
|
231
|
+
COUNT(DISTINCT m.id) as message_count,
|
|
232
|
+
COUNT(DISTINCT CASE WHEN ms.status = 'pending' THEN m.id END) as pending_count
|
|
233
|
+
FROM queen.queues q
|
|
234
|
+
LEFT JOIN queen.partitions p ON p.queue_id = q.id
|
|
235
|
+
LEFT JOIN queen.messages m ON m.partition_id = p.id
|
|
236
|
+
LEFT JOIN queen.messages_status ms ON ms.message_id = m.id AND ms.consumer_group IS NULL
|
|
237
|
+
WHERE q.task IS NOT NULL
|
|
238
|
+
GROUP BY q.task
|
|
239
|
+
ORDER BY q.task
|
|
240
|
+
`);
|
|
241
|
+
|
|
242
|
+
return result.rows.map(row => ({
|
|
243
|
+
task: row.task,
|
|
244
|
+
queues: parseInt(row.queue_count),
|
|
245
|
+
partitions: parseInt(row.partition_count),
|
|
246
|
+
messages: {
|
|
247
|
+
total: parseInt(row.message_count),
|
|
248
|
+
pending: parseInt(row.pending_count)
|
|
249
|
+
}
|
|
250
|
+
}));
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// Get system overview
|
|
254
|
+
const getSystemOverview = async () => {
|
|
255
|
+
const overview = await pool.query(`
|
|
256
|
+
SELECT
|
|
257
|
+
(SELECT COUNT(*) FROM queen.queues) as total_queues,
|
|
258
|
+
(SELECT COUNT(*) FROM queen.partitions) as total_partitions,
|
|
259
|
+
(SELECT COUNT(*) FROM queen.messages) as total_messages,
|
|
260
|
+
(SELECT COUNT(*) FROM queen.messages_status WHERE status = 'pending' AND consumer_group IS NULL) as pending_messages,
|
|
261
|
+
(SELECT COUNT(*) FROM queen.messages_status WHERE status = 'processing' AND consumer_group IS NULL) as processing_messages,
|
|
262
|
+
(SELECT COUNT(*) FROM queen.messages_status WHERE status = 'completed' AND consumer_group IS NULL) as completed_messages,
|
|
263
|
+
(SELECT COUNT(*) FROM queen.messages_status WHERE status = 'failed' AND consumer_group IS NULL) as failed_messages,
|
|
264
|
+
(SELECT COUNT(*) FROM queen.messages_status WHERE status = 'dead_letter' AND consumer_group IS NULL) as dead_letter_messages,
|
|
265
|
+
(SELECT COUNT(DISTINCT namespace) FROM queen.queues WHERE namespace IS NOT NULL) as namespaces,
|
|
266
|
+
(SELECT COUNT(DISTINCT task) FROM queen.queues WHERE task IS NOT NULL) as tasks
|
|
267
|
+
`);
|
|
268
|
+
|
|
269
|
+
const row = overview.rows[0];
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
queues: parseInt(row.total_queues),
|
|
273
|
+
partitions: parseInt(row.total_partitions),
|
|
274
|
+
namespaces: parseInt(row.namespaces),
|
|
275
|
+
tasks: parseInt(row.tasks),
|
|
276
|
+
messages: {
|
|
277
|
+
total: parseInt(row.total_messages),
|
|
278
|
+
pending: parseInt(row.pending_messages),
|
|
279
|
+
processing: parseInt(row.processing_messages),
|
|
280
|
+
completed: parseInt(row.completed_messages),
|
|
281
|
+
failed: parseInt(row.failed_messages),
|
|
282
|
+
deadLetter: parseInt(row.dead_letter_messages)
|
|
283
|
+
},
|
|
284
|
+
timestamp: new Date().toISOString()
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
getQueues,
|
|
290
|
+
getQueue,
|
|
291
|
+
getPartitions,
|
|
292
|
+
getNamespaces,
|
|
293
|
+
getTasks,
|
|
294
|
+
getSystemOverview
|
|
295
|
+
};
|
|
296
|
+
};
|