pgserve 1.1.3-rc.7 → 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.
@@ -0,0 +1,119 @@
1
+ # Windows Debug Context
2
+
3
+ ## RESOLVED (2025-12-12)
4
+
5
+ The Windows router binding issue has been fixed. See "Solution Applied" below.
6
+
7
+ ---
8
+
9
+ ## Original Issue
10
+ PostgreSQL starts successfully on port 9432, but the router/proxy on port 8432 was NOT listening.
11
+ Clients could not connect because 8432 wasn't bound.
12
+
13
+ ## Root Causes Found
14
+
15
+ ### 1. `reusePort: true` - Linux-only feature (PRIMARY CAUSE)
16
+ **Location:** `src/cluster.js:75`
17
+
18
+ The cluster mode uses `Bun.listen({ reusePort: true })` which maps to `SO_REUSEPORT`.
19
+ This socket option is Linux-only and **silently fails on Windows**.
20
+
21
+ ### 2. Auto-enabled cluster mode on multi-core Windows systems
22
+ **Location:** `bin/pglite-server.js:106`
23
+
24
+ Windows systems with multiple CPU cores automatically entered cluster mode,
25
+ triggering the `reusePort` failure.
26
+
27
+ ### 3. TCP port opens before PostgreSQL ready for protocol handshakes
28
+ **Location:** `src/postgres.js:803-821`
29
+
30
+ PostgreSQL was marked "ready" when TCP port opened, but it wasn't actually
31
+ ready for protocol-level handshakes. Admin pool connection timed out.
32
+
33
+ ---
34
+
35
+ ## Solution Applied
36
+
37
+ ### Fix 1: Disable cluster mode on Windows
38
+ **File:** `bin/pglite-server.js:98-108`
39
+ ```javascript
40
+ const isWindows = os.platform() === 'win32';
41
+ cluster: cpuCount > 1 && !isWindows,
42
+ ```
43
+
44
+ ### Fix 2: Add platform check to reusePort
45
+ **File:** `src/cluster.js:72-76`
46
+ ```javascript
47
+ const isWindows = os.platform() === 'win32';
48
+ this.server = Bun.listen({
49
+ reusePort: !isWindows, // SO_REUSEPORT only works on Linux/macOS
50
+ ...
51
+ });
52
+ ```
53
+
54
+ ### Fix 3: Add port binding verification
55
+ **File:** `src/cluster.js:99-102`
56
+ ```javascript
57
+ if (!this.server || !this.server.port) {
58
+ throw new Error(`Failed to bind to port ${this.port} - reusePort may not be supported`);
59
+ }
60
+ ```
61
+
62
+ ### Fix 4: Add Windows readiness delay
63
+ **File:** `src/postgres.js:813-817`
64
+ ```javascript
65
+ if (isWindows) {
66
+ await Bun.sleep(2000); // 2 second delay for Windows
67
+ }
68
+ ```
69
+
70
+ ### Fix 5: Increase admin pool retry for Windows
71
+ **File:** `src/postgres.js:598-599`
72
+ ```javascript
73
+ const maxRetries = isWindows ? 10 : 5;
74
+ const baseDelay = isWindows ? 2000 : 1000;
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Verification
80
+
81
+ After applying fixes, server runs correctly on Windows:
82
+ - Router: `127.0.0.1:7432` ✅
83
+ - PostgreSQL: `127.0.0.1:8432` ✅
84
+ - Database auto-provisioning: ✅
85
+ - Query execution: ✅
86
+
87
+ ```
88
+ Server started successfully!
89
+
90
+ Endpoint: postgresql://127.0.0.1:7432/<database>
91
+ Mode: In-memory (ephemeral)
92
+ PostgreSQL: Port 8432 (internal)
93
+ Auto-create: Enabled
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Test Commands (Run from Windows)
99
+ ```cmd
100
+ # Build binary
101
+ bun build --compile bin/pglite-server.js --outfile dist/pgserve-windows-x64.exe
102
+
103
+ # Start server
104
+ dist\pgserve-windows-x64.exe
105
+
106
+ # Check ports are listening (should see BOTH)
107
+ netstat -an | findstr "LISTEN" | findstr "8432 9432"
108
+
109
+ # Test connection
110
+ psql postgresql://localhost:8432/testdb
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Related Files
116
+ - `bin/pglite-server.js` - Entry point, cluster mode decision
117
+ - `src/cluster.js` - Cluster mode with reusePort
118
+ - `src/router.js` - Single-process mode (works on Windows)
119
+ - `src/postgres.js` - PostgreSQL startup and admin pool
@@ -70,21 +70,34 @@ jobs:
70
70
  - name: Install dependencies
71
71
  run: bun install
72
72
 
73
- # Windows: native build with custom icon
74
- - name: Build for Windows (with icon)
73
+ # Windows: native build with icon and proper metadata
74
+ - name: Build for Windows (with branding)
75
75
  if: matrix.platform == 'windows-x64'
76
76
  run: |
77
- mkdir -p dist
78
- bun build --compile --windows-icon=assets/icon.ico bin/pglite-server.js --outfile dist/${{ matrix.output }}
79
- ls -lh dist/
80
- shell: bash
77
+ New-Item -ItemType Directory -Force -Path dist | Out-Null
78
+ $RAW_VERSION = node -p "require('./package.json').version"
79
+ # Convert 1.1.3-rc.10 to 1.1.3.10 (Windows requires X.Y.Z.W format)
80
+ $WIN_VERSION = $RAW_VERSION -replace '-rc\.', '.'
81
+ Write-Host "Raw version: $RAW_VERSION -> Windows version: $WIN_VERSION"
82
+ bun build --compile `
83
+ --define BUILD_VERSION="'$RAW_VERSION'" `
84
+ --windows-icon=assets/icon.ico `
85
+ --windows-title="pgserve" `
86
+ --windows-publisher="Namastex Labs" `
87
+ --windows-description="Embedded PostgreSQL Server - Zero config, auto-provision, unlimited connections" `
88
+ --windows-version="$WIN_VERSION" `
89
+ --windows-copyright="Copyright (c) 2025 Namastex Labs" `
90
+ bin/pglite-server.js --outfile dist/${{ matrix.output }}
91
+ Get-ChildItem dist/
92
+ shell: pwsh
81
93
 
82
94
  # Linux/macOS: native build
83
95
  - name: Build for ${{ matrix.platform }}
84
96
  if: matrix.platform != 'windows-x64'
85
97
  run: |
86
98
  mkdir -p dist
87
- bun build --compile bin/pglite-server.js --outfile dist/${{ matrix.output }}
99
+ VERSION=$(node -p "require('./package.json').version")
100
+ bun build --compile --define BUILD_VERSION="'$VERSION'" bin/pglite-server.js --outfile dist/${{ matrix.output }}
88
101
  ls -lh dist/
89
102
 
90
103
  - name: Upload artifact
package/README.md CHANGED
@@ -260,46 +260,140 @@ pgserve --sync-to "postgresql://..." --sync-databases "myapp,tenant_*"
260
260
 
261
261
  ## Performance
262
262
 
263
- ### Benchmark Results
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>pgserve (Node)</th>
271
- <th>pgserve (Bun)</th>
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>85 qps</td>
277
- <td>211 qps</td>
278
- <td>833 qps</td>
279
- <td>1,754 qps</td>
280
- <td><b>4,000 qps</b> 🏆</td>
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>339 qps</td>
285
- <td>505 qps</td>
286
- <td>1,017 qps</td>
287
- <td>999 qps</td>
288
- <td><b>1,986 qps</b> 🏆</td>
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>86 qps</td>
293
- <td>168 qps</td>
294
- <td>488 qps</td>
295
- <td>1,176 qps</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
- > pgserve uses Bun runtime internally for 2x throughput. **RAM mode adds another 2-4x** on Linux.
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: `bun run bench` (contributors only)
396
+ > Run benchmarks yourself: <code>bun tests/benchmarks/runner.js --include-vector</code>
303
397
 
304
398
  <br>
305
399
 
package/assets/icon.ico CHANGED
Binary file
@@ -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:
@@ -94,7 +96,9 @@ FEATURES:
94
96
  */
95
97
  function parseArgs() {
96
98
  // Auto-enable cluster mode on multi-core systems for best performance
99
+ // Note: Cluster mode uses SO_REUSEPORT which is not supported on Windows
97
100
  const cpuCount = os.cpus().length;
101
+ const isWindows = os.platform() === 'win32';
98
102
 
99
103
  const options = {
100
104
  port: 8432,
@@ -103,10 +107,12 @@ function parseArgs() {
103
107
  useRam: false, // Use /dev/shm for true RAM storage (Linux only)
104
108
  logLevel: 'info',
105
109
  autoProvision: true,
106
- cluster: cpuCount > 1, // Auto-enable on multi-core (use --no-cluster to disable)
110
+ cluster: cpuCount > 1 && !isWindows, // Auto-enable on multi-core (disabled on Windows - no SO_REUSEPORT)
107
111
  workers: null, // null = use CPU count
108
112
  syncTo: null, // Sync target PostgreSQL URL
109
- 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)
110
116
  };
111
117
 
112
118
  for (let i = 0; i < args.length; i++) {
@@ -161,6 +167,18 @@ function parseArgs() {
161
167
  options.syncDatabases = args[++i];
162
168
  break;
163
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
+
164
182
  case '--help':
165
183
  case 'help':
166
184
  printHelp();
@@ -209,7 +227,8 @@ pgserve - Embedded PostgreSQL Server
209
227
  useRam: options.useRam,
210
228
  logLevel: options.logLevel,
211
229
  autoProvision: options.autoProvision,
212
- workers: options.workers
230
+ workers: options.workers,
231
+ maxConnections: options.maxConnections
213
232
  });
214
233
 
215
234
  // Only primary process shows full startup message
@@ -220,7 +239,7 @@ pgserve - Embedded PostgreSQL Server
220
239
  Cluster started successfully!
221
240
 
222
241
  Endpoint: postgresql://${options.host}:${options.port}/<database>
223
- Mode: ${memoryMode ? 'In-memory (ephemeral)' : 'Persistent'} (Cluster)
242
+ Mode: ${memoryMode ? (options.useRam ? 'RAM (/dev/shm)' : 'Ephemeral (temp)') : 'Persistent'} (Cluster)
224
243
  Workers: ${stats.workers} processes
225
244
  Data: ${storageType}
226
245
  Auto-create: ${options.autoProvision ? 'Enabled' : 'Disabled'}
@@ -242,7 +261,8 @@ Press Ctrl+C to stop
242
261
  logLevel: options.logLevel,
243
262
  autoProvision: options.autoProvision,
244
263
  syncTo: options.syncTo,
245
- syncDatabases: options.syncDatabases
264
+ syncDatabases: options.syncDatabases,
265
+ maxConnections: options.maxConnections
246
266
  });
247
267
 
248
268
  server = router;
@@ -256,7 +276,7 @@ Press Ctrl+C to stop
256
276
  Server started successfully!
257
277
 
258
278
  Endpoint: postgresql://${options.host}:${options.port}/<database>
259
- Mode: ${memoryMode ? 'In-memory (ephemeral)' : 'Persistent'}
279
+ Mode: ${memoryMode ? (options.useRam ? 'RAM (/dev/shm)' : 'Ephemeral (temp)') : 'Persistent'}
260
280
  Data: ${storageType}
261
281
  PostgreSQL: Port ${router.pgPort} (internal)
262
282
  Auto-create: ${options.autoProvision ? 'Enabled' : 'Disabled'}
@@ -270,12 +290,45 @@ Press Ctrl+C to stop
270
290
  `);
271
291
  }
272
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
+
273
317
  // Graceful shutdown (only for primary/single-process, workers handle via IPC)
274
318
  if (!process.env.PGSERVE_WORKER) {
275
319
  const shutdown = async () => {
320
+ // Stop dashboard first to restore cursor
321
+ if (dashboard) {
322
+ dashboard.stop();
323
+ }
276
324
  console.log('\nShutting down...');
277
- await server.stop();
278
- console.log('Server stopped.');
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
+ }
279
332
  process.exit(0);
280
333
  };
281
334
 
package/eslint.config.js CHANGED
@@ -24,6 +24,8 @@ export default [
24
24
  TextDecoder: 'readonly',
25
25
  TextEncoder: 'readonly',
26
26
  Response: 'readonly',
27
+ // Build-time constants (injected via --define)
28
+ BUILD_VERSION: 'readonly',
27
29
  },
28
30
  },
29
31
  rules: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgserve",
3
- "version": "1.1.3-rc.7",
3
+ "version": "1.1.4",
4
4
  "description": "Embedded PostgreSQL server with true concurrent connections - zero config, auto-provision databases",
5
5
  "main": "src/index.js",
6
6
  "type": "module",