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,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized logging utility for Queen message queue
|
|
3
|
+
* Logs all state-changing operations with consistent formatting
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Log a message with timestamp and process ID
|
|
8
|
+
* @param {...any} args - Arguments to log (objects will be JSON stringified)
|
|
9
|
+
*/
|
|
10
|
+
export function log(...args) {
|
|
11
|
+
args = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg);
|
|
12
|
+
const msg = args.join(' ');
|
|
13
|
+
console.log(new Date(), `| ${process.pid} | #> ${msg}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Log operation types for consistency
|
|
18
|
+
*/
|
|
19
|
+
export const LogTypes = {
|
|
20
|
+
// Message operations
|
|
21
|
+
PUSH: 'PUSH',
|
|
22
|
+
POP: 'POP',
|
|
23
|
+
POP_FILTERED: 'POP_FILTERED',
|
|
24
|
+
ACK: 'ACK',
|
|
25
|
+
ACK_BATCH: 'ACK_BATCH',
|
|
26
|
+
|
|
27
|
+
// Message state changes
|
|
28
|
+
DELETE: 'DELETE',
|
|
29
|
+
RETRY: 'RETRY',
|
|
30
|
+
RETRY_SCHEDULED: 'RETRY_SCHEDULED',
|
|
31
|
+
MOVED_TO_DLQ: 'MOVED_TO_DLQ',
|
|
32
|
+
MANUAL_DLQ: 'MANUAL_DLQ',
|
|
33
|
+
|
|
34
|
+
// Queue operations
|
|
35
|
+
CLEAR_QUEUE: 'CLEAR_QUEUE',
|
|
36
|
+
CONFIGURE_QUEUE: 'CONFIGURE_QUEUE',
|
|
37
|
+
|
|
38
|
+
// System operations
|
|
39
|
+
EVICTION: 'EVICTION',
|
|
40
|
+
RETENTION: 'RETENTION',
|
|
41
|
+
LEASE_EXPIRED: 'LEASE_EXPIRED'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default log;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { generateUUID } from '../utils/uuid.js';
|
|
2
|
+
import config from '../config.js';
|
|
3
|
+
|
|
4
|
+
export const createWebSocketServer = (app, eventManager) => {
|
|
5
|
+
const connections = new Map();
|
|
6
|
+
|
|
7
|
+
// Broadcast event to all connected clients
|
|
8
|
+
const broadcast = (event, data) => {
|
|
9
|
+
const message = JSON.stringify({ event, data, timestamp: new Date().toISOString() });
|
|
10
|
+
|
|
11
|
+
for (const [id, ws] of connections) {
|
|
12
|
+
try {
|
|
13
|
+
ws.send(message);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(`Failed to send to client ${id}:`, error);
|
|
16
|
+
connections.delete(id);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Setup WebSocket route
|
|
22
|
+
app.ws('/ws/dashboard', {
|
|
23
|
+
compression: config.WEBSOCKET.COMPRESSION,
|
|
24
|
+
maxPayloadLength: config.WEBSOCKET.MAX_PAYLOAD_LENGTH,
|
|
25
|
+
idleTimeout: config.WEBSOCKET.IDLE_TIMEOUT,
|
|
26
|
+
|
|
27
|
+
open: (ws) => {
|
|
28
|
+
const connectionId = generateUUID();
|
|
29
|
+
connections.set(connectionId, ws);
|
|
30
|
+
ws.connectionId = connectionId;
|
|
31
|
+
|
|
32
|
+
console.log(`WebSocket client connected: ${connectionId}`);
|
|
33
|
+
|
|
34
|
+
// Send welcome message
|
|
35
|
+
ws.send(JSON.stringify({
|
|
36
|
+
event: 'connected',
|
|
37
|
+
data: {
|
|
38
|
+
connectionId,
|
|
39
|
+
version: 'v2'
|
|
40
|
+
},
|
|
41
|
+
timestamp: new Date().toISOString()
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
broadcast('client.connected', { clientId: connectionId });
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
message: (ws, message, isBinary) => {
|
|
48
|
+
// Handle ping/pong
|
|
49
|
+
const msg = Buffer.from(message).toString();
|
|
50
|
+
if (msg === 'ping') {
|
|
51
|
+
ws.send('pong');
|
|
52
|
+
} else {
|
|
53
|
+
// Handle subscription requests
|
|
54
|
+
try {
|
|
55
|
+
const data = JSON.parse(msg);
|
|
56
|
+
if (data.type === 'subscribe') {
|
|
57
|
+
// Store subscription preferences (could be used for filtering)
|
|
58
|
+
ws.subscriptions = data.queues || [];
|
|
59
|
+
ws.send(JSON.stringify({
|
|
60
|
+
event: 'subscribed',
|
|
61
|
+
data: { queues: ws.subscriptions },
|
|
62
|
+
timestamp: new Date().toISOString()
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
// Invalid JSON, ignore
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
close: (ws, code, message) => {
|
|
72
|
+
const connectionId = ws.connectionId;
|
|
73
|
+
connections.delete(connectionId);
|
|
74
|
+
console.log(`WebSocket client disconnected: ${connectionId}`);
|
|
75
|
+
broadcast('client.disconnected', { clientId: connectionId });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Subscribe to queue events - Updated for V2 structure
|
|
80
|
+
eventManager.on('message.pushed', (data) => {
|
|
81
|
+
broadcast('message.pushed', {
|
|
82
|
+
queue: data.queue,
|
|
83
|
+
partition: data.partition,
|
|
84
|
+
transactionId: data.transactionId
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
eventManager.on('message.processing', (data) => {
|
|
89
|
+
broadcast('message.processing', {
|
|
90
|
+
queue: data.queue,
|
|
91
|
+
partition: data.partition,
|
|
92
|
+
transactionId: data.transactionId,
|
|
93
|
+
workerId: data.workerId
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
eventManager.on('message.completed', (data) => {
|
|
98
|
+
broadcast('message.completed', {
|
|
99
|
+
transactionId: data.transactionId
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
eventManager.on('message.failed', (data) => {
|
|
104
|
+
broadcast('message.failed', {
|
|
105
|
+
transactionId: data.transactionId,
|
|
106
|
+
error: data.error
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
eventManager.on('queue.created', (data) => {
|
|
111
|
+
broadcast('queue.created', {
|
|
112
|
+
queue: data.queue,
|
|
113
|
+
partition: data.partition ?? 'Default'
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Periodic queue depth updates - Updated for V2
|
|
118
|
+
const updateQueueDepths = async (queueManager) => {
|
|
119
|
+
try {
|
|
120
|
+
const stats = await queueManager.getQueueStats();
|
|
121
|
+
|
|
122
|
+
// Group by queue for aggregated depths
|
|
123
|
+
const queueDepths = {};
|
|
124
|
+
|
|
125
|
+
for (const stat of stats) {
|
|
126
|
+
if (!queueDepths[stat.queue]) {
|
|
127
|
+
queueDepths[stat.queue] = {
|
|
128
|
+
queue: stat.queue,
|
|
129
|
+
namespace: stat.namespace,
|
|
130
|
+
task: stat.task,
|
|
131
|
+
totalDepth: 0,
|
|
132
|
+
totalProcessing: 0,
|
|
133
|
+
partitions: {}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Skip consumer group specific stats for queue depth calculation
|
|
138
|
+
// Only count NULL consumer_group (queue mode) stats
|
|
139
|
+
if (stat.consumer_group) continue;
|
|
140
|
+
|
|
141
|
+
// Initialize partition if not exists
|
|
142
|
+
if (!queueDepths[stat.queue].partitions[stat.partition]) {
|
|
143
|
+
queueDepths[stat.queue].partitions[stat.partition] = {
|
|
144
|
+
depth: 0,
|
|
145
|
+
processing: 0,
|
|
146
|
+
completed: 0,
|
|
147
|
+
failed: 0
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Add partition stats (using direct field names from new schema)
|
|
152
|
+
const partitionStats = queueDepths[stat.queue].partitions[stat.partition];
|
|
153
|
+
partitionStats.depth += parseInt(stat.pending || 0);
|
|
154
|
+
partitionStats.processing += parseInt(stat.processing || 0);
|
|
155
|
+
partitionStats.completed += parseInt(stat.completed || 0);
|
|
156
|
+
partitionStats.failed += parseInt(stat.failed || 0);
|
|
157
|
+
|
|
158
|
+
// Update totals
|
|
159
|
+
queueDepths[stat.queue].totalDepth += parseInt(stat.pending || 0);
|
|
160
|
+
queueDepths[stat.queue].totalProcessing += parseInt(stat.processing || 0);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Broadcast aggregated queue depths
|
|
164
|
+
for (const queueData of Object.values(queueDepths)) {
|
|
165
|
+
broadcast('queue.depth', queueData);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Also broadcast individual partition depths for detailed monitoring
|
|
169
|
+
for (const stat of stats) {
|
|
170
|
+
// Skip consumer group specific stats
|
|
171
|
+
if (stat.consumer_group) continue;
|
|
172
|
+
|
|
173
|
+
broadcast('partition.depth', {
|
|
174
|
+
queue: stat.queue,
|
|
175
|
+
partition: stat.partition,
|
|
176
|
+
depth: parseInt(stat.pending || 0),
|
|
177
|
+
processing: parseInt(stat.processing || 0),
|
|
178
|
+
completed: parseInt(stat.completed || 0),
|
|
179
|
+
failed: parseInt(stat.failed || 0),
|
|
180
|
+
total: parseInt(stat.total_messages || 0)
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error('Error updating queue depths:', error);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Send system stats periodically
|
|
190
|
+
const sendSystemStats = async (pool) => {
|
|
191
|
+
try {
|
|
192
|
+
const result = await pool.query(`
|
|
193
|
+
SELECT
|
|
194
|
+
(SELECT COUNT(*) FROM queen.messages_status WHERE status = 'pending' AND consumer_group IS NULL) as pending,
|
|
195
|
+
(SELECT COUNT(*) FROM queen.messages_status WHERE status = 'processing' AND consumer_group IS NULL) as processing,
|
|
196
|
+
(SELECT COUNT(*) FROM queen.messages WHERE created_at > NOW() - INTERVAL '1 minute') as recent_created,
|
|
197
|
+
(SELECT COUNT(*) FROM queen.messages_status WHERE completed_at > NOW() - INTERVAL '1 minute' AND consumer_group IS NULL) as recent_completed
|
|
198
|
+
`);
|
|
199
|
+
|
|
200
|
+
const stats = result.rows[0];
|
|
201
|
+
|
|
202
|
+
broadcast('system.stats', {
|
|
203
|
+
pending: parseInt(stats.pending || 0),
|
|
204
|
+
processing: parseInt(stats.processing || 0),
|
|
205
|
+
recentCreated: parseInt(stats.recent_created || 0),
|
|
206
|
+
recentCompleted: parseInt(stats.recent_completed || 0),
|
|
207
|
+
connections: connections.size
|
|
208
|
+
});
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error('Error sending system stats:', error);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
broadcast,
|
|
216
|
+
updateQueueDepths,
|
|
217
|
+
sendSystemStats,
|
|
218
|
+
getConnectionCount: () => connections.size,
|
|
219
|
+
getConnections: () => Array.from(connections.keys())
|
|
220
|
+
};
|
|
221
|
+
};
|