Skip to content

Commit e80b614

Browse files
fix(benchmark): persist remote fixture doc ids
1 parent 3962697 commit e80b614

2 files changed

Lines changed: 96 additions & 3 deletions

File tree

docs/BENCHMARKS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ pnpm benchmark:sync:remote -- \
116116
--server-fixture-cache=reuse
117117
```
118118

119+
For remote targets, `prime` now records the exact fixture doc ID locally under `tmp/sqlite-node-sync-bench/server-fixtures/`. That means a fresh endpoint can be primed once with `--server-fixture-cache=rebuild`, and later `--server-fixture-cache=reuse` runs on the same machine can reopen that exact remote fixture doc instead of relying on historical deterministic fixture residue.
120+
119121
By default, the local sync target runs the Postgres sync server in a spawned child process so local and remote measurements are closer to each other. When you add `--profile-backend`, the local target intentionally switches to the in-process server so per-backend timings are visible inside the benchmark process.
120122

121123
Local server benchmarks now seed the Postgres backend directly before the timer starts. That keeps the measured path honest, because the actual sync to the client still goes through the real websocket server, while avoiding huge protocol-seed setup costs that are not part of the benchmark question.

packages/treecrdt-sqlite-node/scripts/bench-sync.ts

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ type SyncBenchSample = {
101101
type PreparedServerFixture = {
102102
docId: string;
103103
cacheKey?: string;
104-
cacheStatus: "disabled" | "hit" | "miss" | "rebuild" | "assumed";
104+
cacheStatus: "disabled" | "hit" | "miss" | "rebuild" | "assumed" | "manifest";
105105
seedUploadMs?: number;
106106
seedAllReadyMs?: number;
107107
filterReadyMs?: number;
@@ -116,6 +116,7 @@ type SyncBenchConnection = {
116116
type SyncBenchTargetRuntime = {
117117
id: Exclude<SyncBenchTargetId, "direct">;
118118
serverProcess: "child-process" | "in-process" | "remote";
119+
fixtureCacheScope?: string;
119120
connect: (docId: string) => Promise<SyncBenchConnection>;
120121
seedOps?: (docId: string, ops: Operation[]) => Promise<void>;
121122
inspectDoc?: (
@@ -239,6 +240,14 @@ const SYNC_BENCH_POST_SEED_WAIT_MS = Math.max(
239240
const DEFAULT_SERVER_FIXTURE_CACHE_MODE: ServerFixtureCacheMode = "reuse";
240241
const SYNC_BENCH_SERVER_FIXTURE_CACHE_VERSION = "2026-03-21-v1";
241242
const SERVER_READY_POLL_MS = 100;
243+
const BENCH_REPO_ROOT = repoRootFromImportMeta(import.meta.url, 3);
244+
const SERVER_FIXTURE_MANIFEST_VERSION = 1;
245+
const SERVER_FIXTURE_MANIFEST_DIR = path.join(
246+
BENCH_REPO_ROOT,
247+
"tmp",
248+
"sqlite-node-sync-bench",
249+
"server-fixtures"
250+
);
242251

243252
function envInt(name: string): number | undefined {
244253
const raw = process.env[name];
@@ -1181,6 +1190,7 @@ async function createLocalPostgresSyncServerTarget(
11811190
return {
11821191
id: "local-postgres-sync-server",
11831192
serverProcess: "in-process",
1193+
fixtureCacheScope: "local-postgres-sync-server",
11841194
connect: async (docId) =>
11851195
await connectToSyncServer(`ws://127.0.0.1:${server.port}`, docId),
11861196
seedOps,
@@ -1244,6 +1254,7 @@ async function createLocalPostgresSyncServerTarget(
12441254
return {
12451255
id: "local-postgres-sync-server",
12461256
serverProcess: "child-process",
1257+
fixtureCacheScope: "local-postgres-sync-server",
12471258
connect: async (docId) =>
12481259
await connectToSyncServer(`ws://127.0.0.1:${port}`, docId),
12491260
seedOps,
@@ -1348,6 +1359,7 @@ function createRemoteSyncServerTarget(
13481359
return {
13491360
id: "remote-sync-server",
13501361
serverProcess: "remote",
1362+
fixtureCacheScope: baseUrl,
13511363
connect: async (docId) =>
13521364
await connectToSyncServer(baseUrl, docId, { client: "builtin" }),
13531365
close: async () => {},
@@ -1684,6 +1696,76 @@ function createServerFixtureCacheKey(
16841696
return hash.digest("hex").slice(0, 24);
16851697
}
16861698

1699+
function fixtureCacheScopeForRuntime(runtime: SyncBenchTargetRuntime): string {
1700+
return runtime.fixtureCacheScope ?? runtime.id;
1701+
}
1702+
1703+
function fixtureManifestPath(
1704+
runtime: SyncBenchTargetRuntime,
1705+
cacheKey: string
1706+
): string {
1707+
const hash = createHash("sha256");
1708+
updateFixtureHashWithString(hash, fixtureCacheScopeForRuntime(runtime));
1709+
updateFixtureHashWithString(hash, cacheKey);
1710+
return path.join(
1711+
SERVER_FIXTURE_MANIFEST_DIR,
1712+
`${runtime.id}-${hash.digest("hex").slice(0, 24)}.json`
1713+
);
1714+
}
1715+
1716+
async function readServerFixtureManifest(
1717+
runtime: SyncBenchTargetRuntime,
1718+
cacheKey: string
1719+
): Promise<string | undefined> {
1720+
try {
1721+
const raw = JSON.parse(
1722+
await fs.readFile(fixtureManifestPath(runtime, cacheKey), "utf8")
1723+
) as {
1724+
version?: number;
1725+
runtimeId?: string;
1726+
scope?: string;
1727+
cacheKey?: string;
1728+
docId?: string;
1729+
};
1730+
if (
1731+
raw.version !== SERVER_FIXTURE_MANIFEST_VERSION ||
1732+
raw.runtimeId !== runtime.id ||
1733+
raw.scope !== fixtureCacheScopeForRuntime(runtime) ||
1734+
raw.cacheKey !== cacheKey ||
1735+
typeof raw.docId !== "string" ||
1736+
raw.docId.length === 0
1737+
) {
1738+
return undefined;
1739+
}
1740+
return raw.docId;
1741+
} catch {
1742+
return undefined;
1743+
}
1744+
}
1745+
1746+
async function writeServerFixtureManifest(
1747+
runtime: SyncBenchTargetRuntime,
1748+
cacheKey: string,
1749+
docId: string
1750+
): Promise<void> {
1751+
await fs.mkdir(SERVER_FIXTURE_MANIFEST_DIR, { recursive: true });
1752+
await fs.writeFile(
1753+
fixtureManifestPath(runtime, cacheKey),
1754+
JSON.stringify(
1755+
{
1756+
version: SERVER_FIXTURE_MANIFEST_VERSION,
1757+
runtimeId: runtime.id,
1758+
scope: fixtureCacheScopeForRuntime(runtime),
1759+
cacheKey,
1760+
docId,
1761+
writtenAt: new Date().toISOString(),
1762+
},
1763+
null,
1764+
2
1765+
)
1766+
);
1767+
}
1768+
16871769
async function prepareServerFixture(
16881770
runtime: SyncBenchTargetRuntime,
16891771
bench: ReturnType<typeof buildSyncBenchCase>,
@@ -1695,9 +1777,15 @@ async function prepareServerFixture(
16951777
const cacheKey =
16961778
cacheMode === "off" ? undefined : createServerFixtureCacheKey(bench);
16971779
const hasResettableFixture = runtime.resetDoc != null;
1780+
const manifestDocId =
1781+
cacheMode === "reuse" && cacheKey != null && !runtime.inspectDoc
1782+
? await readServerFixtureManifest(runtime, cacheKey)
1783+
: undefined;
16981784
const docId =
16991785
cacheMode === "off"
17001786
? `sqlite-node-sync-bench-${runtime.id}-fixture-${crypto.randomUUID()}`
1787+
: cacheMode === "reuse" && manifestDocId != null
1788+
? manifestDocId
17011789
: cacheMode === "rebuild" && !hasResettableFixture
17021790
? `sqlite-node-sync-bench-${runtime.id}-fixture-${cacheKey}-${crypto.randomUUID()}`
17031791
: `sqlite-node-sync-bench-${runtime.id}-fixture-${cacheKey}`;
@@ -1722,7 +1810,7 @@ async function prepareServerFixture(
17221810
return {
17231811
docId,
17241812
cacheKey,
1725-
cacheStatus: "assumed",
1813+
cacheStatus: manifestDocId != null ? "manifest" : "assumed",
17261814
};
17271815
}
17281816
if (cacheMode !== "off") {
@@ -1755,6 +1843,9 @@ async function prepareServerFixture(
17551843
);
17561844
}
17571845
const filterReadyMs = performance.now() - filterReadyStartedAt;
1846+
if (cacheKey != null && !runtime.inspectDoc) {
1847+
await writeServerFixtureManifest(runtime, cacheKey, docId);
1848+
}
17581849
return {
17591850
docId,
17601851
cacheKey,
@@ -2341,7 +2432,7 @@ async function primeServerFixtureCase(
23412432

23422433
async function main() {
23432434
const argv = process.argv.slice(2);
2344-
const repoRoot = repoRootFromImportMeta(import.meta.url, 3);
2435+
const repoRoot = BENCH_REPO_ROOT;
23452436

23462437
const iterationsOverride = parseIterationsOverride(argv);
23472438
const warmupIterationsOverride = parseWarmupIterationsOverride(argv);

0 commit comments

Comments
 (0)