perf: complete persist-load P2 hydration pass

This commit is contained in:
Youzini-afk
2026-04-22 19:31:44 +08:00
parent 37c6266a81
commit e880fe0b39
10 changed files with 1037 additions and 23 deletions

View File

@@ -0,0 +1,397 @@
import { performance } from "node:perf_hooks";
import path from "node:path";
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";
import {
BmeDatabase,
buildBmeDbName,
buildGraphFromSnapshot,
buildSnapshotFromGraph,
ensureDexieLoaded,
} from "../../sync/bme-db.js";
import {
BME_GRAPH_LOCAL_STORAGE_MODE_OPFS_PRIMARY,
OpfsGraphStore,
} from "../../sync/bme-opfs-store.js";
import { createMemoryOpfsRoot } from "../helpers/memory-opfs.mjs";
const RUNS = 4;
const outputJson = process.argv.includes("--json");
const projectRootHint = String(process.env.ST_BME_NODE_MODULES_ROOT || "").trim();
const requireFromProjectRoot = projectRootHint
? createRequire(path.join(projectRootHint, "package.json"))
: null;
const SIZE_PRESETS = [
{ label: "M", seed: 17, nodeCount: 1200, edgeCount: 3600 },
{ label: "L", seed: 29, nodeCount: 3600, edgeCount: 10800 },
{ label: "XL", seed: 43, nodeCount: 7200, edgeCount: 21600 },
];
async function importWithProjectRootFallback(specifier) {
try {
return await import(specifier);
} catch (error) {
if (!requireFromProjectRoot) {
throw error;
}
const resolved = requireFromProjectRoot.resolve(specifier);
return await import(pathToFileURL(resolved).href);
}
}
function summarize(values = []) {
if (!values.length) {
return { avg: 0, p95: 0, min: 0, max: 0 };
}
const sorted = [...values].sort((a, b) => a - b);
const sum = sorted.reduce((acc, value) => acc + value, 0);
const p95Index = Math.min(sorted.length - 1, Math.floor(sorted.length * 0.95));
return {
avg: sum / sorted.length,
p95: sorted[p95Index],
min: sorted[0],
max: sorted[sorted.length - 1],
};
}
function formatSummary(label, values = []) {
const summary = summarize(values);
return `${label} avg=${summary.avg.toFixed(2)}ms p95=${summary.p95.toFixed(2)}ms min=${summary.min.toFixed(2)}ms max=${summary.max.toFixed(2)}ms`;
}
function createRandom(seed = 1) {
let state = seed >>> 0;
return () => {
state = (state * 1664525 + 1013904223) >>> 0;
return state / 0xffffffff;
};
}
function buildRuntimeGraph(seed = 1, nodeCount = 100, edgeCount = 200, chatId = "bench-chat") {
const rand = createRandom(seed);
const nodes = [];
const edges = [];
for (let index = 0; index < nodeCount; index += 1) {
nodes.push({
id: `node-${index}`,
type: "event",
updatedAt: 1000 + index,
archived: false,
sourceFloor: index,
fields: {
title: `Node ${index}`,
text: `node-${index}-${Math.floor(rand() * 100000)}`,
},
});
}
for (let index = 0; index < edgeCount; index += 1) {
const fromIndex = Math.floor(rand() * nodeCount);
let toIndex = Math.floor(rand() * nodeCount);
if (toIndex === fromIndex) {
toIndex = (toIndex + 1) % nodeCount;
}
edges.push({
id: `edge-${index}`,
fromId: `node-${fromIndex}`,
toId: `node-${toIndex}`,
relation: "related",
strength: rand(),
updatedAt: 2000 + index,
});
}
return {
version: 1,
nodes,
edges,
historyState: {
chatId,
lastProcessedAssistantFloor: Math.max(0, Math.floor(nodeCount / 12)),
extractionCount: Math.max(1, Math.floor(nodeCount / 40)),
processedMessageHashes: {},
processedMessageHashVersion: 1,
processedMessageHashesNeedRefresh: false,
recentRecallOwnerKeys: [],
activeRecallOwnerKey: "",
activeRegion: "",
activeRegionSource: "",
activeStorySegmentId: "",
activeStoryTimeLabel: "",
activeStoryTimeSource: "",
lastBatchStatus: null,
lastMutationSource: "bench",
lastExtractedRegion: "",
lastExtractedStorySegmentId: "",
activeCharacterPovOwner: "",
activeUserPovOwner: "",
},
vectorIndexState: {
chatId,
collectionId: "",
hashToNodeId: {},
nodeToHash: {},
replayRequiredNodeIds: [],
dirty: false,
dirtyReason: "",
pendingRepairFromFloor: null,
lastIntegrityIssue: null,
lastStats: {
nodesIndexed: 0,
updatedAt: 0,
},
},
knowledgeState: {
owners: {},
activeOwnerKey: "",
},
regionState: {
activeRegion: "",
knownRegions: {},
manualActiveRegion: "",
},
timelineState: {
activeSegmentId: "",
manualActiveSegmentId: "",
segments: [],
},
summaryState: {
updatedAt: 0,
entries: [],
},
batchJournal: [],
maintenanceJournal: [],
lastRecallResult: null,
lastProcessedSeq: Math.max(0, Math.floor(nodeCount / 12)),
};
}
function buildBenchSnapshot({ label, seed, nodeCount, edgeCount }) {
const chatId = `load-bench-${label.toLowerCase()}-${seed}`;
const graph = buildRuntimeGraph(seed, nodeCount, edgeCount, chatId);
return {
chatId,
snapshot: buildSnapshotFromGraph(graph, {
chatId,
revision: 1,
}),
};
}
async function setupIndexedDbTestEnv() {
try {
await importWithProjectRootFallback("fake-indexeddb/auto");
} catch {
// no-op
}
if (!globalThis.Dexie) {
try {
const imported = await importWithProjectRootFallback("dexie");
globalThis.Dexie = imported?.default || imported?.Dexie || imported;
} catch {
await import("../../lib/dexie.min.js");
}
}
await ensureDexieLoaded();
}
async function cleanupDatabase(chatId = "") {
if (!chatId || typeof globalThis.Dexie?.delete !== "function") return;
try {
await globalThis.Dexie.delete(buildBmeDbName(chatId));
} catch {
// no-op
}
}
async function prepareIndexedDb(chatId, snapshot) {
await cleanupDatabase(chatId);
const db = new BmeDatabase(chatId, { dexieClass: globalThis.Dexie });
await db.open();
await db.importSnapshot(snapshot, {
mode: "replace",
preserveRevision: true,
markSyncDirty: false,
});
return db;
}
async function prepareOpfsStore(chatId, snapshot) {
const rootDirectory = createMemoryOpfsRoot();
const store = new OpfsGraphStore(chatId, {
rootDirectoryFactory: async () => rootDirectory,
storeMode: BME_GRAPH_LOCAL_STORAGE_MODE_OPFS_PRIMARY,
});
await store.open();
await store.importSnapshot(snapshot, {
mode: "replace",
preserveRevision: true,
markSyncDirty: false,
});
return store;
}
async function readProbeOrFallback(store) {
let inspectionSnapshot = null;
let exportProbeMs = 0;
let exportSnapshotMs = 0;
let exportSource = "";
if (typeof store.exportSnapshotProbe === "function") {
const probeStartedAt = performance.now();
inspectionSnapshot = await store.exportSnapshotProbe({ includeTombstones: false });
exportProbeMs = performance.now() - probeStartedAt;
exportSource = "probe";
}
if (!inspectionSnapshot) {
const exportStartedAt = performance.now();
inspectionSnapshot = await store.exportSnapshot({ includeTombstones: false });
exportSnapshotMs = performance.now() - exportStartedAt;
exportSource = "full-export";
}
return {
inspectionSnapshot,
exportProbeMs,
exportSnapshotMs,
exportSource,
};
}
async function measureSuccessPreApply(store, chatId) {
const startedAt = performance.now();
const probeResult = await readProbeOrFallback(store);
let snapshot = probeResult.inspectionSnapshot;
let exportSnapshotMs = probeResult.exportSnapshotMs;
let exportSource = probeResult.exportSource;
if (snapshot?.__stBmeProbeOnly === true) {
const exportStartedAt = performance.now();
snapshot = await store.exportSnapshot({ includeTombstones: false });
exportSnapshotMs += performance.now() - exportStartedAt;
exportSource =
probeResult.exportSource === "probe" ? "probe+full-export" : "full-export";
}
const preApplyMs = performance.now() - startedAt;
const hydrateStartedAt = performance.now();
buildGraphFromSnapshot(snapshot, { chatId });
const hydrateMs = performance.now() - hydrateStartedAt;
return {
preApplyMs,
exportProbeMs: probeResult.exportProbeMs,
exportSnapshotMs,
hydrateMs,
exportSource,
};
}
async function measureProbeRejectPreApply(store) {
const startedAt = performance.now();
const probeResult = await readProbeOrFallback(store);
return {
preApplyMs: performance.now() - startedAt,
exportProbeMs: probeResult.exportProbeMs,
exportSnapshotMs: probeResult.exportSnapshotMs,
exportSource: probeResult.exportSource,
};
}
async function runPreset(preset) {
const indexedDbSuccessSamples = [];
const indexedDbProbeRejectSamples = [];
const indexedDbProbeSamples = [];
const indexedDbExportSamples = [];
const indexedDbHydrateSamples = [];
const opfsSuccessSamples = [];
const opfsProbeRejectSamples = [];
const opfsProbeSamples = [];
const opfsExportSamples = [];
const opfsHydrateSamples = [];
for (let run = 0; run < RUNS; run += 1) {
const { chatId, snapshot } = buildBenchSnapshot({
...preset,
seed: preset.seed + run * 17,
});
const indexedDbChatId = `${chatId}-indexeddb`;
const db = await prepareIndexedDb(indexedDbChatId, snapshot);
const indexedDbSuccess = await measureSuccessPreApply(db, indexedDbChatId);
const indexedDbProbeReject = await measureProbeRejectPreApply(db);
indexedDbSuccessSamples.push(indexedDbSuccess.preApplyMs);
indexedDbProbeRejectSamples.push(indexedDbProbeReject.preApplyMs);
indexedDbProbeSamples.push(indexedDbSuccess.exportProbeMs);
indexedDbExportSamples.push(indexedDbSuccess.exportSnapshotMs);
indexedDbHydrateSamples.push(indexedDbSuccess.hydrateMs);
await db.close();
await cleanupDatabase(indexedDbChatId);
const opfsChatId = `${chatId}-opfs`;
const opfsStore = await prepareOpfsStore(opfsChatId, snapshot);
const opfsSuccess = await measureSuccessPreApply(opfsStore, opfsChatId);
const opfsProbeReject = await measureProbeRejectPreApply(opfsStore);
opfsSuccessSamples.push(opfsSuccess.preApplyMs);
opfsProbeRejectSamples.push(opfsProbeReject.preApplyMs);
opfsProbeSamples.push(opfsSuccess.exportProbeMs);
opfsExportSamples.push(opfsSuccess.exportSnapshotMs);
opfsHydrateSamples.push(opfsSuccess.hydrateMs);
await opfsStore.close();
}
const result = {
indexedDbPreApplySuccessMs: summarize(indexedDbSuccessSamples),
indexedDbProbeRejectMs: summarize(indexedDbProbeRejectSamples),
indexedDbExportProbeMs: summarize(indexedDbProbeSamples),
indexedDbExportSnapshotMs: summarize(indexedDbExportSamples),
indexedDbHydrateMs: summarize(indexedDbHydrateSamples),
opfsPreApplySuccessMs: summarize(opfsSuccessSamples),
opfsProbeRejectMs: summarize(opfsProbeRejectSamples),
opfsExportProbeMs: summarize(opfsProbeSamples),
opfsExportSnapshotMs: summarize(opfsExportSamples),
opfsHydrateMs: summarize(opfsHydrateSamples),
};
if (!outputJson) {
console.log(`\n[ST-BME][load-preapply-bench] ${preset.label}`);
console.log(
formatSummary("indexeddb-preapply-success", indexedDbSuccessSamples),
`probeRejectP95=${result.indexedDbProbeRejectMs.p95.toFixed(2)}ms`,
`probeP95=${result.indexedDbExportProbeMs.p95.toFixed(2)}ms`,
`exportP95=${result.indexedDbExportSnapshotMs.p95.toFixed(2)}ms`,
);
console.log(
formatSummary("opfs-preapply-success", opfsSuccessSamples),
`probeRejectP95=${result.opfsProbeRejectMs.p95.toFixed(2)}ms`,
`probeP95=${result.opfsExportProbeMs.p95.toFixed(2)}ms`,
`exportP95=${result.opfsExportSnapshotMs.p95.toFixed(2)}ms`,
);
console.log(
formatSummary("indexeddb-hydrate", indexedDbHydrateSamples),
formatSummary("opfs-hydrate", opfsHydrateSamples),
);
}
return result;
}
async function main() {
await setupIndexedDbTestEnv();
const results = {};
for (const preset of SIZE_PRESETS) {
results[preset.label] = await runPreset(preset);
}
if (outputJson) {
console.log(
JSON.stringify({
runs: RUNS,
presets: results,
}),
);
}
}
await main();

View File

@@ -12,6 +12,7 @@ import {
import { createMemoryOpfsRoot } from "../helpers/memory-opfs.mjs";
const RUNS = 4;
const outputJson = process.argv.includes("--json");
const SIZE_PRESETS = [
{ label: "M", seed: 17, nodeCount: 1200, edgeCount: 3600, churn: 0.08 },
{ label: "L", seed: 29, nodeCount: 3600, edgeCount: 10800, churn: 0.1 },
@@ -260,6 +261,11 @@ async function runPreset(preset) {
const opfsCommitSamples = [];
const snapshotNodesSamples = [];
const hydrateRuntimeMetaSamples = [];
const hydrateNodesSamples = [];
const hydrateEdgesSamples = [];
const hydrateStateSamples = [];
const hydrateNormalizeSamples = [];
const hydrateIntegritySamples = [];
const walFileWriteSamples = [];
const manifestFileWriteSamples = [];
@@ -295,31 +301,64 @@ async function runPreset(preset) {
opfsCommitSamples.push(opfsCommitResult.elapsedMs);
snapshotNodesSamples.push(Number(afterSnapshotResult.diagnostics?.nodesMs || 0));
hydrateRuntimeMetaSamples.push(Number(hydrateResult.diagnostics?.runtimeMetaMs || 0));
hydrateNodesSamples.push(Number(hydrateResult.diagnostics?.nodesMs || 0));
hydrateEdgesSamples.push(Number(hydrateResult.diagnostics?.edgesMs || 0));
hydrateStateSamples.push(Number(hydrateResult.diagnostics?.stateMs || 0));
hydrateNormalizeSamples.push(Number(hydrateResult.diagnostics?.normalizeMs || 0));
hydrateIntegritySamples.push(Number(hydrateResult.diagnostics?.integrityMs || 0));
walFileWriteSamples.push(Number(opfsCommitResult.diagnostics?.walFileWriteMs || 0));
manifestFileWriteSamples.push(
Number(opfsCommitResult.diagnostics?.manifestFileWriteMs || 0),
);
}
console.log(`\n[ST-BME][persist-load-bench] ${preset.label}`);
console.log(
formatSummary("snapshot-build", snapshotBuildSamples),
`nodesPhaseP95=${summarize(snapshotNodesSamples).p95.toFixed(2)}ms`,
);
console.log(
formatSummary("hydrate", hydrateSamples),
`runtimeMetaP95=${summarize(hydrateRuntimeMetaSamples).p95.toFixed(2)}ms`,
);
console.log(
formatSummary("opfs-commit", opfsCommitSamples),
`walFileP95=${summarize(walFileWriteSamples).p95.toFixed(2)}ms`,
`manifestFileP95=${summarize(manifestFileWriteSamples).p95.toFixed(2)}ms`,
);
const result = {
snapshotBuildMs: summarize(snapshotBuildSamples),
snapshotNodesMs: summarize(snapshotNodesSamples),
hydrateMs: summarize(hydrateSamples),
hydrateNodesMs: summarize(hydrateNodesSamples),
hydrateEdgesMs: summarize(hydrateEdgesSamples),
hydrateStateMs: summarize(hydrateStateSamples),
hydrateNormalizeMs: summarize(hydrateNormalizeSamples),
hydrateIntegrityMs: summarize(hydrateIntegritySamples),
hydrateRuntimeMetaMs: summarize(hydrateRuntimeMetaSamples),
opfsCommitMs: summarize(opfsCommitSamples),
opfsWalFileWriteMs: summarize(walFileWriteSamples),
opfsManifestFileWriteMs: summarize(manifestFileWriteSamples),
};
if (!outputJson) {
console.log(`\n[ST-BME][persist-load-bench] ${preset.label}`);
console.log(
formatSummary("snapshot-build", snapshotBuildSamples),
`nodesPhaseP95=${result.snapshotNodesMs.p95.toFixed(2)}ms`,
);
console.log(
formatSummary("hydrate", hydrateSamples),
`nodesP95=${result.hydrateNodesMs.p95.toFixed(2)}ms`,
`edgesP95=${result.hydrateEdgesMs.p95.toFixed(2)}ms`,
`normalizeP95=${result.hydrateNormalizeMs.p95.toFixed(2)}ms`,
`integrityP95=${result.hydrateIntegrityMs.p95.toFixed(2)}ms`,
`runtimeMetaP95=${result.hydrateRuntimeMetaMs.p95.toFixed(2)}ms`,
);
console.log(
formatSummary("opfs-commit", opfsCommitSamples),
`walFileP95=${result.opfsWalFileWriteMs.p95.toFixed(2)}ms`,
`manifestFileP95=${result.opfsManifestFileWriteMs.p95.toFixed(2)}ms`,
);
}
return result;
}
async function main() {
const results = {};
for (const preset of SIZE_PRESETS) {
await runPreset(preset);
results[preset.label] = await runPreset(preset);
}
if (outputJson) {
console.log(JSON.stringify({
runs: RUNS,
presets: results,
}));
}
}