pgserve 1.0.5 → 1.0.7
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/bin/pglite-server.js +15 -10
- package/package.json +1 -1
- package/src/cluster.js +20 -6
- package/src/logger.js +39 -0
- package/src/restore.js +2 -2
- package/src/router.js +2 -8
- package/src/sync.js +2 -2
package/bin/pglite-server.js
CHANGED
|
@@ -179,10 +179,13 @@ async function main() {
|
|
|
179
179
|
const options = parseArgs();
|
|
180
180
|
const memoryMode = !options.dataDir;
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
// Only print header if not a cluster worker (workers get PGSERVE_WORKER env)
|
|
183
|
+
if (!process.env.PGSERVE_WORKER) {
|
|
184
|
+
console.log(`
|
|
183
185
|
pgserve - Embedded PostgreSQL Server
|
|
184
186
|
=====================================
|
|
185
187
|
`);
|
|
188
|
+
}
|
|
186
189
|
|
|
187
190
|
try {
|
|
188
191
|
let server;
|
|
@@ -254,16 +257,18 @@ Press Ctrl+C to stop
|
|
|
254
257
|
`);
|
|
255
258
|
}
|
|
256
259
|
|
|
257
|
-
// Graceful shutdown
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
260
|
+
// Graceful shutdown (only for primary/single-process, workers handle via IPC)
|
|
261
|
+
if (!process.env.PGSERVE_WORKER) {
|
|
262
|
+
const shutdown = async () => {
|
|
263
|
+
console.log('\nShutting down...');
|
|
264
|
+
await server.stop();
|
|
265
|
+
console.log('Server stopped.');
|
|
266
|
+
process.exit(0);
|
|
267
|
+
};
|
|
264
268
|
|
|
265
|
-
|
|
266
|
-
|
|
269
|
+
process.on('SIGINT', shutdown);
|
|
270
|
+
process.on('SIGTERM', shutdown);
|
|
271
|
+
}
|
|
267
272
|
|
|
268
273
|
// Keep process alive
|
|
269
274
|
await new Promise(() => {});
|
package/package.json
CHANGED
package/src/cluster.js
CHANGED
|
@@ -13,7 +13,7 @@ import cluster from 'cluster';
|
|
|
13
13
|
import os from 'os';
|
|
14
14
|
import net from 'net';
|
|
15
15
|
import pg from 'pg';
|
|
16
|
-
import
|
|
16
|
+
import { createLogger } from './logger.js';
|
|
17
17
|
import { PostgresManager } from './postgres.js';
|
|
18
18
|
import { extractDatabaseNameFromSocket } from './protocol.js';
|
|
19
19
|
import { EventEmitter } from 'events';
|
|
@@ -34,7 +34,7 @@ class ClusterRouter extends EventEmitter {
|
|
|
34
34
|
this.autoProvision = options.autoProvision !== false;
|
|
35
35
|
this.maxConnections = options.maxConnections || 1000;
|
|
36
36
|
|
|
37
|
-
this.logger =
|
|
37
|
+
this.logger = createLogger({ level: options.logLevel || 'info' });
|
|
38
38
|
this.adminClient = null;
|
|
39
39
|
this.server = null;
|
|
40
40
|
this.connections = new Set();
|
|
@@ -74,6 +74,8 @@ class ClusterRouter extends EventEmitter {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
this.adminClient = new pg.Client(connectionConfig);
|
|
77
|
+
// Suppress errors during shutdown (PostgreSQL terminating connections)
|
|
78
|
+
this.adminClient.on('error', () => {});
|
|
77
79
|
await this.adminClient.connect();
|
|
78
80
|
}
|
|
79
81
|
|
|
@@ -178,7 +180,11 @@ class ClusterRouter extends EventEmitter {
|
|
|
178
180
|
this.connections.clear();
|
|
179
181
|
|
|
180
182
|
if (this.adminClient) {
|
|
181
|
-
|
|
183
|
+
try {
|
|
184
|
+
await this.adminClient.end();
|
|
185
|
+
} catch {
|
|
186
|
+
// Ignore - connection may already be terminated
|
|
187
|
+
}
|
|
182
188
|
}
|
|
183
189
|
|
|
184
190
|
if (this.server) {
|
|
@@ -200,7 +206,7 @@ export async function startClusterServer(options = {}) {
|
|
|
200
206
|
console.log(`[pgserve] Cluster mode: ${numWorkers} workers`);
|
|
201
207
|
|
|
202
208
|
// PRIMARY: Start our embedded PostgreSQL (single instance)
|
|
203
|
-
const logger =
|
|
209
|
+
const logger = createLogger({ level: options.logLevel || 'info' });
|
|
204
210
|
const pgManager = new PostgresManager({
|
|
205
211
|
dataDir: options.baseDir,
|
|
206
212
|
port: pgPort,
|
|
@@ -231,11 +237,18 @@ export async function startClusterServer(options = {}) {
|
|
|
231
237
|
workers.set(worker.id, worker);
|
|
232
238
|
}
|
|
233
239
|
|
|
234
|
-
//
|
|
240
|
+
// Track shutdown state to prevent worker restart during shutdown
|
|
241
|
+
let shuttingDown = false;
|
|
242
|
+
|
|
243
|
+
// Restart dead workers (unless shutting down)
|
|
235
244
|
cluster.on('exit', (worker, code, signal) => {
|
|
236
|
-
console.log(`[pgserve] Worker ${worker.id} died (${signal || code}), restarting...`);
|
|
237
245
|
workers.delete(worker.id);
|
|
238
246
|
|
|
247
|
+
if (shuttingDown) {
|
|
248
|
+
return; // Don't restart during shutdown
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log(`[pgserve] Worker ${worker.id} died (${signal || code}), restarting...`);
|
|
239
252
|
const newWorker = cluster.fork({
|
|
240
253
|
PGSERVE_WORKER: 'true',
|
|
241
254
|
PGSERVE_PORT: String(port),
|
|
@@ -270,6 +283,7 @@ export async function startClusterServer(options = {}) {
|
|
|
270
283
|
pgSocketPath,
|
|
271
284
|
stop: async () => {
|
|
272
285
|
console.log('[pgserve] Stopping cluster...');
|
|
286
|
+
shuttingDown = true; // Prevent worker restart during shutdown
|
|
273
287
|
for (const worker of workers.values()) {
|
|
274
288
|
worker.send({ type: 'shutdown' });
|
|
275
289
|
}
|
package/src/logger.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Logger Configuration
|
|
3
|
+
*
|
|
4
|
+
* Provides colorful, human-readable logging via pino-pretty.
|
|
5
|
+
* All modules should use createLogger() for consistent output.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import pino from 'pino';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a configured pino logger with pretty output
|
|
12
|
+
* @param {Object} options - Logger options
|
|
13
|
+
* @param {string} options.level - Log level (default: 'info')
|
|
14
|
+
* @param {string} options.component - Component name for log context
|
|
15
|
+
* @returns {pino.Logger} Configured pino logger
|
|
16
|
+
*/
|
|
17
|
+
export function createLogger(options = {}) {
|
|
18
|
+
const level = options.level || process.env.LOG_LEVEL || 'info';
|
|
19
|
+
|
|
20
|
+
const logger = pino({
|
|
21
|
+
level,
|
|
22
|
+
transport: {
|
|
23
|
+
target: 'pino-pretty',
|
|
24
|
+
options: {
|
|
25
|
+
colorize: true,
|
|
26
|
+
ignore: 'pid,hostname', // Remove noise
|
|
27
|
+
translateTime: 'HH:MM:ss', // Short timestamp
|
|
28
|
+
hideObject: true, // Hide JSON attributes for clean output
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Return child logger with component if specified
|
|
34
|
+
if (options.component) {
|
|
35
|
+
return logger.child({ component: options.component });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return logger;
|
|
39
|
+
}
|
package/src/restore.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import pg from 'pg';
|
|
17
17
|
import { from as copyFrom, to as copyTo } from 'pg-copy-streams';
|
|
18
|
-
import
|
|
18
|
+
import { createLogger } from './logger.js';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Match database name against patterns (supports wildcards)
|
|
@@ -46,7 +46,7 @@ export class RestoreManager {
|
|
|
46
46
|
this.targetPort = options.targetPort; // Local embedded PostgreSQL port
|
|
47
47
|
this.targetSocketPath = options.targetSocketPath; // Unix socket path (optional)
|
|
48
48
|
|
|
49
|
-
this.logger = options.logger ||
|
|
49
|
+
this.logger = options.logger || createLogger({ level: options.logLevel || 'info', component: 'restore' });
|
|
50
50
|
|
|
51
51
|
// Connection pools (lazy initialized)
|
|
52
52
|
this.sourcePool = null;
|
package/src/router.js
CHANGED
|
@@ -19,7 +19,7 @@ import { RestoreManager } from './restore.js';
|
|
|
19
19
|
import { Dashboard } from './dashboard.js';
|
|
20
20
|
import { extractDatabaseNameFromSocket } from './protocol.js';
|
|
21
21
|
import { EventEmitter } from 'events';
|
|
22
|
-
import
|
|
22
|
+
import { createLogger } from './logger.js';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Multi-Tenant Router Server
|
|
@@ -39,13 +39,7 @@ export class MultiTenantRouter extends EventEmitter {
|
|
|
39
39
|
|
|
40
40
|
// Pino logger (ultra-fast structured logging)
|
|
41
41
|
const logLevel = options.logLevel || 'info';
|
|
42
|
-
this.logger = options.logger ||
|
|
43
|
-
level: logLevel,
|
|
44
|
-
transport: logLevel === 'debug' ? {
|
|
45
|
-
target: 'pino-pretty',
|
|
46
|
-
options: { colorize: true }
|
|
47
|
-
} : undefined
|
|
48
|
-
});
|
|
42
|
+
this.logger = options.logger || createLogger({ level: logLevel });
|
|
49
43
|
|
|
50
44
|
// Sync options (async replication to real PostgreSQL)
|
|
51
45
|
this.syncTo = options.syncTo || null;
|
package/src/sync.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import pg from 'pg';
|
|
9
|
-
import
|
|
9
|
+
import { createLogger } from './logger.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Match database name against patterns (supports wildcards)
|
|
@@ -36,7 +36,7 @@ export class SyncManager {
|
|
|
36
36
|
this.sourcePort = options.sourcePort; // pgserve PostgreSQL port
|
|
37
37
|
this.sourceSocketPath = options.sourceSocketPath; // pgserve socket path (optional)
|
|
38
38
|
|
|
39
|
-
this.logger =
|
|
39
|
+
this.logger = createLogger({ level: options.logLevel || 'info', component: 'sync' });
|
|
40
40
|
|
|
41
41
|
this.sourcePool = null; // Connection to pgserve's PostgreSQL
|
|
42
42
|
this.targetPool = null; // Connection to real PostgreSQL
|