pgserve 2.1.2 → 2.2.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.
Files changed (227) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +105 -1
  3. package/bin/autopg-wrapper.cjs +16 -0
  4. package/bin/pgserve-wrapper.cjs +31 -6
  5. package/bin/postgres-server.js +80 -7
  6. package/console/README.md +131 -0
  7. package/console/api.js +173 -0
  8. package/console/app.jsx +483 -0
  9. package/console/colors_and_type.css +227 -0
  10. package/console/components.jsx +167 -0
  11. package/console/console.css +1666 -0
  12. package/console/data.jsx +350 -0
  13. package/console/index.html +31 -0
  14. package/console/screens/databases.jsx +5 -0
  15. package/console/screens/health.jsx +5 -0
  16. package/console/screens/ingress.jsx +5 -0
  17. package/console/screens/optimizer.jsx +5 -0
  18. package/console/screens/rlm-sim.jsx +5 -0
  19. package/console/screens/rlm-trace.jsx +5 -0
  20. package/console/screens/security.jsx +5 -0
  21. package/console/screens/settings.jsx +611 -0
  22. package/console/screens/sql.jsx +5 -0
  23. package/console/screens/sync.jsx +5 -0
  24. package/console/screens/tables.jsx +5 -0
  25. package/console/tweaks-panel.jsx +425 -0
  26. package/package.json +11 -1
  27. package/src/cli-config.cjs +310 -0
  28. package/src/cli-install.cjs +98 -11
  29. package/src/cli-restart.cjs +228 -0
  30. package/src/cli-ui.cjs +580 -0
  31. package/src/cluster.js +43 -38
  32. package/src/postgres.js +141 -19
  33. package/src/settings-loader.cjs +235 -0
  34. package/src/settings-migrate.cjs +212 -0
  35. package/src/settings-pg-args.cjs +146 -0
  36. package/src/settings-schema.cjs +422 -0
  37. package/src/settings-validator.cjs +416 -0
  38. package/src/settings-writer.cjs +288 -0
  39. package/.claude/context/windows-debug.md +0 -119
  40. package/.genie/AGENTS.md +0 -15
  41. package/.genie/agents/README.md +0 -110
  42. package/.genie/agents/analyze.md +0 -176
  43. package/.genie/agents/forge.md +0 -290
  44. package/.genie/agents/garbage-cleaner.md +0 -324
  45. package/.genie/agents/garbage-collector.md +0 -596
  46. package/.genie/agents/github-issue-gc.md +0 -618
  47. package/.genie/agents/review.md +0 -380
  48. package/.genie/agents/semantic-analyzer/find-duplicates.md +0 -90
  49. package/.genie/agents/semantic-analyzer/find-orphans.md +0 -99
  50. package/.genie/agents/semantic-analyzer.md +0 -101
  51. package/.genie/agents/update.md +0 -182
  52. package/.genie/agents/wish.md +0 -357
  53. package/.genie/brainstorms/pgserve-v2/DESIGN.md +0 -174
  54. package/.genie/code/AGENTS.md +0 -694
  55. package/.genie/code/agents/audit/risk.md +0 -173
  56. package/.genie/code/agents/audit/security.md +0 -189
  57. package/.genie/code/agents/audit.md +0 -145
  58. package/.genie/code/agents/challenge.md +0 -230
  59. package/.genie/code/agents/change-reviewer.md +0 -295
  60. package/.genie/code/agents/code-garbage-collector.md +0 -425
  61. package/.genie/code/agents/code-quality.md +0 -410
  62. package/.genie/code/agents/commit-suggester.md +0 -255
  63. package/.genie/code/agents/commit.md +0 -124
  64. package/.genie/code/agents/consensus.md +0 -204
  65. package/.genie/code/agents/daily-standup.md +0 -722
  66. package/.genie/code/agents/docgen.md +0 -48
  67. package/.genie/code/agents/explore.md +0 -79
  68. package/.genie/code/agents/fix.md +0 -100
  69. package/.genie/code/agents/git/commit-advisory.md +0 -219
  70. package/.genie/code/agents/git/workflows/issue.md +0 -244
  71. package/.genie/code/agents/git/workflows/pr.md +0 -179
  72. package/.genie/code/agents/git/workflows/release.md +0 -460
  73. package/.genie/code/agents/git/workflows/report.md +0 -342
  74. package/.genie/code/agents/git.md +0 -432
  75. package/.genie/code/agents/implementor.md +0 -161
  76. package/.genie/code/agents/install.md +0 -515
  77. package/.genie/code/agents/issue-creator.md +0 -344
  78. package/.genie/code/agents/polish.md +0 -116
  79. package/.genie/code/agents/qa.md +0 -653
  80. package/.genie/code/agents/refactor.md +0 -294
  81. package/.genie/code/agents/release.md +0 -1129
  82. package/.genie/code/agents/roadmap.md +0 -885
  83. package/.genie/code/agents/tests.md +0 -557
  84. package/.genie/code/agents/tracer.md +0 -50
  85. package/.genie/code/agents/update/upstream-update.md +0 -85
  86. package/.genie/code/agents/update/versions/generic-update.md +0 -305
  87. package/.genie/code/agents/vibe.md +0 -1317
  88. package/.genie/code/spells/agent-configuration.md +0 -58
  89. package/.genie/code/spells/automated-rc-publishing.md +0 -106
  90. package/.genie/code/spells/branch-tracker-guidance.md +0 -28
  91. package/.genie/code/spells/debug.md +0 -320
  92. package/.genie/code/spells/emoji-naming-convention.md +0 -303
  93. package/.genie/code/spells/evidence-storage.md +0 -26
  94. package/.genie/code/spells/file-naming-rules.md +0 -35
  95. package/.genie/code/spells/forge-code-blueprints.md +0 -195
  96. package/.genie/code/spells/genie-integration.md +0 -153
  97. package/.genie/code/spells/publishing-protocol.md +0 -61
  98. package/.genie/code/spells/team-consultation-protocol.md +0 -284
  99. package/.genie/code/spells/tool-requirements.md +0 -20
  100. package/.genie/code/spells/triad-maintenance-protocol.md +0 -154
  101. package/.genie/code/teams/tech-council/council.md +0 -328
  102. package/.genie/code/teams/tech-council/jt.md +0 -352
  103. package/.genie/code/teams/tech-council/nayr.md +0 -305
  104. package/.genie/code/teams/tech-council/oettam.md +0 -375
  105. package/.genie/neurons/README.md +0 -193
  106. package/.genie/neurons/forge.md +0 -106
  107. package/.genie/neurons/genie.md +0 -63
  108. package/.genie/neurons/review.md +0 -106
  109. package/.genie/neurons/wish.md +0 -104
  110. package/.genie/product/README.md +0 -20
  111. package/.genie/product/cli-automation.md +0 -359
  112. package/.genie/product/environment.md +0 -60
  113. package/.genie/product/mission.md +0 -60
  114. package/.genie/product/roadmap.md +0 -44
  115. package/.genie/product/tech-stack.md +0 -34
  116. package/.genie/product/templates/context-template.md +0 -218
  117. package/.genie/product/templates/qa-done-report-template.md +0 -68
  118. package/.genie/product/templates/review-report-template.md +0 -89
  119. package/.genie/product/templates/wish-template.md +0 -120
  120. package/.genie/scripts/helpers/analyze-commit.js +0 -195
  121. package/.genie/scripts/helpers/bullet-counter.js +0 -194
  122. package/.genie/scripts/helpers/bullet-find.js +0 -289
  123. package/.genie/scripts/helpers/bullet-id.js +0 -244
  124. package/.genie/scripts/helpers/check-secrets.js +0 -237
  125. package/.genie/scripts/helpers/count-tokens.js +0 -200
  126. package/.genie/scripts/helpers/create-frontmatter.js +0 -456
  127. package/.genie/scripts/helpers/detect-markers.js +0 -293
  128. package/.genie/scripts/helpers/detect-todos.js +0 -267
  129. package/.genie/scripts/helpers/detect-unlabeled-blocks.js +0 -135
  130. package/.genie/scripts/helpers/embeddings.js +0 -344
  131. package/.genie/scripts/helpers/find-empty-sections.js +0 -158
  132. package/.genie/scripts/helpers/index.js +0 -319
  133. package/.genie/scripts/helpers/validate-frontmatter.js +0 -578
  134. package/.genie/scripts/helpers/validate-links.js +0 -207
  135. package/.genie/scripts/helpers/validate-paths.js +0 -373
  136. package/.genie/spells/README.md +0 -9
  137. package/.genie/spells/ace-protocol.md +0 -118
  138. package/.genie/spells/ask-one-at-a-time.md +0 -175
  139. package/.genie/spells/backup-analyzer.md +0 -542
  140. package/.genie/spells/blocker.md +0 -12
  141. package/.genie/spells/break-things-move-fast.md +0 -56
  142. package/.genie/spells/context-candidates.md +0 -72
  143. package/.genie/spells/context-critic.md +0 -51
  144. package/.genie/spells/defer-to-expertise.md +0 -278
  145. package/.genie/spells/delegate-dont-do.md +0 -292
  146. package/.genie/spells/error-investigation-protocol.md +0 -328
  147. package/.genie/spells/evidence-based-completion.md +0 -273
  148. package/.genie/spells/experiment.md +0 -65
  149. package/.genie/spells/file-creation-protocol.md +0 -229
  150. package/.genie/spells/forge-integration.md +0 -281
  151. package/.genie/spells/forge-orchestration.md +0 -514
  152. package/.genie/spells/gather-context.md +0 -18
  153. package/.genie/spells/global-health-check.md +0 -34
  154. package/.genie/spells/global-noop-roundtrip.md +0 -25
  155. package/.genie/spells/install-genie.md +0 -1232
  156. package/.genie/spells/install.md +0 -82
  157. package/.genie/spells/investigate-before-commit.md +0 -112
  158. package/.genie/spells/know-yourself.md +0 -288
  159. package/.genie/spells/learn.md +0 -828
  160. package/.genie/spells/mcp-diagnostic-protocol.md +0 -246
  161. package/.genie/spells/mcp-first.md +0 -124
  162. package/.genie/spells/multi-step-execution.md +0 -67
  163. package/.genie/spells/orchestration-boundary-protocol.md +0 -256
  164. package/.genie/spells/orchestrator-not-implementor.md +0 -189
  165. package/.genie/spells/prompt.md +0 -746
  166. package/.genie/spells/reflect.md +0 -404
  167. package/.genie/spells/routing-decision-matrix.md +0 -368
  168. package/.genie/spells/run-in-parallel.md +0 -12
  169. package/.genie/spells/session-state-updater-example.md +0 -196
  170. package/.genie/spells/session-state-updater.md +0 -220
  171. package/.genie/spells/track-long-running-tasks.md +0 -133
  172. package/.genie/spells/troubleshoot-infrastructure.md +0 -176
  173. package/.genie/spells/upgrade-genie.md +0 -415
  174. package/.genie/spells/url-presentation-protocol.md +0 -301
  175. package/.genie/spells/wish-initiation.md +0 -158
  176. package/.genie/spells/wish-issue-linkage.md +0 -410
  177. package/.genie/spells/wish-lifecycle.md +0 -100
  178. package/.genie/state/provider-status.json +0 -3
  179. package/.genie/state/version.json +0 -16
  180. package/.genie/wishes/canonical-pgserve-pm2-supervision/WISH.md +0 -290
  181. package/.genie/wishes/pgserve-v2/BRIEF-from-genie-pgserve.md +0 -99
  182. package/.genie/wishes/pgserve-v2/WISH.md +0 -442
  183. package/.genie/wishes/release-system-genie-pattern/WISH.md +0 -268
  184. package/.genie/wishes/release-system-genie-pattern/validation.md +0 -205
  185. package/.gitguardian.yaml +0 -29
  186. package/.gitguardianignore +0 -16
  187. package/.github/workflows/ci.yml +0 -122
  188. package/.github/workflows/release.yml +0 -289
  189. package/.github/workflows/version.yml +0 -228
  190. package/.husky/pre-commit +0 -2
  191. package/AGENTS.md +0 -433
  192. package/CLAUDE.md +0 -1
  193. package/Makefile +0 -285
  194. package/assets/icon.ico +0 -0
  195. package/bun.lock +0 -435
  196. package/bunfig.toml +0 -28
  197. package/ecosystem.config.cjs +0 -23
  198. package/eslint.config.js +0 -63
  199. package/examples/multi-tenant-demo.js +0 -104
  200. package/install.sh +0 -123
  201. package/knip.json +0 -9
  202. package/scripts/test-bun-self-heal.sh +0 -163
  203. package/scripts/test-npx.sh +0 -60
  204. package/tests/audit.test.js +0 -189
  205. package/tests/backpressure.test.js +0 -167
  206. package/tests/benchmarks/runner.js +0 -1197
  207. package/tests/benchmarks/vector-generator.js +0 -368
  208. package/tests/cli-install.test.js +0 -322
  209. package/tests/control-db.test.js +0 -285
  210. package/tests/daemon-control.test.js +0 -171
  211. package/tests/daemon-fingerprint-integration.test.js +0 -111
  212. package/tests/daemon-pr24-regression.test.js +0 -198
  213. package/tests/fingerprint.test.js +0 -263
  214. package/tests/fixtures/240-orphan-seed.sql +0 -30
  215. package/tests/multi-tenant.test.js +0 -374
  216. package/tests/orphan-cleanup.test.js +0 -390
  217. package/tests/pg-version-regex.test.js +0 -129
  218. package/tests/quick-bench.js +0 -135
  219. package/tests/router-handshake-retry.test.js +0 -119
  220. package/tests/router-handshake-watchdog.test.js +0 -110
  221. package/tests/sdk.test.js +0 -71
  222. package/tests/stale-postmaster-pid.test.js +0 -85
  223. package/tests/stress-test.js +0 -439
  224. package/tests/sync-perf-test.js +0 -150
  225. package/tests/tcp-listen.test.js +0 -368
  226. package/tests/tenancy.test.js +0 -403
  227. package/tests/wrapper-supervision.test.js +0 -107
@@ -1,439 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- /**
4
- * pgserve Stress Test Suite
5
- *
6
- * Like PassMark but for PostgreSQL - progressive load testing with multiple scenarios.
7
- * Perfect for filming terminal under stress.
8
- *
9
- * Usage: bun tests/stress-test.js [port]
10
- */
11
-
12
- import pg from 'pg';
13
- const { Pool } = pg;
14
-
15
- const PORT = parseInt(process.argv[2]) || 8433;
16
-
17
- // ANSI colors
18
- const C = {
19
- reset: '\x1b[0m',
20
- bold: '\x1b[1m',
21
- dim: '\x1b[2m',
22
- green: '\x1b[32m',
23
- yellow: '\x1b[33m',
24
- cyan: '\x1b[36m',
25
- red: '\x1b[31m',
26
- magenta: '\x1b[35m',
27
- };
28
-
29
- const results = [];
30
- let globalPool = null;
31
-
32
- /**
33
- * Create a connection pool
34
- */
35
- function createPool(maxConnections) {
36
- return new Pool({
37
- host: '127.0.0.1',
38
- port: PORT,
39
- database: 'stresstest',
40
- user: 'postgres',
41
- password: 'postgres',
42
- max: maxConnections,
43
- idleTimeoutMillis: 30000,
44
- connectionTimeoutMillis: 10000,
45
- });
46
- }
47
-
48
- /**
49
- * Setup test tables
50
- */
51
- async function setup(pool) {
52
- const client = await pool.connect();
53
- try {
54
- await client.query(`
55
- DROP TABLE IF EXISTS stress_users CASCADE;
56
- DROP TABLE IF EXISTS stress_orders CASCADE;
57
- DROP TABLE IF EXISTS stress_logs CASCADE;
58
-
59
- CREATE TABLE stress_users (
60
- id SERIAL PRIMARY KEY,
61
- name TEXT NOT NULL,
62
- email TEXT,
63
- created_at TIMESTAMP DEFAULT NOW()
64
- );
65
-
66
- CREATE TABLE stress_orders (
67
- id SERIAL PRIMARY KEY,
68
- user_id INTEGER,
69
- amount DECIMAL(10,2),
70
- status TEXT DEFAULT 'pending',
71
- created_at TIMESTAMP DEFAULT NOW()
72
- );
73
-
74
- CREATE TABLE stress_logs (
75
- id SERIAL PRIMARY KEY,
76
- level TEXT,
77
- message TEXT,
78
- data JSONB,
79
- created_at TIMESTAMP DEFAULT NOW()
80
- );
81
-
82
- CREATE INDEX idx_orders_user ON stress_orders(user_id);
83
- CREATE INDEX idx_orders_status ON stress_orders(status);
84
- CREATE INDEX idx_logs_level ON stress_logs(level);
85
- `);
86
- } finally {
87
- client.release();
88
- }
89
- }
90
-
91
- /**
92
- * Print banner
93
- */
94
- function banner() {
95
- console.log(`
96
- ${C.cyan}${C.bold}╔════════════════════════════════════════════════════════════════╗
97
- ║ pgserve STRESS TEST SUITE ║
98
- ║ ║
99
- ║ Progressive load testing with multiple scenarios ║
100
- ╚════════════════════════════════════════════════════════════════╝${C.reset}
101
-
102
- ${C.dim}Target: postgresql://127.0.0.1:${PORT}/stresstest${C.reset}
103
- `);
104
- }
105
-
106
- /**
107
- * Print section header
108
- */
109
- function section(name, description) {
110
- console.log(`
111
- ${C.yellow}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
112
- ${C.bold}${C.cyan}▶ ${name}${C.reset}
113
- ${C.dim}${description}${C.reset}
114
- ${C.yellow}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
115
- `);
116
- }
117
-
118
- /**
119
- * Progress bar
120
- */
121
- function progressBar(current, total, width = 30) {
122
- const pct = Math.min(1, Math.max(0, current / total || 0));
123
- // filled is clamped to [0, width], so (width - filled) is always non-negative
124
- const filled = Math.max(0, Math.min(width, Math.round(pct * width)));
125
- const empty = width - filled;
126
- return `[${'█'.repeat(filled)}${'░'.repeat(empty)}] ${(pct * 100).toFixed(0)}%`;
127
- }
128
-
129
- /**
130
- * Run a test phase
131
- */
132
- async function runPhase(name, config) {
133
- const { connections, duration, workload } = config;
134
-
135
- const pool = createPool(connections);
136
- const latencies = [];
137
- let queries = 0;
138
- let errors = 0;
139
- let running = true;
140
-
141
- const startTime = performance.now();
142
-
143
- // Worker function
144
- async function worker(id) {
145
- while (running) {
146
- const start = performance.now();
147
- try {
148
- await workload(pool, id);
149
- latencies.push(performance.now() - start);
150
- queries++;
151
- } catch (err) {
152
- // Only count errors while test is running (not pool shutdown errors)
153
- if (running) {
154
- errors++;
155
- }
156
- }
157
- }
158
- }
159
-
160
- // Start workers
161
- const workers = Array.from({ length: connections }, (_, i) => worker(i));
162
-
163
- // Progress display
164
- const progressInterval = setInterval(() => {
165
- const elapsed = (performance.now() - startTime) / 1000;
166
- const qps = queries / elapsed;
167
- const progress = progressBar(elapsed, duration);
168
- process.stdout.write(`\r ${progress} | ${queries.toLocaleString()} queries | ${qps.toFixed(0)} QPS | ${errors} errors `);
169
- }, 200);
170
-
171
- // Wait for duration
172
- await new Promise(r => setTimeout(r, duration * 1000));
173
- running = false;
174
-
175
- // End pool immediately to unblock workers waiting for connections
176
- // This is necessary because workers may be stuck in pool.connect() or pool.query()
177
- await pool.end().catch(() => {});
178
-
179
- // Wait for workers to finish (with timeout in case any are still stuck)
180
- await Promise.race([
181
- Promise.allSettled(workers),
182
- new Promise(r => setTimeout(r, 2000)) // 2 second grace period
183
- ]);
184
- clearInterval(progressInterval);
185
-
186
- const totalTime = (performance.now() - startTime) / 1000;
187
-
188
- // Calculate stats
189
- latencies.sort((a, b) => a - b);
190
- const avg = latencies.length > 0 ? latencies.reduce((a, b) => a + b, 0) / latencies.length : 0;
191
- const p50 = latencies[Math.floor(latencies.length * 0.5)] || 0;
192
- const p95 = latencies[Math.floor(latencies.length * 0.95)] || 0;
193
- const p99 = latencies[Math.floor(latencies.length * 0.99)] || 0;
194
- const qps = queries / totalTime;
195
-
196
- const result = {
197
- name,
198
- connections,
199
- duration: totalTime,
200
- queries,
201
- errors,
202
- qps,
203
- latency: { avg, p50, p95, p99 }
204
- };
205
-
206
- results.push(result);
207
-
208
- console.log(`\r ${C.green}✓${C.reset} Complete: ${queries.toLocaleString()} queries in ${totalTime.toFixed(1)}s = ${C.bold}${qps.toFixed(0)} QPS${C.reset} `);
209
- console.log(` ${C.dim}Latency: avg=${avg.toFixed(1)}ms p50=${p50.toFixed(1)}ms p95=${p95.toFixed(1)}ms p99=${p99.toFixed(1)}ms${C.reset}`);
210
-
211
- return result;
212
- }
213
-
214
- // ============================================================================
215
- // WORKLOADS
216
- // ============================================================================
217
-
218
- const workloads = {
219
- // Pure inserts
220
- writeHeavy: async (pool, workerId) => {
221
- await pool.query(
222
- 'INSERT INTO stress_logs (level, message, data) VALUES ($1, $2, $3)',
223
- ['info', `Worker ${workerId} log entry`, JSON.stringify({ ts: Date.now(), worker: workerId })]
224
- );
225
- },
226
-
227
- // Pure reads
228
- readHeavy: async (pool) => {
229
- await pool.query('SELECT * FROM stress_logs ORDER BY id DESC LIMIT 50');
230
- },
231
-
232
- // Mixed CRUD
233
- mixed: async (pool, workerId) => {
234
- const op = Math.random();
235
- if (op < 0.3) {
236
- // 30% writes
237
- await pool.query(
238
- 'INSERT INTO stress_orders (user_id, amount, status) VALUES ($1, $2, $3)',
239
- [Math.floor(Math.random() * 1000), Math.random() * 1000, 'pending']
240
- );
241
- } else if (op < 0.5) {
242
- // 20% updates
243
- await pool.query(
244
- "UPDATE stress_orders SET status = $1 WHERE id = (SELECT id FROM stress_orders WHERE status = 'pending' LIMIT 1)",
245
- ['completed']
246
- );
247
- } else {
248
- // 50% reads
249
- await pool.query('SELECT * FROM stress_orders WHERE status = $1 LIMIT 20', ['pending']);
250
- }
251
- },
252
-
253
- // Complex queries with joins
254
- complex: async (pool) => {
255
- await pool.query(`
256
- SELECT o.*, COUNT(*) OVER() as total
257
- FROM stress_orders o
258
- WHERE o.created_at > NOW() - INTERVAL '1 hour'
259
- ORDER BY o.created_at DESC
260
- LIMIT 10
261
- `);
262
- },
263
-
264
- // Transaction heavy
265
- transactions: async (pool) => {
266
- const client = await pool.connect();
267
- try {
268
- await client.query('BEGIN');
269
- await client.query(
270
- 'INSERT INTO stress_users (name, email) VALUES ($1, $2) RETURNING id',
271
- [`User-${Date.now()}`, `user-${Date.now()}@test.com`]
272
- );
273
- await client.query(
274
- 'INSERT INTO stress_orders (user_id, amount) VALUES (currval(pg_get_serial_sequence(\'stress_users\', \'id\')), $1)',
275
- [Math.random() * 500]
276
- );
277
- await client.query('COMMIT');
278
- } catch (err) {
279
- await client.query('ROLLBACK');
280
- throw err;
281
- } finally {
282
- client.release();
283
- }
284
- },
285
- };
286
-
287
- // ============================================================================
288
- // MAIN TEST SUITE
289
- // ============================================================================
290
-
291
- async function runSuite() {
292
- banner();
293
-
294
- console.log(`${C.dim}Connecting and setting up test database...${C.reset}`);
295
- globalPool = createPool(5);
296
- await setup(globalPool);
297
- console.log(`${C.green}✓${C.reset} Setup complete\n`);
298
-
299
- // -------------------------------------------------------------------------
300
- // TEST 1: Connection Ramp-Up
301
- // -------------------------------------------------------------------------
302
- section('TEST 1: Connection Ramp-Up', 'Gradually increasing concurrent connections');
303
-
304
- for (const conns of [10, 50, 100, 250, 500]) {
305
- console.log(`\n ${C.cyan}→ ${conns} connections${C.reset}`);
306
- await runPhase(`ramp-${conns}`, {
307
- connections: conns,
308
- duration: 10,
309
- workload: workloads.mixed
310
- });
311
- await new Promise(r => setTimeout(r, 1000)); // Brief pause between phases
312
- }
313
-
314
- // -------------------------------------------------------------------------
315
- // TEST 2: Write Stress
316
- // -------------------------------------------------------------------------
317
- section('TEST 2: Write Stress', 'Heavy INSERT workload - 200 connections, 15 seconds');
318
-
319
- await runPhase('write-stress', {
320
- connections: 200,
321
- duration: 15,
322
- workload: workloads.writeHeavy
323
- });
324
-
325
- // -------------------------------------------------------------------------
326
- // TEST 3: Read Stress
327
- // -------------------------------------------------------------------------
328
- section('TEST 3: Read Stress', 'Heavy SELECT workload - 200 connections, 15 seconds');
329
-
330
- await runPhase('read-stress', {
331
- connections: 200,
332
- duration: 15,
333
- workload: workloads.readHeavy
334
- });
335
-
336
- // -------------------------------------------------------------------------
337
- // TEST 4: Mixed Workload
338
- // -------------------------------------------------------------------------
339
- section('TEST 4: Mixed Workload', 'Real-world CRUD simulation - 300 connections, 20 seconds');
340
-
341
- await runPhase('mixed-heavy', {
342
- connections: 300,
343
- duration: 20,
344
- workload: workloads.mixed
345
- });
346
-
347
- // -------------------------------------------------------------------------
348
- // TEST 5: Transaction Stress
349
- // -------------------------------------------------------------------------
350
- section('TEST 5: Transaction Stress', 'Multi-statement transactions - 150 connections, 15 seconds');
351
-
352
- await runPhase('transactions', {
353
- connections: 150,
354
- duration: 15,
355
- workload: workloads.transactions
356
- });
357
-
358
- // -------------------------------------------------------------------------
359
- // TEST 6: Peak Load
360
- // -------------------------------------------------------------------------
361
- section('TEST 6: Peak Load', 'Maximum stress - 500 connections, 20 seconds');
362
-
363
- await runPhase('peak-load', {
364
- connections: 500,
365
- duration: 20,
366
- workload: workloads.mixed
367
- });
368
-
369
- // -------------------------------------------------------------------------
370
- // TEST 7: Extreme Load
371
- // -------------------------------------------------------------------------
372
- section('TEST 7: Extreme Load', 'Near-limit stress - 750 connections, 15 seconds');
373
-
374
- await runPhase('extreme-load', {
375
- connections: 750,
376
- duration: 15,
377
- workload: workloads.mixed
378
- });
379
-
380
- // -------------------------------------------------------------------------
381
- // FINAL REPORT
382
- // -------------------------------------------------------------------------
383
- await globalPool.end();
384
-
385
- console.log(`
386
- ${C.cyan}${C.bold}
387
- ╔════════════════════════════════════════════════════════════════╗
388
- ║ FINAL RESULTS ║
389
- ╚════════════════════════════════════════════════════════════════╝${C.reset}
390
- `);
391
-
392
- // Summary table
393
- console.log(`${C.bold}Test Name Conn Queries QPS Avg P95 P99 Errors${C.reset}`);
394
- console.log(`${'─'.repeat(85)}`);
395
-
396
- let totalQueries = 0;
397
- let totalErrors = 0;
398
- let peakQps = 0;
399
-
400
- for (const r of results) {
401
- const name = r.name.padEnd(20);
402
- const conn = String(r.connections).padStart(4);
403
- const queries = r.queries.toLocaleString().padStart(10);
404
- const qps = r.qps.toFixed(0).padStart(7);
405
- const avg = r.latency.avg.toFixed(1).padStart(6) + 'ms';
406
- const p95 = r.latency.p95.toFixed(1).padStart(6) + 'ms';
407
- const p99 = r.latency.p99.toFixed(1).padStart(6) + 'ms';
408
- const errors = String(r.errors).padStart(6);
409
-
410
- const color = r.errors > 0 ? C.yellow : C.green;
411
- console.log(`${color}${name} ${conn} ${queries} ${qps} ${avg} ${p95} ${p99} ${errors}${C.reset}`);
412
-
413
- totalQueries += r.queries;
414
- totalErrors += r.errors;
415
- if (r.qps > peakQps) peakQps = r.qps;
416
- }
417
-
418
- console.log(`${'─'.repeat(85)}`);
419
-
420
- // Score calculation (arbitrary but fun)
421
- const score = Math.round((peakQps * 0.5) + (totalQueries / 1000) - (totalErrors * 10));
422
-
423
- console.log(`
424
- ${C.bold}Summary:${C.reset}
425
- Total Queries: ${totalQueries.toLocaleString()}
426
- Total Errors: ${totalErrors}
427
- Peak QPS: ${peakQps.toFixed(0)}
428
-
429
- ${C.magenta}${C.bold}╔═══════════════════════════════════╗
430
- ║ PGSERVE SCORE: ${String(score).padStart(6)} ║
431
- ╚═══════════════════════════════════╝${C.reset}
432
- `);
433
- }
434
-
435
- // Run
436
- runSuite().catch(err => {
437
- console.error(`${C.red}Error:${C.reset}`, err);
438
- process.exit(1);
439
- });
@@ -1,150 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Sync Performance Impact Test
4
- *
5
- * Verifies that enabling sync has ZERO impact on hot path performance.
6
- * Runs identical workloads with and without sync, compares results.
7
- */
8
-
9
- import { startMultiTenantServer } from '../src/index.js';
10
- import pg from 'pg';
11
-
12
- const ITERATIONS = 1000;
13
- const WARMUP = 100;
14
-
15
- async function runWorkload(port, label) {
16
- const client = new pg.Client({
17
- host: 'localhost',
18
- port,
19
- database: 'perftest',
20
- user: 'postgres',
21
- password: 'postgres'
22
- });
23
-
24
- await client.connect();
25
-
26
- // Create table
27
- await client.query('CREATE TABLE IF NOT EXISTS bench (id SERIAL PRIMARY KEY, data TEXT)');
28
- await client.query('TRUNCATE bench');
29
-
30
- // Warmup
31
- for (let i = 0; i < WARMUP; i++) {
32
- await client.query('INSERT INTO bench (data) VALUES ($1)', [`warmup-${i}`]);
33
- }
34
- await client.query('TRUNCATE bench');
35
-
36
- // Benchmark
37
- const start = process.hrtime.bigint();
38
-
39
- for (let i = 0; i < ITERATIONS; i++) {
40
- await client.query('INSERT INTO bench (data) VALUES ($1)', [`row-${i}`]);
41
- }
42
-
43
- const insertEnd = process.hrtime.bigint();
44
- const insertMs = Number(insertEnd - start) / 1_000_000;
45
-
46
- // Read benchmark
47
- const readStart = process.hrtime.bigint();
48
- for (let i = 0; i < ITERATIONS; i++) {
49
- await client.query('SELECT * FROM bench WHERE id = $1', [i % ITERATIONS + 1]);
50
- }
51
- const readEnd = process.hrtime.bigint();
52
- const readMs = Number(readEnd - readStart) / 1_000_000;
53
-
54
- await client.end();
55
-
56
- return {
57
- label,
58
- inserts: {
59
- total: insertMs,
60
- perOp: insertMs / ITERATIONS,
61
- opsPerSec: Math.round(ITERATIONS / (insertMs / 1000))
62
- },
63
- reads: {
64
- total: readMs,
65
- perOp: readMs / ITERATIONS,
66
- opsPerSec: Math.round(ITERATIONS / (readMs / 1000))
67
- }
68
- };
69
- }
70
-
71
- async function main() {
72
- console.log('='.repeat(60));
73
- console.log('SYNC PERFORMANCE IMPACT TEST');
74
- console.log('='.repeat(60));
75
- console.log(`Iterations: ${ITERATIONS} | Warmup: ${WARMUP}`);
76
- console.log();
77
-
78
- // Test 1: Without sync
79
- console.log('[1/2] Starting pgserve WITHOUT sync...');
80
- const serverNoSync = await startMultiTenantServer({
81
- port: 18001,
82
- logLevel: 'error'
83
- });
84
-
85
- await new Promise(r => setTimeout(r, 2000)); // Wait for server
86
- const resultNoSync = await runWorkload(18001, 'NO SYNC');
87
- await serverNoSync.stop();
88
-
89
- console.log(' Done.');
90
- console.log();
91
-
92
- // Test 2: With sync enabled (failing target - simulates sync overhead)
93
- console.log('[2/2] Starting pgserve WITH sync (failing target)...');
94
- const serverWithSync = await startMultiTenantServer({
95
- port: 18002,
96
- logLevel: 'error',
97
- syncTo: 'postgresql://dummy:dummy@localhost:59999/fake', // Intentionally failing
98
- syncDatabases: 'perftest'
99
- });
100
-
101
- await new Promise(r => setTimeout(r, 2000)); // Wait for server
102
- const resultWithSync = await runWorkload(18002, 'WITH SYNC');
103
- await serverWithSync.stop();
104
-
105
- console.log(' Done.');
106
- console.log();
107
-
108
- // Results
109
- console.log('='.repeat(60));
110
- console.log('RESULTS');
111
- console.log('='.repeat(60));
112
- console.log();
113
-
114
- console.log('INSERT PERFORMANCE:');
115
- console.log(` Without Sync: ${resultNoSync.inserts.opsPerSec.toLocaleString()} ops/sec (${resultNoSync.inserts.perOp.toFixed(2)} ms/op)`);
116
- console.log(` With Sync: ${resultWithSync.inserts.opsPerSec.toLocaleString()} ops/sec (${resultWithSync.inserts.perOp.toFixed(2)} ms/op)`);
117
-
118
- const insertDiff = ((resultWithSync.inserts.opsPerSec - resultNoSync.inserts.opsPerSec) / resultNoSync.inserts.opsPerSec * 100).toFixed(2);
119
- console.log(` Difference: ${insertDiff > 0 ? '+' : ''}${insertDiff}%`);
120
- console.log();
121
-
122
- console.log('READ PERFORMANCE:');
123
- console.log(` Without Sync: ${resultNoSync.reads.opsPerSec.toLocaleString()} ops/sec (${resultNoSync.reads.perOp.toFixed(2)} ms/op)`);
124
- console.log(` With Sync: ${resultWithSync.reads.opsPerSec.toLocaleString()} ops/sec (${resultWithSync.reads.perOp.toFixed(2)} ms/op)`);
125
-
126
- const readDiff = ((resultWithSync.reads.opsPerSec - resultNoSync.reads.opsPerSec) / resultNoSync.reads.opsPerSec * 100).toFixed(2);
127
- console.log(` Difference: ${readDiff > 0 ? '+' : ''}${readDiff}%`);
128
- console.log();
129
-
130
- // Verdict
131
- console.log('='.repeat(60));
132
- const threshold = 5; // 5% tolerance
133
- const insertPass = Math.abs(parseFloat(insertDiff)) < threshold;
134
- const readPass = Math.abs(parseFloat(readDiff)) < threshold;
135
-
136
- if (insertPass && readPass) {
137
- console.log('VERDICT: ✅ PASS - ZERO PERFORMANCE IMPACT');
138
- console.log(` (within ${threshold}% tolerance)`);
139
- } else {
140
- console.log('VERDICT: ❌ FAIL - PERFORMANCE REGRESSION DETECTED');
141
- }
142
- console.log('='.repeat(60));
143
-
144
- process.exit(insertPass && readPass ? 0 : 1);
145
- }
146
-
147
- main().catch(e => {
148
- console.error('Test failed:', e);
149
- process.exit(1);
150
- });