Files
Jyotisha/frontend/tests/bench-local-pg-roundtrips.ts
T
jesse-uxandGrok (agent loop) 3893783e4c
Independent Staging Quality Gate / publish (push) Canceled after 0s
Independent Staging Quality Gate / validate (push) Canceled after 4m24s
把数据库往返对照脚本挪出镜像会打包的目录
脚本从 frontend/scripts 挪到 frontend/tests,不再从会被打包的目录引用测试夹具。业务代码没动,对照数字没重跑。tsc 通过,lint 0 个错误(125 条原有警告),镜像导入守卫 2 通过、0 失败。

Co-Authored-By: Grok (agent loop)
2026-10-14 07:41:19 +08:00

225 lines
8.5 KiB
TypeScript

/**
* Count local-Postgres adapter roundtrips for a fictional homepage plus consult write.
* Run from frontend/: npx tsx tests/bench-local-pg-roundtrips.ts
* Prints JSON. Does not print connection strings.
*/
import { spawnSync } from "node:child_process";
import type { Pool, PoolClient } from "pg";
import {
closeLocalPostgresDataPools,
clearLocalPostgresMetadataCacheForTests,
createLocalPostgresDataClient,
localPostgresPoolForTests,
} from "../src/lib/db/local-postgres-client-core.ts";
import { startPostgresFixture } from "./helpers/postgres-fixture.ts";
const userId = "018f4e6d-7a11-7000-8000-0000000000bb";
const sessionId = "018f4e6d-7a11-7000-8000-0000000000cc";
const chartId = "018f4e6d-7a11-7000-8000-0000000000dd";
const schemaSql = `
create table if not exists public.profiles (
id uuid primary key,
display_name text
);
create table if not exists public.chart_profiles (
id uuid primary key,
user_id uuid not null,
label text,
updated_at timestamptz not null default now()
);
create table if not exists public.chat_sessions (
id uuid primary key,
user_id uuid not null,
title text,
updated_at timestamptz not null default now()
);
grant usage on schema public to authenticated, service_role;
grant select, insert, update, delete on public.profiles, public.chart_profiles, public.chat_sessions to authenticated, service_role;
insert into public.profiles (id, display_name) values ('${userId}', 'Fictional Person')
on conflict (id) do nothing;
insert into public.chart_profiles (id, user_id, label) values ('${chartId}', '${userId}', 'home')
on conflict (id) do nothing;
insert into public.chat_sessions (id, user_id, title) values ('${sessionId}', '${userId}', 'consult')
on conflict (id) do nothing;
create or replace function public.session_probe()
returns table(db_user text)
language sql stable
as $$ select current_user::text $$;
grant execute on function public.session_probe() to service_role, authenticated;
`;
type Mode = { cache: "on" | "off"; merged: "on" | "off" };
function percentile(values: number[], ratio: number): number {
const sorted = [...values].sort((left, right) => left - right);
const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(ratio * sorted.length) - 1));
return sorted[index] ?? 0;
}
function median(values: number[]): number {
const sorted = [...values].sort((left, right) => left - right);
return sorted[Math.floor(sorted.length / 2)] ?? 0;
}
const benchCounter = { roundtrips: 0 };
const queryWrapped = new WeakSet<object>();
async function withCountingPools(
urls: readonly string[],
run: () => Promise<unknown>,
): Promise<{ roundtrips: number; elapsedMs: number; payload: string }> {
const restores: Array<() => void> = [];
benchCounter.roundtrips = 0;
for (const url of urls) {
const pool = localPostgresPoolForTests(url) as Pool & {
connect: () => Promise<PoolClient>;
};
const original = pool.connect.bind(pool);
pool.connect = async () => {
const client = await original();
if (!queryWrapped.has(client)) {
queryWrapped.add(client);
const query = client.query.bind(client) as (input: unknown, values?: unknown) => Promise<unknown>;
client.query = ((input: unknown, values?: unknown) => {
benchCounter.roundtrips += 1;
return values === undefined ? query(input) : query(input, values);
}) as PoolClient["query"];
}
return client;
};
restores.push(() => {
pool.connect = original;
});
}
const started = performance.now();
try {
const payload = await run();
return {
roundtrips: benchCounter.roundtrips,
elapsedMs: performance.now() - started,
payload: JSON.stringify(payload),
};
} finally {
for (const restore of restores) restore();
}
}
async function sequence(url: string, serviceUrl: string): Promise<unknown[]> {
const user = createLocalPostgresDataClient(url, { id: userId, email: "person@example.com" }, "authenticated");
const service = createLocalPostgresDataClient(serviceUrl, null, "service_role");
const profile = await user.from("profiles").select("id,display_name").eq("id", userId).maybeSingle();
const charts = await user.from("chart_profiles").select("id,label").eq("user_id", userId).order("updated_at", { ascending: false }).limit(40);
const sessions = await user.from("chat_sessions").select("id,title").eq("user_id", userId).order("updated_at", { ascending: false }).limit(40);
const updated = await user.from("chat_sessions").update({ title: "consult-bench" }).eq("id", sessionId);
const upsertConflict = await user.from("chat_sessions").upsert(
{ id: sessionId, user_id: userId, title: "consult-bench" },
{ onConflict: "id" },
);
const upsertKey = await user.from("chat_sessions").upsert({ id: sessionId, user_id: userId, title: "consult-bench" });
const rpc = await service.rpc("session_probe");
const serviceRead = await service.from("profiles").select("id").eq("id", userId).maybeSingle();
return [profile, charts, sessions, updated, upsertConflict, upsertKey, rpc, serviceRead].map((result) => ({
data: result.data ?? null,
error: result.error ?? null,
count: "count" in result ? result.count ?? null : null,
}));
}
async function measure(url: string, serviceUrl: string, mode: Mode): Promise<{
roundtrips: number;
medianMs: number;
p95Ms: number;
payload: string;
groups: Array<{ roundtrips: number; medianMs: number }>;
}> {
process.env.JYOTISHA_PG_METADATA_CACHE = mode.cache;
process.env.JYOTISHA_PG_MERGED_SESSION_SETUP = mode.merged;
clearLocalPostgresMetadataCacheForTests();
await sequence(url, serviceUrl);
const groupMedians: number[] = [];
const groupRoundtrips: number[] = [];
const allTimes: number[] = [];
let payload = "";
for (let index = 0; index < 3; index += 1) {
const group: number[] = [];
let roundtrips = 0;
for (let repeat = 0; repeat < 50; repeat += 1) {
const sample = await withCountingPools([url, serviceUrl], () => sequence(url, serviceUrl));
group.push(sample.elapsedMs);
allTimes.push(sample.elapsedMs);
roundtrips += sample.roundtrips;
payload = sample.payload;
}
groupMedians.push(median(group));
groupRoundtrips.push(Math.round(roundtrips / 50));
}
return {
roundtrips: median(groupRoundtrips),
medianMs: median(groupMedians),
p95Ms: percentile(allTimes, 0.95),
payload,
groups: groupRoundtrips.map((roundtrips, index) => ({
roundtrips,
medianMs: Number((groupMedians[index] ?? 0).toFixed(2)),
})),
};
}
async function main(): Promise<void> {
const docker = spawnSync("docker", ["info"], { encoding: "utf8" });
if (docker.status !== 0) {
console.log(JSON.stringify({ ok: false, reason: "docker-unavailable" }));
process.exit(2);
}
const fixture = startPostgresFixture();
const appUrl = fixture.connectionUrl("app_runtime", "app-runtime-test-password");
const serviceUrl = fixture.connectionUrl("service_runtime", "service-runtime-test-password");
try {
fixture.psqlScriptAs("postgres", "postgres-test-password", schemaSql);
const modes: Record<string, Mode> = {
A: { cache: "off", merged: "off" },
B: { cache: "on", merged: "on" },
B1: { cache: "on", merged: "off" },
B2: { cache: "off", merged: "on" },
};
const results: Record<string, { roundtrips: number; medianMs: number; p95Ms: number; groups: Array<{ roundtrips: number; medianMs: number }> }> = {};
let payloadA = "";
let payloadMismatch = false;
for (const [name, mode] of Object.entries(modes)) {
const measured = await measure(appUrl, serviceUrl, mode);
results[name] = {
roundtrips: measured.roundtrips,
medianMs: Number(measured.medianMs.toFixed(2)),
p95Ms: Number(measured.p95Ms.toFixed(2)),
groups: measured.groups,
};
if (name === "A") payloadA = measured.payload;
else if (measured.payload !== payloadA) payloadMismatch = true;
clearLocalPostgresMetadataCacheForTests();
await closeLocalPostgresDataPools();
}
console.log(JSON.stringify({
ok: true,
repeats: 3,
repsPerGroup: 50,
payloadMismatch,
results,
node: process.version,
}));
} finally {
await closeLocalPostgresDataPools();
fixture.stop();
delete process.env.JYOTISHA_PG_METADATA_CACHE;
delete process.env.JYOTISHA_PG_MERGED_SESSION_SETUP;
}
}
main().catch((error: unknown) => {
const message = error instanceof Error ? error.message : "bench failed";
console.log(JSON.stringify({ ok: false, reason: message }));
process.exit(1);
});