queen-mq 0.2.0 → 0.2.11
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 +165 -769
- package/{src → client-js}/benchmark/consumer.js +5 -3
- package/{src → client-js}/benchmark/producer.js +9 -4
- package/{src → client-js}/client/client.js +272 -6
- package/{src → client-js}/client/index.js +1 -4
- package/{src → client-js}/client/utils/http.js +3 -2
- package/{src → client-js}/client/utils/retry.js +7 -1
- package/{src → client-js}/test/core-tests.js +7 -2
- package/{src → client-js}/test/edge-case-tests.js +2 -1
- package/client-js/test/human.js +162 -0
- package/client-js/test/qos0-tests.js +334 -0
- package/{src → client-js}/test/test-new.js +42 -0
- package/package.json +8 -17
- package/init-db.js +0 -20
- package/src/cluster-server.js +0 -242
- package/src/config.js +0 -229
- package/src/database/connection.js +0 -129
- package/src/database/poolManager.js +0 -199
- package/src/database/schema-v2.sql +0 -278
- package/src/managers/eventManager.js +0 -59
- package/src/managers/queueManagerOptimized.js +0 -1634
- package/src/managers/resourceCache.js +0 -96
- package/src/managers/systemEventManager.js +0 -132
- package/src/routes/ack.js +0 -26
- package/src/routes/configure.js +0 -46
- package/src/routes/messages.js +0 -368
- package/src/routes/pop.js +0 -69
- package/src/routes/push.js +0 -28
- package/src/routes/resources.js +0 -330
- package/src/routes/status.js +0 -1037
- package/src/server.js +0 -1633
- package/src/test/MIGRATION_ISSUES.md +0 -174
- package/src/test/test.js +0 -4524
- package/src/utils/streaming.js +0 -231
- package/src/webapp-dist/assets/Analytics-DJrE4Rz2.css +0 -1
- package/src/webapp-dist/assets/Analytics-HEMXKmfJ.js +0 -1
- package/src/webapp-dist/assets/ConfirmDialog-YEoCdE7x.js +0 -1
- package/src/webapp-dist/assets/ConsumerGroups-CC5VJywp.css +0 -1
- package/src/webapp-dist/assets/ConsumerGroups-D8Z20HhZ.js +0 -1
- package/src/webapp-dist/assets/Dashboard-BRFOz6it.css +0 -1
- package/src/webapp-dist/assets/Dashboard-Bpx9I32h.js +0 -1
- package/src/webapp-dist/assets/LoadingSpinner-BPttXcbs.js +0 -1
- package/src/webapp-dist/assets/Messages-BOfVHKma.css +0 -1
- package/src/webapp-dist/assets/Messages-EKURCbLl.js +0 -1
- package/src/webapp-dist/assets/QueueDetail-BXXA7xoZ.css +0 -1
- package/src/webapp-dist/assets/QueueDetail-Bmxg0KiA.js +0 -1
- package/src/webapp-dist/assets/Queues-CvrJkrZT.css +0 -1
- package/src/webapp-dist/assets/Queues-yUx9_3-9.js +0 -1
- package/src/webapp-dist/assets/StatusBadge-C1bBtXHh.css +0 -1
- package/src/webapp-dist/assets/StatusBadge-C6N84XmO.js +0 -1
- package/src/webapp-dist/assets/analytics-BapTekLM.js +0 -1
- package/src/webapp-dist/assets/colors-5z6Q_kUr.js +0 -18
- package/src/webapp-dist/assets/index-B7w2bKT6.css +0 -1
- package/src/webapp-dist/assets/index-CUSnwYTH.js +0 -31
- package/src/webapp-dist/assets/messages-CRV6E-xU.js +0 -1
- package/src/webapp-dist/assets/queen-logo-blue.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-cyan.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-indigo.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-orange.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-pink.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-purple.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-rose.svg +0 -239
- package/src/webapp-dist/assets/queen-logo.svg +0 -263
- package/src/webapp-dist/assets/queues-BdUbxfBC.js +0 -1
- package/src/webapp-dist/assets/resources-Bs9U-ceF.js +0 -1
- package/src/webapp-dist/index.html +0 -15
- package/src/websocket/wsServer.js +0 -228
- /package/{src → client-js}/benchmark/consumer_multi.js +0 -0
- /package/{src → client-js}/benchmark/producer_multi.js +0 -0
- /package/{src → client-js}/client/utils/loadBalancer.js +0 -0
- /package/{src → client-js}/services/encryptionService.js +0 -0
- /package/{src → client-js}/services/evictionService.js +0 -0
- /package/{src → client-js}/services/retentionService.js +0 -0
- /package/{src → client-js}/services/startupSync.js +0 -0
- /package/{src → client-js}/test/README.md +0 -0
- /package/{src → client-js}/test/advanced-client-tests.js +0 -0
- /package/{src → client-js}/test/advanced-pattern-tests.js +0 -0
- /package/{src → client-js}/test/bus-mode-tests.js +0 -0
- /package/{src → client-js}/test/enterprise-tests.js +0 -0
- /package/{src → client-js}/test/partition-locking-tests.js +0 -0
- /package/{src → client-js}/test/utils.js +0 -0
- /package/{src → client-js}/test/window-buffer-test.js +0 -0
- /package/{src → client-js}/utils/logger.js +0 -0
- /package/{src → client-js}/utils/uuid.js +0 -0
package/src/cluster-server.js
DELETED
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Queen Message Queue - Clustered Server
|
|
5
|
-
*
|
|
6
|
-
* This module enables multi-core utilization using Node.js cluster module.
|
|
7
|
-
* Each worker runs an independent instance of the Queen server, sharing the same port.
|
|
8
|
-
*
|
|
9
|
-
* Benefits:
|
|
10
|
-
* - 10x throughput improvement (utilizes all CPU cores)
|
|
11
|
-
* - Automatic load balancing across workers
|
|
12
|
-
* - Fault tolerance (automatic worker restart on crash)
|
|
13
|
-
* - Zero-downtime deploys (rolling worker restarts)
|
|
14
|
-
*
|
|
15
|
-
* Usage:
|
|
16
|
-
* node src/cluster-server.js
|
|
17
|
-
*
|
|
18
|
-
* Environment Variables:
|
|
19
|
-
* QUEEN_WORKERS=N - Number of workers (default: CPU count)
|
|
20
|
-
* WORKER_ID=name - Base worker ID prefix (default: queen-worker)
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import cluster from 'cluster';
|
|
24
|
-
import os from 'os';
|
|
25
|
-
import { log } from './utils/logger.js';
|
|
26
|
-
|
|
27
|
-
// Configuration
|
|
28
|
-
const numWorkers = parseInt(process.env.QUEEN_WORKERS) || os.cpus().length;
|
|
29
|
-
const workerIdPrefix = process.env.WORKER_ID || 'queen-worker';
|
|
30
|
-
|
|
31
|
-
if (cluster.isPrimary) {
|
|
32
|
-
console.log('');
|
|
33
|
-
console.log('═══════════════════════════════════════════════════════════');
|
|
34
|
-
console.log('🚀 Queen Message Queue - Clustered Mode');
|
|
35
|
-
console.log('═══════════════════════════════════════════════════════════');
|
|
36
|
-
console.log(`📊 System: ${os.cpus()[0].model}`);
|
|
37
|
-
console.log(`🔢 CPU Cores: ${os.cpus().length}`);
|
|
38
|
-
console.log(`👷 Starting Workers: ${numWorkers}`);
|
|
39
|
-
console.log(`🆔 Worker ID Prefix: ${workerIdPrefix}`);
|
|
40
|
-
console.log('═══════════════════════════════════════════════════════════');
|
|
41
|
-
console.log('');
|
|
42
|
-
|
|
43
|
-
// Track worker statistics
|
|
44
|
-
const workerStats = {
|
|
45
|
-
startTime: Date.now(),
|
|
46
|
-
totalRestarts: 0,
|
|
47
|
-
workers: new Map()
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// Graceful shutdown state (declared early for use in event handlers)
|
|
51
|
-
let isShuttingDown = false;
|
|
52
|
-
let forceExitTimeout = null;
|
|
53
|
-
let statusInterval = null;
|
|
54
|
-
|
|
55
|
-
// Fork workers
|
|
56
|
-
for (let i = 0; i < numWorkers; i++) {
|
|
57
|
-
forkWorker(i, workerStats);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Handle worker exit
|
|
61
|
-
cluster.on('exit', (worker, code, signal) => {
|
|
62
|
-
const workerInfo = workerStats.workers.get(worker.id);
|
|
63
|
-
const workerId = workerInfo ? workerInfo.workerId : `worker-${worker.id}`;
|
|
64
|
-
const workerIndex = workerInfo ? workerInfo.index : 0;
|
|
65
|
-
|
|
66
|
-
if (code !== 0 && !worker.exitedAfterDisconnect) {
|
|
67
|
-
// Unexpected crash
|
|
68
|
-
console.log('');
|
|
69
|
-
console.log(`⚠️ Worker ${workerId} (PID: ${worker.process.pid}) crashed!`);
|
|
70
|
-
console.log(` Exit code: ${code}, Signal: ${signal}`);
|
|
71
|
-
console.log(` Restarting worker...`);
|
|
72
|
-
|
|
73
|
-
workerStats.totalRestarts++;
|
|
74
|
-
|
|
75
|
-
// Remove from stats
|
|
76
|
-
workerStats.workers.delete(worker.id);
|
|
77
|
-
|
|
78
|
-
// Restart with same index
|
|
79
|
-
setTimeout(() => {
|
|
80
|
-
forkWorker(workerIndex, workerStats);
|
|
81
|
-
}, 1000); // Wait 1 second before restart
|
|
82
|
-
} else {
|
|
83
|
-
// Graceful shutdown
|
|
84
|
-
console.log(`✅ Worker ${workerId} (PID: ${worker.process.pid}) exited gracefully`);
|
|
85
|
-
workerStats.workers.delete(worker.id);
|
|
86
|
-
|
|
87
|
-
// Check if all workers have exited during shutdown
|
|
88
|
-
if (isShuttingDown && Object.keys(cluster.workers).length === 0) {
|
|
89
|
-
console.log('✅ All workers exited, shutting down master process');
|
|
90
|
-
clearInterval(statusInterval);
|
|
91
|
-
clearTimeout(forceExitTimeout);
|
|
92
|
-
process.exit(0);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// Handle worker online
|
|
98
|
-
cluster.on('online', (worker) => {
|
|
99
|
-
const workerInfo = workerStats.workers.get(worker.id);
|
|
100
|
-
if (workerInfo) {
|
|
101
|
-
// Update PID now that it's available
|
|
102
|
-
workerInfo.pid = worker.process.pid;
|
|
103
|
-
console.log(`✅ Worker ${workerInfo.workerId} (PID: ${worker.process.pid}) online and ready`);
|
|
104
|
-
} else {
|
|
105
|
-
console.log(`✅ Worker ${worker.id} (PID: ${worker.process.pid}) online`);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// Handle worker listening
|
|
110
|
-
cluster.on('listening', (worker, address) => {
|
|
111
|
-
const workerInfo = workerStats.workers.get(worker.id);
|
|
112
|
-
const workerId = workerInfo ? workerInfo.workerId : `worker-${worker.id}`;
|
|
113
|
-
console.log(`🎧 Worker ${workerId} listening on ${address.address}:${address.port}`);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// Handle cluster errors
|
|
117
|
-
cluster.on('error', (error) => {
|
|
118
|
-
console.error('⚠️ Cluster error:', error.message);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// Handle uncaught exceptions in master
|
|
122
|
-
process.on('uncaughtException', (error) => {
|
|
123
|
-
console.error('❌ Uncaught exception in master process:', error);
|
|
124
|
-
console.error(error.stack);
|
|
125
|
-
// Don't exit - try to keep the cluster running
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
process.on('unhandledRejection', (reason, promise) => {
|
|
129
|
-
console.error('❌ Unhandled rejection in master process:', reason);
|
|
130
|
-
// Don't exit - try to keep the cluster running
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// Graceful shutdown handling
|
|
134
|
-
const gracefulShutdown = (signal) => {
|
|
135
|
-
if (isShuttingDown) return;
|
|
136
|
-
isShuttingDown = true;
|
|
137
|
-
|
|
138
|
-
console.log('');
|
|
139
|
-
console.log(`🛑 Received ${signal}, shutting down gracefully...`);
|
|
140
|
-
console.log(`📊 Total worker restarts during uptime: ${workerStats.totalRestarts}`);
|
|
141
|
-
console.log(`⏱️ Uptime: ${((Date.now() - workerStats.startTime) / 1000 / 60).toFixed(2)} minutes`);
|
|
142
|
-
|
|
143
|
-
// Stop status reporting during shutdown
|
|
144
|
-
clearInterval(statusInterval);
|
|
145
|
-
|
|
146
|
-
// Check if we already have no workers
|
|
147
|
-
if (Object.keys(cluster.workers).length === 0) {
|
|
148
|
-
console.log('✅ No active workers, exiting immediately');
|
|
149
|
-
process.exit(0);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Disconnect all workers
|
|
153
|
-
for (const id in cluster.workers) {
|
|
154
|
-
cluster.workers[id].disconnect();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Force exit if workers don't shut down in 10 seconds (reduced from 30)
|
|
158
|
-
forceExitTimeout = setTimeout(() => {
|
|
159
|
-
console.log('⚠️ Forcing shutdown after 10 second timeout');
|
|
160
|
-
console.log(` Remaining workers: ${Object.keys(cluster.workers).length}`);
|
|
161
|
-
process.exit(0);
|
|
162
|
-
}, 10000);
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
166
|
-
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
167
|
-
|
|
168
|
-
// Status reporting every 60 seconds
|
|
169
|
-
statusInterval = setInterval(() => {
|
|
170
|
-
// Don't show status during shutdown
|
|
171
|
-
if (isShuttingDown) return;
|
|
172
|
-
|
|
173
|
-
const aliveWorkers = Object.keys(cluster.workers).length;
|
|
174
|
-
const uptime = ((Date.now() - workerStats.startTime) / 1000 / 60).toFixed(2);
|
|
175
|
-
|
|
176
|
-
console.log('');
|
|
177
|
-
console.log(`📊 Cluster Status (${new Date().toISOString()})`);
|
|
178
|
-
console.log(` Active Workers: ${aliveWorkers}/${numWorkers}`);
|
|
179
|
-
console.log(` Total Restarts: ${workerStats.totalRestarts}`);
|
|
180
|
-
console.log(` Uptime: ${uptime} minutes`);
|
|
181
|
-
}, 60000);
|
|
182
|
-
|
|
183
|
-
} else {
|
|
184
|
-
// Worker process - run the normal server
|
|
185
|
-
const workerId = process.env.WORKER_ID || `worker-${process.pid}`;
|
|
186
|
-
const workerIndex = process.env.WORKER_INDEX || '0';
|
|
187
|
-
|
|
188
|
-
console.log(`👷 Worker ${workerId} (index: ${workerIndex}, PID: ${process.pid}) starting...`);
|
|
189
|
-
|
|
190
|
-
try {
|
|
191
|
-
// Import and run the main server
|
|
192
|
-
await import('./server.js');
|
|
193
|
-
|
|
194
|
-
// Worker-specific metrics could be added here
|
|
195
|
-
// e.g., track messages processed, errors, etc.
|
|
196
|
-
} catch (error) {
|
|
197
|
-
console.error(`❌ Worker ${workerId} failed to start:`, error.message);
|
|
198
|
-
console.error(error.stack);
|
|
199
|
-
process.exit(1);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Handle uncaught exceptions in worker
|
|
203
|
-
process.on('uncaughtException', (error) => {
|
|
204
|
-
console.error(`❌ Uncaught exception in worker ${workerId}:`, error);
|
|
205
|
-
process.exit(1);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
process.on('unhandledRejection', (reason, promise) => {
|
|
209
|
-
console.error(`❌ Unhandled rejection in worker ${workerId}:`, reason);
|
|
210
|
-
process.exit(1);
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Fork a new worker with proper environment setup
|
|
216
|
-
*/
|
|
217
|
-
function forkWorker(index, stats) {
|
|
218
|
-
const workerId = `${workerIdPrefix}-${index}`;
|
|
219
|
-
|
|
220
|
-
try {
|
|
221
|
-
const worker = cluster.fork({
|
|
222
|
-
WORKER_ID: workerId,
|
|
223
|
-
WORKER_INDEX: index.toString(),
|
|
224
|
-
// Each worker gets a unique ID for system events
|
|
225
|
-
SYSTEM_WORKER_ID: `${workerIdPrefix}-${index}-${Date.now()}`
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// Store worker info immediately (PID will be updated in 'online' event)
|
|
229
|
-
stats.workers.set(worker.id, {
|
|
230
|
-
workerId,
|
|
231
|
-
index,
|
|
232
|
-
startTime: Date.now(),
|
|
233
|
-
pid: null // Will be set when worker comes online
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
return worker;
|
|
237
|
-
} catch (error) {
|
|
238
|
-
console.error(`❌ Failed to fork worker ${workerId}:`, error.message);
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
package/src/config.js
DELETED
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centralized Configuration for Queen Message Queue System
|
|
3
|
-
*
|
|
4
|
-
* All configuration values can be overridden using environment variables.
|
|
5
|
-
* This file provides default values and documentation for all settings.
|
|
6
|
-
*/
|
|
7
|
-
import os from 'os';
|
|
8
|
-
|
|
9
|
-
// Server Configuration
|
|
10
|
-
export const SERVER = {
|
|
11
|
-
PORT: parseInt(process.env.PORT) || 6632,
|
|
12
|
-
HOST: process.env.HOST || '0.0.0.0',
|
|
13
|
-
WORKER_ID: process.env.WORKER_ID || `worker-${os.hostname()}-${process.pid}`,
|
|
14
|
-
APPLICATION_NAME: process.env.APP_NAME || 'queen-mq'
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// Database Configuration
|
|
18
|
-
export const DATABASE = {
|
|
19
|
-
// Connection settings
|
|
20
|
-
USER: process.env.PG_USER || 'postgres',
|
|
21
|
-
HOST: process.env.PG_HOST || 'localhost',
|
|
22
|
-
DATABASE: process.env.PG_DB || 'postgres',
|
|
23
|
-
PASSWORD: process.env.PG_PASSWORD || 'postgres',
|
|
24
|
-
PORT: parseInt(process.env.PG_PORT) || 5432,
|
|
25
|
-
|
|
26
|
-
// SSL configuration
|
|
27
|
-
USE_SSL: process.env.PG_USE_SSL === 'true',
|
|
28
|
-
SSL_REJECT_UNAUTHORIZED: process.env.PG_SSL_REJECT_UNAUTHORIZED !== 'false',
|
|
29
|
-
|
|
30
|
-
// Pool configuration
|
|
31
|
-
POOL_SIZE: parseInt(process.env.DB_POOL_SIZE) || 150,
|
|
32
|
-
IDLE_TIMEOUT: parseInt(process.env.DB_IDLE_TIMEOUT) || 30000, // 30 seconds
|
|
33
|
-
CONNECTION_TIMEOUT: parseInt(process.env.DB_CONNECTION_TIMEOUT) || 2000, // 2 seconds
|
|
34
|
-
STATEMENT_TIMEOUT: parseInt(process.env.DB_STATEMENT_TIMEOUT) || 30000, // 30 seconds
|
|
35
|
-
QUERY_TIMEOUT: parseInt(process.env.DB_QUERY_TIMEOUT) || 30000, // 30 seconds
|
|
36
|
-
LOCK_TIMEOUT: parseInt(process.env.DB_LOCK_TIMEOUT) || 10000, // 10 seconds
|
|
37
|
-
|
|
38
|
-
// Pool manager settings
|
|
39
|
-
MAX_RETRIES: parseInt(process.env.DB_MAX_RETRIES) || 3
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// Queue Processing Configuration
|
|
43
|
-
export const QUEUE = {
|
|
44
|
-
// Pop operation defaults
|
|
45
|
-
DEFAULT_TIMEOUT: parseInt(process.env.DEFAULT_TIMEOUT) || 30000, // 30 seconds
|
|
46
|
-
MAX_TIMEOUT: parseInt(process.env.MAX_TIMEOUT) || 60000, // 60 seconds
|
|
47
|
-
DEFAULT_BATCH_SIZE: parseInt(process.env.DEFAULT_BATCH_SIZE) || 1,
|
|
48
|
-
BATCH_INSERT_SIZE: parseInt(process.env.BATCH_INSERT_SIZE) || 1000,
|
|
49
|
-
|
|
50
|
-
// Long polling
|
|
51
|
-
POLL_INTERVAL: parseInt(process.env.QUEUE_POLL_INTERVAL) || 100, // 100ms - initial poll interval
|
|
52
|
-
POLL_INTERVAL_FILTERED: parseInt(process.env.QUEUE_POLL_INTERVAL_FILTERED) || 50, // 50ms for better distribution across consumers
|
|
53
|
-
MAX_POLL_INTERVAL: parseInt(process.env.QUEUE_MAX_POLL_INTERVAL) || 2000, // 2000ms - max poll interval after backoff
|
|
54
|
-
BACKOFF_THRESHOLD: parseInt(process.env.QUEUE_BACKOFF_THRESHOLD) || 5, // Number of empty polls before backoff starts
|
|
55
|
-
BACKOFF_MULTIPLIER: parseFloat(process.env.QUEUE_BACKOFF_MULTIPLIER) || 2, // Exponential backoff multiplier
|
|
56
|
-
|
|
57
|
-
// Partition selection for filtered pops (namespace/task)
|
|
58
|
-
MAX_PARTITION_CANDIDATES: parseInt(process.env.MAX_PARTITION_CANDIDATES) || 100, // Number of candidate partitions to fetch for lease acquisition
|
|
59
|
-
|
|
60
|
-
// Queue defaults
|
|
61
|
-
DEFAULT_LEASE_TIME: parseInt(process.env.DEFAULT_LEASE_TIME) || 300, // 5 minutes
|
|
62
|
-
DEFAULT_RETRY_LIMIT: parseInt(process.env.DEFAULT_RETRY_LIMIT) || 3,
|
|
63
|
-
DEFAULT_RETRY_DELAY: parseInt(process.env.DEFAULT_RETRY_DELAY) || 1000, // 1 second
|
|
64
|
-
DEFAULT_MAX_SIZE: parseInt(process.env.DEFAULT_MAX_SIZE) || 10000,
|
|
65
|
-
DEFAULT_TTL: parseInt(process.env.DEFAULT_TTL) || 3600, // 1 hour
|
|
66
|
-
DEFAULT_PRIORITY: parseInt(process.env.DEFAULT_PRIORITY) || 0,
|
|
67
|
-
DEFAULT_DELAYED_PROCESSING: parseInt(process.env.DEFAULT_DELAYED_PROCESSING) || 0,
|
|
68
|
-
DEFAULT_WINDOW_BUFFER: parseInt(process.env.DEFAULT_WINDOW_BUFFER) || 0,
|
|
69
|
-
|
|
70
|
-
// Dead Letter Queue
|
|
71
|
-
DEFAULT_DLQ_ENABLED: process.env.DEFAULT_DLQ_ENABLED === 'true' || false,
|
|
72
|
-
DEFAULT_DLQ_AFTER_MAX_RETRIES: process.env.DEFAULT_DLQ_AFTER_MAX_RETRIES === 'true' || false,
|
|
73
|
-
|
|
74
|
-
// Retention defaults
|
|
75
|
-
DEFAULT_RETENTION_SECONDS: parseInt(process.env.DEFAULT_RETENTION_SECONDS) || 0,
|
|
76
|
-
DEFAULT_COMPLETED_RETENTION_SECONDS: parseInt(process.env.DEFAULT_COMPLETED_RETENTION_SECONDS) || 0,
|
|
77
|
-
DEFAULT_RETENTION_ENABLED: process.env.DEFAULT_RETENTION_ENABLED === 'true' || false,
|
|
78
|
-
|
|
79
|
-
// Eviction
|
|
80
|
-
DEFAULT_MAX_WAIT_TIME_SECONDS: parseInt(process.env.DEFAULT_MAX_WAIT_TIME_SECONDS) || 0
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
// System Events Configuration
|
|
84
|
-
export const SYSTEM_EVENTS = {
|
|
85
|
-
// Enable/disable system event propagation
|
|
86
|
-
ENABLED: process.env.QUEEN_SYSTEM_EVENTS_ENABLED === 'true' || false,
|
|
87
|
-
|
|
88
|
-
// Batching window for event publishing (milliseconds)
|
|
89
|
-
BATCH_MS: parseInt(process.env.QUEEN_SYSTEM_EVENTS_BATCH_MS) || 10,
|
|
90
|
-
|
|
91
|
-
// Timeout for startup synchronization (milliseconds)
|
|
92
|
-
SYNC_TIMEOUT: parseInt(process.env.QUEEN_SYSTEM_EVENTS_SYNC_TIMEOUT) || 30000
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// Background Jobs Configuration
|
|
96
|
-
export const JOBS = {
|
|
97
|
-
// Lease reclamation
|
|
98
|
-
LEASE_RECLAIM_INTERVAL: parseInt(process.env.LEASE_RECLAIM_INTERVAL) || 5000, // 5 seconds
|
|
99
|
-
|
|
100
|
-
// Retention service
|
|
101
|
-
RETENTION_INTERVAL: parseInt(process.env.RETENTION_INTERVAL) || 300000, // 5 minutes
|
|
102
|
-
RETENTION_BATCH_SIZE: parseInt(process.env.RETENTION_BATCH_SIZE) || 1000,
|
|
103
|
-
PARTITION_CLEANUP_DAYS: parseInt(process.env.PARTITION_CLEANUP_DAYS) || 7,
|
|
104
|
-
|
|
105
|
-
// Metrics retention (messages_consumed table)
|
|
106
|
-
METRICS_RETENTION_DAYS: parseInt(process.env.METRICS_RETENTION_DAYS) || 90, // Keep 90 days of metrics by default
|
|
107
|
-
|
|
108
|
-
// Eviction service
|
|
109
|
-
EVICTION_INTERVAL: parseInt(process.env.EVICTION_INTERVAL) || 60000, // 1 minute
|
|
110
|
-
EVICTION_BATCH_SIZE: parseInt(process.env.EVICTION_BATCH_SIZE) || 1000,
|
|
111
|
-
|
|
112
|
-
// WebSocket updates
|
|
113
|
-
QUEUE_DEPTH_UPDATE_INTERVAL: parseInt(process.env.QUEUE_DEPTH_UPDATE_INTERVAL) || 5000, // 5 seconds
|
|
114
|
-
SYSTEM_STATS_UPDATE_INTERVAL: parseInt(process.env.SYSTEM_STATS_UPDATE_INTERVAL) || 10000 // 10 seconds
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// WebSocket Configuration
|
|
118
|
-
export const WEBSOCKET = {
|
|
119
|
-
COMPRESSION: parseInt(process.env.WS_COMPRESSION) || 0,
|
|
120
|
-
MAX_PAYLOAD_LENGTH: parseInt(process.env.WS_MAX_PAYLOAD_LENGTH) || 16384, // 16KB
|
|
121
|
-
IDLE_TIMEOUT: parseInt(process.env.WS_IDLE_TIMEOUT) || 60, // 60 seconds
|
|
122
|
-
MAX_CONNECTIONS: parseInt(process.env.WS_MAX_CONNECTIONS) || 1000,
|
|
123
|
-
HEARTBEAT_INTERVAL: parseInt(process.env.WS_HEARTBEAT_INTERVAL) || 30000 // 30 seconds
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
// Encryption Configuration
|
|
127
|
-
export const ENCRYPTION = {
|
|
128
|
-
KEY_ENV_VAR: 'QUEEN_ENCRYPTION_KEY',
|
|
129
|
-
ALGORITHM: 'aes-256-gcm',
|
|
130
|
-
KEY_LENGTH: 32, // bytes
|
|
131
|
-
IV_LENGTH: 16 // bytes
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
// Client SDK Configuration
|
|
135
|
-
export const CLIENT = {
|
|
136
|
-
DEFAULT_BASE_URL: process.env.QUEEN_BASE_URL || 'http://localhost:6632',
|
|
137
|
-
DEFAULT_RETRY_ATTEMPTS: parseInt(process.env.CLIENT_RETRY_ATTEMPTS) || 3,
|
|
138
|
-
DEFAULT_RETRY_DELAY: parseInt(process.env.CLIENT_RETRY_DELAY) || 1000, // 1 second
|
|
139
|
-
DEFAULT_RETRY_BACKOFF: parseFloat(process.env.CLIENT_RETRY_BACKOFF) || 2,
|
|
140
|
-
CONNECTION_POOL_SIZE: parseInt(process.env.CLIENT_POOL_SIZE) || 10,
|
|
141
|
-
REQUEST_TIMEOUT: parseInt(process.env.CLIENT_REQUEST_TIMEOUT) || 30000 // 30 seconds
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
// API Configuration
|
|
145
|
-
export const API = {
|
|
146
|
-
MAX_BODY_SIZE: parseInt(process.env.MAX_BODY_SIZE) || 100 * 1024 * 1024, // 100MB max body size
|
|
147
|
-
// Pagination
|
|
148
|
-
DEFAULT_LIMIT: parseInt(process.env.API_DEFAULT_LIMIT) || 100,
|
|
149
|
-
MAX_LIMIT: parseInt(process.env.API_MAX_LIMIT) || 1000,
|
|
150
|
-
DEFAULT_OFFSET: parseInt(process.env.API_DEFAULT_OFFSET) || 0,
|
|
151
|
-
|
|
152
|
-
// CORS
|
|
153
|
-
CORS_MAX_AGE: parseInt(process.env.CORS_MAX_AGE) || 86400, // 24 hours
|
|
154
|
-
CORS_ALLOWED_ORIGINS: process.env.CORS_ALLOWED_ORIGINS || '*',
|
|
155
|
-
CORS_ALLOWED_METHODS: process.env.CORS_ALLOWED_METHODS || 'GET, POST, PUT, DELETE, OPTIONS',
|
|
156
|
-
CORS_ALLOWED_HEADERS: process.env.CORS_ALLOWED_HEADERS || 'Content-Type, Authorization'
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
// Analytics Configuration
|
|
160
|
-
export const ANALYTICS = {
|
|
161
|
-
// Processing time calculations
|
|
162
|
-
RECENT_COMPLETION_HOURS: parseInt(process.env.ANALYTICS_RECENT_HOURS) || 24,
|
|
163
|
-
MIN_COMPLETED_FOR_STATS: parseInt(process.env.ANALYTICS_MIN_COMPLETED) || 5,
|
|
164
|
-
|
|
165
|
-
// Time windows
|
|
166
|
-
RECENT_MESSAGE_WINDOW: parseInt(process.env.RECENT_MESSAGE_WINDOW) || 60, // 1 minute in seconds
|
|
167
|
-
RELATED_MESSAGE_WINDOW: parseInt(process.env.RELATED_MESSAGE_WINDOW) || 3600, // 1 hour in seconds
|
|
168
|
-
MAX_RELATED_MESSAGES: parseInt(process.env.MAX_RELATED_MESSAGES) || 10
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
// Performance Monitoring
|
|
172
|
-
export const MONITORING = {
|
|
173
|
-
ENABLE_REQUEST_COUNTING: process.env.ENABLE_REQUEST_COUNTING !== 'false',
|
|
174
|
-
ENABLE_MESSAGE_COUNTING: process.env.ENABLE_MESSAGE_COUNTING !== 'false',
|
|
175
|
-
METRICS_ENDPOINT_ENABLED: process.env.METRICS_ENDPOINT_ENABLED !== 'false',
|
|
176
|
-
HEALTH_CHECK_ENABLED: process.env.HEALTH_CHECK_ENABLED !== 'false'
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// Logging Configuration
|
|
180
|
-
export const LOGGING = {
|
|
181
|
-
ENABLE_LOGGING: process.env.ENABLE_LOGGING !== 'false',
|
|
182
|
-
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
|
183
|
-
LOG_FORMAT: process.env.LOG_FORMAT || 'json',
|
|
184
|
-
LOG_TIMESTAMP: process.env.LOG_TIMESTAMP !== 'false'
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
// HTTP Response Status Codes
|
|
188
|
-
export const HTTP_STATUS = {
|
|
189
|
-
OK: 200,
|
|
190
|
-
CREATED: 201,
|
|
191
|
-
NO_CONTENT: 204,
|
|
192
|
-
BAD_REQUEST: 400,
|
|
193
|
-
NOT_FOUND: 404,
|
|
194
|
-
INTERNAL_SERVER_ERROR: 500,
|
|
195
|
-
SERVICE_UNAVAILABLE: 503
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
// Export a function to get the complete configuration
|
|
199
|
-
export const getConfig = () => ({
|
|
200
|
-
server: SERVER,
|
|
201
|
-
database: DATABASE,
|
|
202
|
-
queue: QUEUE,
|
|
203
|
-
jobs: JOBS,
|
|
204
|
-
websocket: WEBSOCKET,
|
|
205
|
-
encryption: ENCRYPTION,
|
|
206
|
-
client: CLIENT,
|
|
207
|
-
api: API,
|
|
208
|
-
analytics: ANALYTICS,
|
|
209
|
-
monitoring: MONITORING,
|
|
210
|
-
logging: LOGGING,
|
|
211
|
-
httpStatus: HTTP_STATUS
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Export default configuration object
|
|
215
|
-
export default {
|
|
216
|
-
SERVER,
|
|
217
|
-
DATABASE,
|
|
218
|
-
QUEUE,
|
|
219
|
-
SYSTEM_EVENTS,
|
|
220
|
-
JOBS,
|
|
221
|
-
WEBSOCKET,
|
|
222
|
-
ENCRYPTION,
|
|
223
|
-
CLIENT,
|
|
224
|
-
API,
|
|
225
|
-
ANALYTICS,
|
|
226
|
-
MONITORING,
|
|
227
|
-
LOGGING,
|
|
228
|
-
HTTP_STATUS
|
|
229
|
-
};
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import pg from 'pg';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import config from '../config.js';
|
|
6
|
-
|
|
7
|
-
const { Pool } = pg;
|
|
8
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Create a PostgreSQL connection pool with proper configuration
|
|
12
|
-
* This is the single source of truth for pool creation across the application
|
|
13
|
-
*/
|
|
14
|
-
export const createPool = () => {
|
|
15
|
-
const poolConfig = {
|
|
16
|
-
user: config.DATABASE.USER,
|
|
17
|
-
host: config.DATABASE.HOST,
|
|
18
|
-
database: config.DATABASE.DATABASE,
|
|
19
|
-
password: config.DATABASE.PASSWORD,
|
|
20
|
-
port: config.DATABASE.PORT,
|
|
21
|
-
max: config.DATABASE.POOL_SIZE,
|
|
22
|
-
idleTimeoutMillis: config.DATABASE.IDLE_TIMEOUT,
|
|
23
|
-
connectionTimeoutMillis: config.DATABASE.CONNECTION_TIMEOUT,
|
|
24
|
-
// Note: statement_timeout and query_timeout cannot be set as startup parameters
|
|
25
|
-
// They should be set per-query or per-transaction using SET commands
|
|
26
|
-
application_name: config.SERVER.APPLICATION_NAME,
|
|
27
|
-
// PERFORMANCE NOTE: TCP_NODELAY for latency reduction
|
|
28
|
-
// The node-postgres driver enables TCP_NODELAY by default on sockets
|
|
29
|
-
// For server-side: Edit postgresql.conf and add: tcp_nodelay = on
|
|
30
|
-
// Reference: https://stackoverflow.com/questions/60634455/how-does-one-configure-tcp-nodelay-for-libpq-and-postgres-server
|
|
31
|
-
keepAlive: true,
|
|
32
|
-
keepAliveInitialDelayMillis: 10000
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// Add SSL configuration if enabled
|
|
36
|
-
if (config.DATABASE.USE_SSL) {
|
|
37
|
-
poolConfig.ssl = {
|
|
38
|
-
rejectUnauthorized: config.DATABASE.SSL_REJECT_UNAUTHORIZED
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return new Pool(poolConfig);
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// Initialize database schema
|
|
46
|
-
export const initDatabase = async (pool) => {
|
|
47
|
-
try {
|
|
48
|
-
// Check if the queen schema already exists
|
|
49
|
-
const schemaCheck = await pool.query(`
|
|
50
|
-
SELECT schema_name
|
|
51
|
-
FROM information_schema.schemata
|
|
52
|
-
WHERE schema_name = 'queen'
|
|
53
|
-
`);
|
|
54
|
-
|
|
55
|
-
if (schemaCheck.rows.length > 0) {
|
|
56
|
-
console.log('Database schema already exists, skipping initialization');
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Schema doesn't exist, create it
|
|
61
|
-
const schemaPath = path.join(__dirname, 'schema-v2.sql');
|
|
62
|
-
const schema = fs.readFileSync(schemaPath, 'utf8');
|
|
63
|
-
|
|
64
|
-
await pool.query(schema);
|
|
65
|
-
console.log('Database schema V2 initialized');
|
|
66
|
-
} catch (error) {
|
|
67
|
-
console.error('Failed to initialize database schema:', error);
|
|
68
|
-
throw error;
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// Valid transaction isolation levels
|
|
73
|
-
const VALID_ISOLATION_LEVELS = [
|
|
74
|
-
'READ COMMITTED',
|
|
75
|
-
'REPEATABLE READ',
|
|
76
|
-
'SERIALIZABLE'
|
|
77
|
-
];
|
|
78
|
-
|
|
79
|
-
// Helper for transactions with retry logic
|
|
80
|
-
export const withTransaction = async (pool, callback, isolationLevel = 'READ COMMITTED') => {
|
|
81
|
-
// Validate isolation level
|
|
82
|
-
if (isolationLevel && !VALID_ISOLATION_LEVELS.includes(isolationLevel)) {
|
|
83
|
-
throw new Error(`Invalid isolation level: ${isolationLevel}`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const maxAttempts = 3;
|
|
87
|
-
let attempt = 1;
|
|
88
|
-
|
|
89
|
-
while (attempt <= maxAttempts) {
|
|
90
|
-
const client = await pool.connect();
|
|
91
|
-
try {
|
|
92
|
-
await client.query('BEGIN');
|
|
93
|
-
|
|
94
|
-
if (isolationLevel) {
|
|
95
|
-
await client.query(`SET TRANSACTION ISOLATION LEVEL ${isolationLevel}`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Set timeouts to prevent long blocks
|
|
99
|
-
await client.query(`SET LOCAL statement_timeout = ${config.DATABASE.STATEMENT_TIMEOUT}`);
|
|
100
|
-
await client.query(`SET LOCAL lock_timeout = ${config.DATABASE.LOCK_TIMEOUT}`);
|
|
101
|
-
|
|
102
|
-
const result = await callback(client);
|
|
103
|
-
await client.query('COMMIT');
|
|
104
|
-
return result;
|
|
105
|
-
} catch (error) {
|
|
106
|
-
await client.query('ROLLBACK');
|
|
107
|
-
|
|
108
|
-
// Check for retriable errors:
|
|
109
|
-
// 40001: serialization_failure
|
|
110
|
-
// 40P01: deadlock_detected
|
|
111
|
-
// 55P03: lock_not_available (lock timeout)
|
|
112
|
-
// 25P02: in_failed_sql_transaction (transaction already aborted - retry whole transaction)
|
|
113
|
-
if ((error.code === '40001' || error.code === '40P01' || error.code === '55P03' || error.code === '25P02') && attempt < maxAttempts) {
|
|
114
|
-
// Exponential backoff: 100ms, 200ms, 400ms
|
|
115
|
-
const delay = Math.min(100 * Math.pow(2, attempt - 1), 1000);
|
|
116
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
117
|
-
attempt++;
|
|
118
|
-
console.log(`Transaction retry attempt ${attempt}/${maxAttempts} after ${error.code} (${error.message})`);
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
throw error;
|
|
123
|
-
} finally {
|
|
124
|
-
client.release();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
throw new Error('Max transaction retry attempts exceeded');
|
|
129
|
-
};
|