pgserve 2.1.3 → 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.
- package/CHANGELOG.md +86 -0
- package/README.md +105 -1
- package/bin/autopg-wrapper.cjs +16 -0
- package/bin/pgserve-wrapper.cjs +31 -6
- package/bin/postgres-server.js +56 -0
- package/console/README.md +131 -0
- package/console/api.js +173 -0
- package/console/app.jsx +483 -0
- package/console/colors_and_type.css +227 -0
- package/console/components.jsx +167 -0
- package/console/console.css +1666 -0
- package/console/data.jsx +350 -0
- package/console/index.html +31 -0
- package/console/screens/databases.jsx +5 -0
- package/console/screens/health.jsx +5 -0
- package/console/screens/ingress.jsx +5 -0
- package/console/screens/optimizer.jsx +5 -0
- package/console/screens/rlm-sim.jsx +5 -0
- package/console/screens/rlm-trace.jsx +5 -0
- package/console/screens/security.jsx +5 -0
- package/console/screens/settings.jsx +611 -0
- package/console/screens/sql.jsx +5 -0
- package/console/screens/sync.jsx +5 -0
- package/console/screens/tables.jsx +5 -0
- package/console/tweaks-panel.jsx +425 -0
- package/package.json +11 -1
- package/src/cli-config.cjs +310 -0
- package/src/cli-install.cjs +98 -11
- package/src/cli-restart.cjs +228 -0
- package/src/cli-ui.cjs +580 -0
- package/src/cluster.js +43 -38
- package/src/postgres.js +141 -19
- package/src/settings-loader.cjs +235 -0
- package/src/settings-migrate.cjs +212 -0
- package/src/settings-pg-args.cjs +146 -0
- package/src/settings-schema.cjs +422 -0
- package/src/settings-validator.cjs +416 -0
- package/src/settings-writer.cjs +288 -0
- package/.claude/context/windows-debug.md +0 -119
- package/.genie/AGENTS.md +0 -15
- package/.genie/agents/README.md +0 -110
- package/.genie/agents/analyze.md +0 -176
- package/.genie/agents/forge.md +0 -290
- package/.genie/agents/garbage-cleaner.md +0 -324
- package/.genie/agents/garbage-collector.md +0 -596
- package/.genie/agents/github-issue-gc.md +0 -618
- package/.genie/agents/review.md +0 -380
- package/.genie/agents/semantic-analyzer/find-duplicates.md +0 -90
- package/.genie/agents/semantic-analyzer/find-orphans.md +0 -99
- package/.genie/agents/semantic-analyzer.md +0 -101
- package/.genie/agents/update.md +0 -182
- package/.genie/agents/wish.md +0 -357
- package/.genie/brainstorms/pgserve-v2/DESIGN.md +0 -174
- package/.genie/code/AGENTS.md +0 -694
- package/.genie/code/agents/audit/risk.md +0 -173
- package/.genie/code/agents/audit/security.md +0 -189
- package/.genie/code/agents/audit.md +0 -145
- package/.genie/code/agents/challenge.md +0 -230
- package/.genie/code/agents/change-reviewer.md +0 -295
- package/.genie/code/agents/code-garbage-collector.md +0 -425
- package/.genie/code/agents/code-quality.md +0 -410
- package/.genie/code/agents/commit-suggester.md +0 -255
- package/.genie/code/agents/commit.md +0 -124
- package/.genie/code/agents/consensus.md +0 -204
- package/.genie/code/agents/daily-standup.md +0 -722
- package/.genie/code/agents/docgen.md +0 -48
- package/.genie/code/agents/explore.md +0 -79
- package/.genie/code/agents/fix.md +0 -100
- package/.genie/code/agents/git/commit-advisory.md +0 -219
- package/.genie/code/agents/git/workflows/issue.md +0 -244
- package/.genie/code/agents/git/workflows/pr.md +0 -179
- package/.genie/code/agents/git/workflows/release.md +0 -460
- package/.genie/code/agents/git/workflows/report.md +0 -342
- package/.genie/code/agents/git.md +0 -432
- package/.genie/code/agents/implementor.md +0 -161
- package/.genie/code/agents/install.md +0 -515
- package/.genie/code/agents/issue-creator.md +0 -344
- package/.genie/code/agents/polish.md +0 -116
- package/.genie/code/agents/qa.md +0 -653
- package/.genie/code/agents/refactor.md +0 -294
- package/.genie/code/agents/release.md +0 -1129
- package/.genie/code/agents/roadmap.md +0 -885
- package/.genie/code/agents/tests.md +0 -557
- package/.genie/code/agents/tracer.md +0 -50
- package/.genie/code/agents/update/upstream-update.md +0 -85
- package/.genie/code/agents/update/versions/generic-update.md +0 -305
- package/.genie/code/agents/vibe.md +0 -1317
- package/.genie/code/spells/agent-configuration.md +0 -58
- package/.genie/code/spells/automated-rc-publishing.md +0 -106
- package/.genie/code/spells/branch-tracker-guidance.md +0 -28
- package/.genie/code/spells/debug.md +0 -320
- package/.genie/code/spells/emoji-naming-convention.md +0 -303
- package/.genie/code/spells/evidence-storage.md +0 -26
- package/.genie/code/spells/file-naming-rules.md +0 -35
- package/.genie/code/spells/forge-code-blueprints.md +0 -195
- package/.genie/code/spells/genie-integration.md +0 -153
- package/.genie/code/spells/publishing-protocol.md +0 -61
- package/.genie/code/spells/team-consultation-protocol.md +0 -284
- package/.genie/code/spells/tool-requirements.md +0 -20
- package/.genie/code/spells/triad-maintenance-protocol.md +0 -154
- package/.genie/code/teams/tech-council/council.md +0 -328
- package/.genie/code/teams/tech-council/jt.md +0 -352
- package/.genie/code/teams/tech-council/nayr.md +0 -305
- package/.genie/code/teams/tech-council/oettam.md +0 -375
- package/.genie/neurons/README.md +0 -193
- package/.genie/neurons/forge.md +0 -106
- package/.genie/neurons/genie.md +0 -63
- package/.genie/neurons/review.md +0 -106
- package/.genie/neurons/wish.md +0 -104
- package/.genie/product/README.md +0 -20
- package/.genie/product/cli-automation.md +0 -359
- package/.genie/product/environment.md +0 -60
- package/.genie/product/mission.md +0 -60
- package/.genie/product/roadmap.md +0 -44
- package/.genie/product/tech-stack.md +0 -34
- package/.genie/product/templates/context-template.md +0 -218
- package/.genie/product/templates/qa-done-report-template.md +0 -68
- package/.genie/product/templates/review-report-template.md +0 -89
- package/.genie/product/templates/wish-template.md +0 -120
- package/.genie/scripts/helpers/analyze-commit.js +0 -195
- package/.genie/scripts/helpers/bullet-counter.js +0 -194
- package/.genie/scripts/helpers/bullet-find.js +0 -289
- package/.genie/scripts/helpers/bullet-id.js +0 -244
- package/.genie/scripts/helpers/check-secrets.js +0 -237
- package/.genie/scripts/helpers/count-tokens.js +0 -200
- package/.genie/scripts/helpers/create-frontmatter.js +0 -456
- package/.genie/scripts/helpers/detect-markers.js +0 -293
- package/.genie/scripts/helpers/detect-todos.js +0 -267
- package/.genie/scripts/helpers/detect-unlabeled-blocks.js +0 -135
- package/.genie/scripts/helpers/embeddings.js +0 -344
- package/.genie/scripts/helpers/find-empty-sections.js +0 -158
- package/.genie/scripts/helpers/index.js +0 -319
- package/.genie/scripts/helpers/validate-frontmatter.js +0 -578
- package/.genie/scripts/helpers/validate-links.js +0 -207
- package/.genie/scripts/helpers/validate-paths.js +0 -373
- package/.genie/spells/README.md +0 -9
- package/.genie/spells/ace-protocol.md +0 -118
- package/.genie/spells/ask-one-at-a-time.md +0 -175
- package/.genie/spells/backup-analyzer.md +0 -542
- package/.genie/spells/blocker.md +0 -12
- package/.genie/spells/break-things-move-fast.md +0 -56
- package/.genie/spells/context-candidates.md +0 -72
- package/.genie/spells/context-critic.md +0 -51
- package/.genie/spells/defer-to-expertise.md +0 -278
- package/.genie/spells/delegate-dont-do.md +0 -292
- package/.genie/spells/error-investigation-protocol.md +0 -328
- package/.genie/spells/evidence-based-completion.md +0 -273
- package/.genie/spells/experiment.md +0 -65
- package/.genie/spells/file-creation-protocol.md +0 -229
- package/.genie/spells/forge-integration.md +0 -281
- package/.genie/spells/forge-orchestration.md +0 -514
- package/.genie/spells/gather-context.md +0 -18
- package/.genie/spells/global-health-check.md +0 -34
- package/.genie/spells/global-noop-roundtrip.md +0 -25
- package/.genie/spells/install-genie.md +0 -1232
- package/.genie/spells/install.md +0 -82
- package/.genie/spells/investigate-before-commit.md +0 -112
- package/.genie/spells/know-yourself.md +0 -288
- package/.genie/spells/learn.md +0 -828
- package/.genie/spells/mcp-diagnostic-protocol.md +0 -246
- package/.genie/spells/mcp-first.md +0 -124
- package/.genie/spells/multi-step-execution.md +0 -67
- package/.genie/spells/orchestration-boundary-protocol.md +0 -256
- package/.genie/spells/orchestrator-not-implementor.md +0 -189
- package/.genie/spells/prompt.md +0 -746
- package/.genie/spells/reflect.md +0 -404
- package/.genie/spells/routing-decision-matrix.md +0 -368
- package/.genie/spells/run-in-parallel.md +0 -12
- package/.genie/spells/session-state-updater-example.md +0 -196
- package/.genie/spells/session-state-updater.md +0 -220
- package/.genie/spells/track-long-running-tasks.md +0 -133
- package/.genie/spells/troubleshoot-infrastructure.md +0 -176
- package/.genie/spells/upgrade-genie.md +0 -415
- package/.genie/spells/url-presentation-protocol.md +0 -301
- package/.genie/spells/wish-initiation.md +0 -158
- package/.genie/spells/wish-issue-linkage.md +0 -410
- package/.genie/spells/wish-lifecycle.md +0 -100
- package/.genie/state/provider-status.json +0 -3
- package/.genie/state/version.json +0 -16
- package/.genie/wishes/canonical-pgserve-pm2-supervision/WISH.md +0 -290
- package/.genie/wishes/pgserve-v2/BRIEF-from-genie-pgserve.md +0 -99
- package/.genie/wishes/pgserve-v2/WISH.md +0 -442
- package/.genie/wishes/release-system-genie-pattern/WISH.md +0 -268
- package/.genie/wishes/release-system-genie-pattern/validation.md +0 -205
- package/.gitguardian.yaml +0 -29
- package/.gitguardianignore +0 -16
- package/.github/workflows/ci.yml +0 -122
- package/.github/workflows/release.yml +0 -289
- package/.github/workflows/version.yml +0 -228
- package/.husky/pre-commit +0 -2
- package/AGENTS.md +0 -433
- package/CLAUDE.md +0 -1
- package/Makefile +0 -285
- package/assets/icon.ico +0 -0
- package/bun.lock +0 -435
- package/bunfig.toml +0 -28
- package/ecosystem.config.cjs +0 -23
- package/eslint.config.js +0 -63
- package/examples/multi-tenant-demo.js +0 -104
- package/install.sh +0 -123
- package/knip.json +0 -9
- package/scripts/test-bun-self-heal.sh +0 -163
- package/scripts/test-npx.sh +0 -60
- package/tests/audit.test.js +0 -189
- package/tests/backpressure.test.js +0 -167
- package/tests/benchmarks/runner.js +0 -1197
- package/tests/benchmarks/vector-generator.js +0 -368
- package/tests/cli-install.test.js +0 -322
- package/tests/control-db.test.js +0 -285
- package/tests/daemon-args.test.js +0 -86
- package/tests/daemon-control.test.js +0 -171
- package/tests/daemon-fingerprint-integration.test.js +0 -111
- package/tests/daemon-pr24-regression.test.js +0 -198
- package/tests/fingerprint.test.js +0 -263
- package/tests/fixtures/240-orphan-seed.sql +0 -30
- package/tests/multi-tenant.test.js +0 -374
- package/tests/orphan-cleanup.test.js +0 -390
- package/tests/pg-version-regex.test.js +0 -129
- package/tests/quick-bench.js +0 -135
- package/tests/router-handshake-retry.test.js +0 -119
- package/tests/router-handshake-watchdog.test.js +0 -110
- package/tests/sdk.test.js +0 -71
- package/tests/stale-postmaster-pid.test.js +0 -85
- package/tests/stress-test.js +0 -439
- package/tests/sync-perf-test.js +0 -150
- package/tests/tcp-listen.test.js +0 -368
- package/tests/tenancy.test.js +0 -403
- package/tests/wrapper-supervision.test.js +0 -107
package/tests/stress-test.js
DELETED
|
@@ -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
|
-
});
|
package/tests/sync-perf-test.js
DELETED
|
@@ -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
|
-
});
|