pgserve 1.1.3 → 1.1.4
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 +113 -19
- package/bin/pglite-server.js +58 -7
- package/package.json +1 -1
- package/src/cluster.js +81 -9
- package/src/index.js +2 -0
- package/src/postgres.js +1 -0
- package/src/stats-collector.js +267 -0
- package/src/stats-dashboard.js +382 -0
- package/tests/benchmarks/runner.js +871 -50
- package/tests/benchmarks/vector-generator.js +368 -0
- package/tests/quick-bench.js +135 -0
- package/tests/stress-test.js +439 -0
package/README.md
CHANGED
|
@@ -260,46 +260,140 @@ pgserve --sync-to "postgresql://..." --sync-databases "myapp,tenant_*"
|
|
|
260
260
|
|
|
261
261
|
## Performance
|
|
262
262
|
|
|
263
|
-
###
|
|
263
|
+
### CRUD Benchmarks
|
|
264
264
|
|
|
265
265
|
<table>
|
|
266
266
|
<tr>
|
|
267
267
|
<th>Scenario</th>
|
|
268
268
|
<th>SQLite</th>
|
|
269
269
|
<th>PGlite</th>
|
|
270
|
-
<th>
|
|
271
|
-
<th>pgserve
|
|
270
|
+
<th>PostgreSQL</th>
|
|
271
|
+
<th>pgserve</th>
|
|
272
272
|
<th>pgserve --ram</th>
|
|
273
273
|
</tr>
|
|
274
274
|
<tr>
|
|
275
275
|
<td><b>Concurrent Writes</b> (10 agents)</td>
|
|
276
|
-
<td>
|
|
277
|
-
<td>
|
|
278
|
-
<td>
|
|
279
|
-
<td>
|
|
280
|
-
<td><b>4,
|
|
276
|
+
<td>91 qps</td>
|
|
277
|
+
<td>204 qps</td>
|
|
278
|
+
<td>1,667 qps</td>
|
|
279
|
+
<td>2,273 qps</td>
|
|
280
|
+
<td><b>4,167 qps</b> 🏆</td>
|
|
281
281
|
</tr>
|
|
282
282
|
<tr>
|
|
283
283
|
<td><b>Mixed Workload</b></td>
|
|
284
|
-
<td>
|
|
285
|
-
<td>
|
|
286
|
-
<td>
|
|
287
|
-
<td>
|
|
288
|
-
<td><b>
|
|
284
|
+
<td>383 qps</td>
|
|
285
|
+
<td>484 qps</td>
|
|
286
|
+
<td>507 qps</td>
|
|
287
|
+
<td>1,133 qps</td>
|
|
288
|
+
<td><b>2,109 qps</b> 🏆</td>
|
|
289
289
|
</tr>
|
|
290
290
|
<tr>
|
|
291
291
|
<td><b>Write Lock</b> (50 writers)</td>
|
|
292
|
-
<td>
|
|
293
|
-
<td>
|
|
294
|
-
<td>
|
|
295
|
-
<td>
|
|
292
|
+
<td>111 qps</td>
|
|
293
|
+
<td>228 qps</td>
|
|
294
|
+
<td>2,857 qps</td>
|
|
295
|
+
<td>3,030 qps</td>
|
|
296
296
|
<td><b>4,348 qps</b> 🏆</td>
|
|
297
297
|
</tr>
|
|
298
298
|
</table>
|
|
299
299
|
|
|
300
|
-
|
|
300
|
+
### Vector Benchmarks (pgvector)
|
|
301
|
+
|
|
302
|
+
<table>
|
|
303
|
+
<tr>
|
|
304
|
+
<th>Metric</th>
|
|
305
|
+
<th>PGlite</th>
|
|
306
|
+
<th>PostgreSQL</th>
|
|
307
|
+
<th>pgserve</th>
|
|
308
|
+
<th>pgserve --ram</th>
|
|
309
|
+
</tr>
|
|
310
|
+
<tr>
|
|
311
|
+
<td><b>Vector INSERT</b> (1000 × 1536-dim)</td>
|
|
312
|
+
<td>152/sec</td>
|
|
313
|
+
<td>392/sec</td>
|
|
314
|
+
<td>387/sec</td>
|
|
315
|
+
<td><b>1,082/sec</b> 🏆</td>
|
|
316
|
+
</tr>
|
|
317
|
+
<tr>
|
|
318
|
+
<td><b>k-NN Search</b> (k=10, 10k corpus)</td>
|
|
319
|
+
<td>22 qps</td>
|
|
320
|
+
<td>33 qps</td>
|
|
321
|
+
<td>31 qps</td>
|
|
322
|
+
<td>30 qps</td>
|
|
323
|
+
</tr>
|
|
324
|
+
<tr>
|
|
325
|
+
<td><b>Recall@10</b></td>
|
|
326
|
+
<td>100%</td>
|
|
327
|
+
<td>100%</td>
|
|
328
|
+
<td>100%</td>
|
|
329
|
+
<td>100%</td>
|
|
330
|
+
</tr>
|
|
331
|
+
</table>
|
|
332
|
+
|
|
333
|
+
> <b>Why pgserve wins on writes:</b> RAM mode uses <code>/dev/shm</code> (tmpfs), eliminating fsync latency. Vector search is CPU-bound, so RAM mode shows minimal benefit there.
|
|
334
|
+
|
|
335
|
+
### Final Score
|
|
336
|
+
|
|
337
|
+
<table>
|
|
338
|
+
<tr>
|
|
339
|
+
<th>Engine</th>
|
|
340
|
+
<th>CRUD QPS</th>
|
|
341
|
+
<th>Vec QPS</th>
|
|
342
|
+
<th>Recall</th>
|
|
343
|
+
<th>P50</th>
|
|
344
|
+
<th>P99</th>
|
|
345
|
+
<th>Score</th>
|
|
346
|
+
</tr>
|
|
347
|
+
<tr>
|
|
348
|
+
<td>SQLite</td>
|
|
349
|
+
<td>195</td>
|
|
350
|
+
<td>N/A</td>
|
|
351
|
+
<td>N/A</td>
|
|
352
|
+
<td>6.3ms</td>
|
|
353
|
+
<td>17.3ms</td>
|
|
354
|
+
<td>117</td>
|
|
355
|
+
</tr>
|
|
356
|
+
<tr>
|
|
357
|
+
<td>PGlite</td>
|
|
358
|
+
<td>305</td>
|
|
359
|
+
<td>65</td>
|
|
360
|
+
<td>100%</td>
|
|
361
|
+
<td>3.3ms</td>
|
|
362
|
+
<td>7.0ms</td>
|
|
363
|
+
<td>209</td>
|
|
364
|
+
</tr>
|
|
365
|
+
<tr>
|
|
366
|
+
<td>PostgreSQL</td>
|
|
367
|
+
<td>1,677</td>
|
|
368
|
+
<td>152</td>
|
|
369
|
+
<td>100%</td>
|
|
370
|
+
<td>6.0ms</td>
|
|
371
|
+
<td>19.0ms</td>
|
|
372
|
+
<td>1,067</td>
|
|
373
|
+
</tr>
|
|
374
|
+
<tr>
|
|
375
|
+
<td>pgserve</td>
|
|
376
|
+
<td>2,145</td>
|
|
377
|
+
<td>149</td>
|
|
378
|
+
<td>100%</td>
|
|
379
|
+
<td>5.3ms</td>
|
|
380
|
+
<td>13.0ms</td>
|
|
381
|
+
<td>1,347</td>
|
|
382
|
+
</tr>
|
|
383
|
+
<tr>
|
|
384
|
+
<td><b>pgserve --ram</b></td>
|
|
385
|
+
<td><b>3,541</b></td>
|
|
386
|
+
<td><b>381</b></td>
|
|
387
|
+
<td><b>100%</b></td>
|
|
388
|
+
<td><b>3.3ms</b></td>
|
|
389
|
+
<td><b>10.7ms</b></td>
|
|
390
|
+
<td><b>2,277</b> 🏆</td>
|
|
391
|
+
</tr>
|
|
392
|
+
</table>
|
|
393
|
+
|
|
394
|
+
> <b>Methodology:</b> Recall@k measured against brute-force ground truth (industry standard). PostgreSQL baseline is Docker <code>pgvector/pgvector:pg17</code>. RAM mode available on Linux and WSL2.
|
|
301
395
|
>
|
|
302
|
-
> Run benchmarks:
|
|
396
|
+
> Run benchmarks yourself: <code>bun tests/benchmarks/runner.js --include-vector</code>
|
|
303
397
|
|
|
304
398
|
<br>
|
|
305
399
|
|
package/bin/pglite-server.js
CHANGED
|
@@ -53,6 +53,8 @@ OPTIONS:
|
|
|
53
53
|
--no-provision Disable auto-provisioning of databases
|
|
54
54
|
--sync-to <url> Sync to real PostgreSQL (async replication)
|
|
55
55
|
--sync-databases Database patterns to sync (comma-separated, e.g. "myapp,tenant_*")
|
|
56
|
+
--no-stats Disable real-time stats dashboard (enabled by default)
|
|
57
|
+
--max-connections Max concurrent connections (default: 1000)
|
|
56
58
|
--help Show this help message
|
|
57
59
|
|
|
58
60
|
MODES:
|
|
@@ -108,7 +110,9 @@ function parseArgs() {
|
|
|
108
110
|
cluster: cpuCount > 1 && !isWindows, // Auto-enable on multi-core (disabled on Windows - no SO_REUSEPORT)
|
|
109
111
|
workers: null, // null = use CPU count
|
|
110
112
|
syncTo: null, // Sync target PostgreSQL URL
|
|
111
|
-
syncDatabases: null // Database patterns to sync (comma-separated)
|
|
113
|
+
syncDatabases: null, // Database patterns to sync (comma-separated)
|
|
114
|
+
showStats: true, // Show real-time stats dashboard (default: enabled)
|
|
115
|
+
maxConnections: 1000 // Max concurrent connections (high default for multi-tenant)
|
|
112
116
|
};
|
|
113
117
|
|
|
114
118
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -163,6 +167,18 @@ function parseArgs() {
|
|
|
163
167
|
options.syncDatabases = args[++i];
|
|
164
168
|
break;
|
|
165
169
|
|
|
170
|
+
case '--stats':
|
|
171
|
+
options.showStats = true;
|
|
172
|
+
break;
|
|
173
|
+
|
|
174
|
+
case '--no-stats':
|
|
175
|
+
options.showStats = false;
|
|
176
|
+
break;
|
|
177
|
+
|
|
178
|
+
case '--max-connections':
|
|
179
|
+
options.maxConnections = parseInt(args[++i], 10);
|
|
180
|
+
break;
|
|
181
|
+
|
|
166
182
|
case '--help':
|
|
167
183
|
case 'help':
|
|
168
184
|
printHelp();
|
|
@@ -211,7 +227,8 @@ pgserve - Embedded PostgreSQL Server
|
|
|
211
227
|
useRam: options.useRam,
|
|
212
228
|
logLevel: options.logLevel,
|
|
213
229
|
autoProvision: options.autoProvision,
|
|
214
|
-
workers: options.workers
|
|
230
|
+
workers: options.workers,
|
|
231
|
+
maxConnections: options.maxConnections
|
|
215
232
|
});
|
|
216
233
|
|
|
217
234
|
// Only primary process shows full startup message
|
|
@@ -222,7 +239,7 @@ pgserve - Embedded PostgreSQL Server
|
|
|
222
239
|
Cluster started successfully!
|
|
223
240
|
|
|
224
241
|
Endpoint: postgresql://${options.host}:${options.port}/<database>
|
|
225
|
-
Mode: ${memoryMode ? '
|
|
242
|
+
Mode: ${memoryMode ? (options.useRam ? 'RAM (/dev/shm)' : 'Ephemeral (temp)') : 'Persistent'} (Cluster)
|
|
226
243
|
Workers: ${stats.workers} processes
|
|
227
244
|
Data: ${storageType}
|
|
228
245
|
Auto-create: ${options.autoProvision ? 'Enabled' : 'Disabled'}
|
|
@@ -244,7 +261,8 @@ Press Ctrl+C to stop
|
|
|
244
261
|
logLevel: options.logLevel,
|
|
245
262
|
autoProvision: options.autoProvision,
|
|
246
263
|
syncTo: options.syncTo,
|
|
247
|
-
syncDatabases: options.syncDatabases
|
|
264
|
+
syncDatabases: options.syncDatabases,
|
|
265
|
+
maxConnections: options.maxConnections
|
|
248
266
|
});
|
|
249
267
|
|
|
250
268
|
server = router;
|
|
@@ -258,7 +276,7 @@ Press Ctrl+C to stop
|
|
|
258
276
|
Server started successfully!
|
|
259
277
|
|
|
260
278
|
Endpoint: postgresql://${options.host}:${options.port}/<database>
|
|
261
|
-
Mode: ${memoryMode ? '
|
|
279
|
+
Mode: ${memoryMode ? (options.useRam ? 'RAM (/dev/shm)' : 'Ephemeral (temp)') : 'Persistent'}
|
|
262
280
|
Data: ${storageType}
|
|
263
281
|
PostgreSQL: Port ${router.pgPort} (internal)
|
|
264
282
|
Auto-create: ${options.autoProvision ? 'Enabled' : 'Disabled'}
|
|
@@ -272,12 +290,45 @@ Press Ctrl+C to stop
|
|
|
272
290
|
`);
|
|
273
291
|
}
|
|
274
292
|
|
|
293
|
+
// Start stats dashboard if requested (only for primary/single-process)
|
|
294
|
+
let dashboard = null;
|
|
295
|
+
if (options.showStats && !process.env.PGSERVE_WORKER) {
|
|
296
|
+
const { StatsDashboard } = await import('../src/stats-dashboard.js');
|
|
297
|
+
const { StatsCollector } = await import('../src/stats-collector.js');
|
|
298
|
+
|
|
299
|
+
// Create stats collector with appropriate sources
|
|
300
|
+
const collector = new StatsCollector({
|
|
301
|
+
router: options.cluster ? null : server,
|
|
302
|
+
pgManager: server.pgManager,
|
|
303
|
+
clusterStats: options.cluster ? () => server.getStats() : null,
|
|
304
|
+
logger: server.logger,
|
|
305
|
+
port: options.port,
|
|
306
|
+
host: options.host
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
dashboard = new StatsDashboard({
|
|
310
|
+
refreshInterval: 2000, // 2 second refresh for real-time feel
|
|
311
|
+
statsProvider: () => collector.collect()
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
dashboard.start();
|
|
315
|
+
}
|
|
316
|
+
|
|
275
317
|
// Graceful shutdown (only for primary/single-process, workers handle via IPC)
|
|
276
318
|
if (!process.env.PGSERVE_WORKER) {
|
|
277
319
|
const shutdown = async () => {
|
|
320
|
+
// Stop dashboard first to restore cursor
|
|
321
|
+
if (dashboard) {
|
|
322
|
+
dashboard.stop();
|
|
323
|
+
}
|
|
278
324
|
console.log('\nShutting down...');
|
|
279
|
-
|
|
280
|
-
|
|
325
|
+
try {
|
|
326
|
+
await server.stop();
|
|
327
|
+
console.log('Server stopped.');
|
|
328
|
+
} catch (err) {
|
|
329
|
+
console.error('Error during shutdown:', err.message);
|
|
330
|
+
// Still exit - best effort cleanup
|
|
331
|
+
}
|
|
281
332
|
process.exit(0);
|
|
282
333
|
};
|
|
283
334
|
|
package/package.json
CHANGED
package/src/cluster.js
CHANGED
|
@@ -23,6 +23,10 @@ const SSL_REQUEST_CODE = 80877103;
|
|
|
23
23
|
const GSSAPI_REQUEST_CODE = 80877104;
|
|
24
24
|
const CANCEL_REQUEST_CODE = 80877102;
|
|
25
25
|
|
|
26
|
+
// Stats collection constants
|
|
27
|
+
const WORKER_STATS_TIMEOUT_MS = 10000; // Worker stats older than this are considered stale
|
|
28
|
+
const WORKER_STATS_REPORT_INTERVAL_MS = 4000; // How often workers report stats to primary
|
|
29
|
+
|
|
26
30
|
/**
|
|
27
31
|
* ClusterRouter - Lightweight TCP router for worker processes
|
|
28
32
|
* Does NOT start PostgreSQL - connects to PRIMARY's PostgreSQL via Unix socket
|
|
@@ -44,6 +48,12 @@ class ClusterRouter extends EventEmitter {
|
|
|
44
48
|
this.server = null;
|
|
45
49
|
this.connections = new Set();
|
|
46
50
|
this.setMaxListeners(this.maxConnections + 10);
|
|
51
|
+
|
|
52
|
+
// Connection stats tracking for IPC reporting
|
|
53
|
+
this.connectionStats = {
|
|
54
|
+
totalConnected: 0,
|
|
55
|
+
totalDisconnected: 0
|
|
56
|
+
};
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
/**
|
|
@@ -134,6 +144,7 @@ class ClusterRouter extends EventEmitter {
|
|
|
134
144
|
handshakeComplete: false
|
|
135
145
|
});
|
|
136
146
|
this.connections.add(socket);
|
|
147
|
+
this.connectionStats.totalConnected++;
|
|
137
148
|
}
|
|
138
149
|
|
|
139
150
|
/**
|
|
@@ -259,6 +270,19 @@ class ClusterRouter extends EventEmitter {
|
|
|
259
270
|
}
|
|
260
271
|
this.connections.delete(socket);
|
|
261
272
|
this.socketState.delete(socket);
|
|
273
|
+
this.connectionStats.totalDisconnected++;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get router stats for IPC reporting
|
|
278
|
+
*/
|
|
279
|
+
getStats() {
|
|
280
|
+
return {
|
|
281
|
+
connections: this.connections.size,
|
|
282
|
+
totalConnected: this.connectionStats.totalConnected,
|
|
283
|
+
totalDisconnected: this.connectionStats.totalDisconnected,
|
|
284
|
+
pid: process.pid
|
|
285
|
+
};
|
|
262
286
|
}
|
|
263
287
|
|
|
264
288
|
/**
|
|
@@ -286,7 +310,7 @@ class ClusterRouter extends EventEmitter {
|
|
|
286
310
|
try {
|
|
287
311
|
await this.sql.close();
|
|
288
312
|
} catch {
|
|
289
|
-
//
|
|
313
|
+
// Expected: connection may already be terminated during cleanup
|
|
290
314
|
}
|
|
291
315
|
}
|
|
292
316
|
|
|
@@ -297,6 +321,7 @@ class ClusterRouter extends EventEmitter {
|
|
|
297
321
|
}
|
|
298
322
|
}
|
|
299
323
|
|
|
324
|
+
|
|
300
325
|
/**
|
|
301
326
|
* Start pgserve in cluster mode
|
|
302
327
|
*/
|
|
@@ -307,6 +332,8 @@ export async function startClusterServer(options = {}) {
|
|
|
307
332
|
const pgPort = options.pgPort || (port + 1000);
|
|
308
333
|
|
|
309
334
|
if (cluster.isPrimary) {
|
|
335
|
+
// Port binding happens in workers via Bun.listen with reusePort
|
|
336
|
+
// If port is in use, first worker will fail with EADDRINUSE
|
|
310
337
|
console.log(`[pgserve] Cluster mode: ${numWorkers} workers`);
|
|
311
338
|
|
|
312
339
|
// PRIMARY: Start our embedded PostgreSQL (single instance)
|
|
@@ -325,6 +352,7 @@ export async function startClusterServer(options = {}) {
|
|
|
325
352
|
console.log(`[pgserve] Socket: ${pgSocketPath || `TCP port ${pgPort}`}`);
|
|
326
353
|
|
|
327
354
|
const workers = new Map();
|
|
355
|
+
const workerStats = new Map(); // Track stats from each worker
|
|
328
356
|
|
|
329
357
|
// Fork workers with PostgreSQL connection info
|
|
330
358
|
for (let i = 0; i < numWorkers; i++) {
|
|
@@ -337,7 +365,8 @@ export async function startClusterServer(options = {}) {
|
|
|
337
365
|
PGSERVE_PG_USER: 'postgres',
|
|
338
366
|
PGSERVE_PG_PASSWORD: 'postgres',
|
|
339
367
|
PGSERVE_LOG_LEVEL: options.logLevel || 'info',
|
|
340
|
-
PGSERVE_AUTO_PROVISION: options.autoProvision !== false ? 'true' : 'false'
|
|
368
|
+
PGSERVE_AUTO_PROVISION: options.autoProvision !== false ? 'true' : 'false',
|
|
369
|
+
PGSERVE_MAX_CONNECTIONS: String(options.maxConnections || 1000)
|
|
341
370
|
});
|
|
342
371
|
workers.set(worker.id, worker);
|
|
343
372
|
}
|
|
@@ -363,18 +392,25 @@ export async function startClusterServer(options = {}) {
|
|
|
363
392
|
PGSERVE_PG_USER: 'postgres',
|
|
364
393
|
PGSERVE_PG_PASSWORD: 'postgres',
|
|
365
394
|
PGSERVE_LOG_LEVEL: options.logLevel || 'info',
|
|
366
|
-
PGSERVE_AUTO_PROVISION: options.autoProvision !== false ? 'true' : 'false'
|
|
395
|
+
PGSERVE_AUTO_PROVISION: options.autoProvision !== false ? 'true' : 'false',
|
|
396
|
+
PGSERVE_MAX_CONNECTIONS: String(options.maxConnections || 1000)
|
|
367
397
|
});
|
|
368
398
|
workers.set(newWorker.id, newWorker);
|
|
369
399
|
});
|
|
370
400
|
|
|
371
|
-
// Wait for workers to be ready
|
|
401
|
+
// Wait for workers to be ready and handle IPC messages
|
|
372
402
|
let readyCount = 0;
|
|
373
403
|
await new Promise((resolve) => {
|
|
374
404
|
cluster.on('message', (worker, message) => {
|
|
375
405
|
if (message.type === 'ready') {
|
|
376
406
|
readyCount++;
|
|
377
407
|
if (readyCount === numWorkers) resolve();
|
|
408
|
+
} else if (message.type === 'stats') {
|
|
409
|
+
// Update worker stats from IPC
|
|
410
|
+
workerStats.set(worker.id, {
|
|
411
|
+
...message.data,
|
|
412
|
+
lastUpdate: Date.now()
|
|
413
|
+
});
|
|
378
414
|
}
|
|
379
415
|
});
|
|
380
416
|
});
|
|
@@ -403,10 +439,35 @@ export async function startClusterServer(options = {}) {
|
|
|
403
439
|
await pgManager.stop();
|
|
404
440
|
console.log('[pgserve] Cluster stopped');
|
|
405
441
|
},
|
|
406
|
-
getStats: () =>
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
442
|
+
getStats: () => {
|
|
443
|
+
// Aggregate stats from all workers
|
|
444
|
+
let totalConnections = 0;
|
|
445
|
+
let totalConnected = 0;
|
|
446
|
+
let totalDisconnected = 0;
|
|
447
|
+
const activeWorkerStats = {};
|
|
448
|
+
|
|
449
|
+
for (const [id, stats] of workerStats) {
|
|
450
|
+
// Only include recent stats (within timeout window)
|
|
451
|
+
if (Date.now() - stats.lastUpdate < WORKER_STATS_TIMEOUT_MS) {
|
|
452
|
+
totalConnections += stats.connections || 0;
|
|
453
|
+
totalConnected += stats.totalConnected || 0;
|
|
454
|
+
totalDisconnected += stats.totalDisconnected || 0;
|
|
455
|
+
activeWorkerStats[id] = stats;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
workers: workers.size,
|
|
461
|
+
pids: Array.from(workers.values()).map(w => w.process.pid),
|
|
462
|
+
connections: {
|
|
463
|
+
active: totalConnections,
|
|
464
|
+
totalConnected,
|
|
465
|
+
totalDisconnected
|
|
466
|
+
},
|
|
467
|
+
workerStats: activeWorkerStats
|
|
468
|
+
};
|
|
469
|
+
},
|
|
470
|
+
pgManager
|
|
410
471
|
};
|
|
411
472
|
} else {
|
|
412
473
|
// WORKER: Only run TCP routing, connect to PRIMARY's PostgreSQL
|
|
@@ -418,7 +479,8 @@ export async function startClusterServer(options = {}) {
|
|
|
418
479
|
pgUser: process.env.PGSERVE_PG_USER || 'postgres',
|
|
419
480
|
pgPassword: process.env.PGSERVE_PG_PASSWORD || 'postgres',
|
|
420
481
|
logLevel: process.env.PGSERVE_LOG_LEVEL || 'info',
|
|
421
|
-
autoProvision: process.env.PGSERVE_AUTO_PROVISION === 'true'
|
|
482
|
+
autoProvision: process.env.PGSERVE_AUTO_PROVISION === 'true',
|
|
483
|
+
maxConnections: parseInt(process.env.PGSERVE_MAX_CONNECTIONS) || 1000
|
|
422
484
|
});
|
|
423
485
|
|
|
424
486
|
await router.start();
|
|
@@ -426,9 +488,19 @@ export async function startClusterServer(options = {}) {
|
|
|
426
488
|
// Tell PRIMARY we're ready
|
|
427
489
|
process.send({ type: 'ready' });
|
|
428
490
|
|
|
491
|
+
// Periodically send stats to PRIMARY
|
|
492
|
+
const statsInterval = setInterval(() => {
|
|
493
|
+
try {
|
|
494
|
+
process.send({ type: 'stats', data: router.getStats() });
|
|
495
|
+
} catch {
|
|
496
|
+
// Expected: IPC channel may be closed during shutdown
|
|
497
|
+
}
|
|
498
|
+
}, WORKER_STATS_REPORT_INTERVAL_MS);
|
|
499
|
+
|
|
429
500
|
// Handle shutdown
|
|
430
501
|
process.on('message', async (message) => {
|
|
431
502
|
if (message.type === 'shutdown') {
|
|
503
|
+
clearInterval(statsInterval);
|
|
432
504
|
await router.stop();
|
|
433
505
|
process.exit(0);
|
|
434
506
|
}
|
package/src/index.js
CHANGED
|
@@ -11,6 +11,8 @@ export { PostgresManager } from './postgres.js';
|
|
|
11
11
|
export { SyncManager } from './sync.js';
|
|
12
12
|
export { RestoreManager } from './restore.js';
|
|
13
13
|
export { Dashboard } from './dashboard.js';
|
|
14
|
+
export { StatsCollector } from './stats-collector.js';
|
|
15
|
+
export { StatsDashboard } from './stats-dashboard.js';
|
|
14
16
|
|
|
15
17
|
// Default export
|
|
16
18
|
export { startMultiTenantServer as default } from './router.js';
|
package/src/postgres.js
CHANGED
|
@@ -660,6 +660,7 @@ export class PostgresManager {
|
|
|
660
660
|
this.binaries.postgres,
|
|
661
661
|
'-D', this.databaseDir,
|
|
662
662
|
'-p', this.port.toString(),
|
|
663
|
+
'-c', 'max_connections=1000', // Support high connection counts for stress testing
|
|
663
664
|
];
|
|
664
665
|
|
|
665
666
|
// Enable Unix socket for faster local connections (Linux/macOS)
|