Skip to content

Commit f6e7bf6

Browse files
fix(benchmark): persist remote fixture doc ids
1 parent 4e30592 commit f6e7bf6

2 files changed

Lines changed: 94 additions & 5 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: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ type PreparedServerFixture = {
9191
docId: string;
9292
cacheKey?: string;
9393
cacheStatus: 'disabled' | 'hit' | 'miss' | 'rebuild' | 'assumed';
94+
cacheStatus: 'disabled' | 'hit' | 'miss' | 'rebuild' | 'assumed' | 'manifest';
9495
seedUploadMs?: number;
9596
seedAllReadyMs?: number;
9697
filterReadyMs?: number;
@@ -105,6 +106,7 @@ type SyncBenchConnection = {
105106
type SyncBenchTargetRuntime = {
106107
id: Exclude<SyncBenchTargetId, 'direct'>;
107108
serverProcess: 'child-process' | 'in-process' | 'remote';
109+
fixtureCacheScope?: string;
108110
connect: (docId: string) => Promise<SyncBenchConnection>;
109111
seedOps?: (docId: string, ops: Operation[]) => Promise<void>;
110112
inspectDoc?: (docId: string) => Promise<{ allCount: number; maxLamport: number }>;
@@ -229,6 +231,14 @@ const SYNC_BENCH_POST_SEED_WAIT_MS = Math.max(0, envInt('SYNC_BENCH_POST_SEED_WA
229231
const DEFAULT_SERVER_FIXTURE_CACHE_MODE: ServerFixtureCacheMode = 'reuse';
230232
const SYNC_BENCH_SERVER_FIXTURE_CACHE_VERSION = '2026-03-21-v1';
231233
const SERVER_READY_POLL_MS = 100;
234+
const BENCH_REPO_ROOT = repoRootFromImportMeta(import.meta.url, 3);
235+
const SERVER_FIXTURE_MANIFEST_VERSION = 1;
236+
const SERVER_FIXTURE_MANIFEST_DIR = path.join(
237+
BENCH_REPO_ROOT,
238+
'tmp',
239+
'sqlite-node-sync-bench',
240+
'server-fixtures',
241+
);
232242

233243
function envInt(name: string): number | undefined {
234244
const raw = process.env[name];
@@ -1163,6 +1173,7 @@ async function createLocalPostgresSyncServerTarget(
11631173
id: 'local-postgres-sync-server',
11641174
serverProcess: 'in-process',
11651175
connect: async (docId) => await connectToSyncServer(`ws://127.0.0.1:${server.port}`, docId),
1176+
fixtureCacheScope: 'local-postgres-sync-server',
11661177
seedOps,
11671178
inspectDoc,
11681179
resetDoc,
@@ -1221,6 +1232,7 @@ async function createLocalPostgresSyncServerTarget(
12211232
id: 'local-postgres-sync-server',
12221233
serverProcess: 'child-process',
12231234
connect: async (docId) => await connectToSyncServer(`ws://127.0.0.1:${port}`, docId),
1235+
fixtureCacheScope: 'local-postgres-sync-server',
12241236
seedOps,
12251237
inspectDoc,
12261238
resetDoc,
@@ -1329,6 +1341,7 @@ function createRemoteSyncServerTarget(baseUrl: string): SyncBenchTargetRuntime {
13291341
id: 'remote-sync-server',
13301342
serverProcess: 'remote',
13311343
connect: async (docId) => await connectToSyncServer(baseUrl, docId, { client: 'builtin' }),
1344+
fixtureCacheScope: baseUrl,
13321345
close: async () => {},
13331346
};
13341347
}
@@ -1656,6 +1669,71 @@ function createServerFixtureCacheKey(bench: ReturnType<typeof buildSyncBenchCase
16561669
return hash.digest('hex').slice(0, 24);
16571670
}
16581671

1672+
function fixtureCacheScopeForRuntime(runtime: SyncBenchTargetRuntime): string {
1673+
return runtime.fixtureCacheScope ?? runtime.id;
1674+
}
1675+
1676+
function fixtureManifestPath(runtime: SyncBenchTargetRuntime, cacheKey: string): string {
1677+
const hash = createHash('sha256');
1678+
updateFixtureHashWithString(hash, fixtureCacheScopeForRuntime(runtime));
1679+
updateFixtureHashWithString(hash, cacheKey);
1680+
return path.join(
1681+
SERVER_FIXTURE_MANIFEST_DIR,
1682+
`${runtime.id}-${hash.digest('hex').slice(0, 24)}.json`,
1683+
);
1684+
}
1685+
1686+
async function readServerFixtureManifest(
1687+
runtime: SyncBenchTargetRuntime,
1688+
cacheKey: string,
1689+
): Promise<string | undefined> {
1690+
try {
1691+
const raw = JSON.parse(await fs.readFile(fixtureManifestPath(runtime, cacheKey), 'utf8')) as {
1692+
version?: number;
1693+
runtimeId?: string;
1694+
scope?: string;
1695+
cacheKey?: string;
1696+
docId?: string;
1697+
};
1698+
if (
1699+
raw.version !== SERVER_FIXTURE_MANIFEST_VERSION ||
1700+
raw.runtimeId !== runtime.id ||
1701+
raw.scope !== fixtureCacheScopeForRuntime(runtime) ||
1702+
raw.cacheKey !== cacheKey ||
1703+
typeof raw.docId !== 'string' ||
1704+
raw.docId.length === 0
1705+
) {
1706+
return undefined;
1707+
}
1708+
return raw.docId;
1709+
} catch {
1710+
return undefined;
1711+
}
1712+
}
1713+
1714+
async function writeServerFixtureManifest(
1715+
runtime: SyncBenchTargetRuntime,
1716+
cacheKey: string,
1717+
docId: string,
1718+
): Promise<void> {
1719+
await fs.mkdir(SERVER_FIXTURE_MANIFEST_DIR, { recursive: true });
1720+
await fs.writeFile(
1721+
fixtureManifestPath(runtime, cacheKey),
1722+
JSON.stringify(
1723+
{
1724+
version: SERVER_FIXTURE_MANIFEST_VERSION,
1725+
runtimeId: runtime.id,
1726+
scope: fixtureCacheScopeForRuntime(runtime),
1727+
cacheKey,
1728+
docId,
1729+
writtenAt: new Date().toISOString(),
1730+
},
1731+
null,
1732+
2,
1733+
),
1734+
);
1735+
}
1736+
16591737
async function prepareServerFixture(
16601738
runtime: SyncBenchTargetRuntime,
16611739
bench: ReturnType<typeof buildSyncBenchCase>,
@@ -1666,12 +1744,18 @@ async function prepareServerFixture(
16661744
const prepareStartedAt = performance.now();
16671745
const cacheKey = cacheMode === 'off' ? undefined : createServerFixtureCacheKey(bench);
16681746
const hasResettableFixture = runtime.resetDoc != null;
1747+
const manifestDocId =
1748+
cacheMode === 'reuse' && cacheKey != null && !runtime.inspectDoc
1749+
? await readServerFixtureManifest(runtime, cacheKey)
1750+
: undefined;
16691751
const docId =
16701752
cacheMode === 'off'
16711753
? `sqlite-node-sync-bench-${runtime.id}-fixture-${crypto.randomUUID()}`
1672-
: cacheMode === 'rebuild' && !hasResettableFixture
1673-
? `sqlite-node-sync-bench-${runtime.id}-fixture-${cacheKey}-${crypto.randomUUID()}`
1674-
: `sqlite-node-sync-bench-${runtime.id}-fixture-${cacheKey}`;
1754+
: cacheMode === 'reuse' && manifestDocId != null
1755+
? manifestDocId
1756+
: cacheMode === 'rebuild' && !hasResettableFixture
1757+
? `sqlite-node-sync-bench-${runtime.id}-fixture-${cacheKey}-${crypto.randomUUID()}`
1758+
: `sqlite-node-sync-bench-${runtime.id}-fixture-${cacheKey}`;
16751759
if (cacheMode === 'reuse' && runtime.inspectDoc) {
16761760
try {
16771761
const current = await runtime.inspectDoc(docId);
@@ -1690,7 +1774,7 @@ async function prepareServerFixture(
16901774
return {
16911775
docId,
16921776
cacheKey,
1693-
cacheStatus: 'assumed',
1777+
cacheStatus: manifestDocId != null ? 'manifest' : 'assumed',
16941778
};
16951779
}
16961780
if (cacheMode !== 'off') {
@@ -1720,6 +1804,9 @@ async function prepareServerFixture(
17201804
);
17211805
}
17221806
const filterReadyMs = performance.now() - filterReadyStartedAt;
1807+
if (cacheKey != null && !runtime.inspectDoc) {
1808+
await writeServerFixtureManifest(runtime, cacheKey, docId);
1809+
}
17231810
return {
17241811
docId,
17251812
cacheKey,
@@ -2277,7 +2364,7 @@ async function primeServerFixtureCase(
22772364

22782365
async function main() {
22792366
const argv = process.argv.slice(2);
2280-
const repoRoot = repoRootFromImportMeta(import.meta.url, 3);
2367+
const repoRoot = BENCH_REPO_ROOT;
22812368

22822369
const iterationsOverride = parseIterationsOverride(argv);
22832370
const warmupIterationsOverride = parseWarmupIterationsOverride(argv);

0 commit comments

Comments
 (0)