fix(sqlite): speed up read-only snapshot startup (#144448)

* perf(sqlite): bundle read-only snapshot child independently

* test(build): retain standalone worker declaration coverage
This commit is contained in:
Peter Steinberger 2026-09-10 16:20:24 -07:00 • committed by GitHub
parent c89f368142
commit 7b230efeac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 108 additions and 17 deletions

View file

@ -19,3 +19,16 @@ export function createRuntimeProcessBuildEntries(
export const runtimeProcessCoreBuildEntries = createRuntimeProcessBuildEntries(
Object.values(runtimeProcessEntrypoints),
);
// Short-lived snapshot children own a separate bundle; parents retain shared runtime identity.
export const standaloneRuntimeProcessBuildEntries = createRuntimeProcessBuildEntries([
runtimeProcessEntrypoints.sqliteReadOnly,
]);
export function sharedRuntimeProcessBuildEntries(entries: Record<string, string>) {
return Object.fromEntries(
Object.entries(entries).filter(
([name]) => !Object.hasOwn(standaloneRuntimeProcessBuildEntries, name),
),
);
}

View file

@ -4,6 +4,10 @@ import { createRequire } from "node:module";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { createManagedHandoffBuildConfig } from "./managed-handoff-build-config.mts";
import {
sharedRuntimeProcessBuildEntries,
standaloneRuntimeProcessBuildEntries,
} from "./runtime-process-core-build-entries.mts";
import { createStateSchemaInlinePlugin } from "./state-schema-inline-plugin.mts";
import {
hashVitestWorkerArtifact,
@ -64,7 +68,7 @@ async function compileVitestWorkerArtifacts(directory: string): Promise<void> {
const config: NonNullable<Parameters<typeof build>[0]> = {
config: false,
cwd: root,
entry,
entry: sharedRuntimeProcessBuildEntries(entry),
outDir,
format: "esm",
platform: "node",
@ -141,6 +145,11 @@ async function compileVitestWorkerArtifacts(directory: string): Promise<void> {
],
};
await build(config);
await build({
...config,
entry: standaloneRuntimeProcessBuildEntries,
outputOptions: { codeSplitting: false },
});
await build({
...createManagedHandoffBuildConfig(),
config: false,

View file

@ -608,6 +608,16 @@ describe("tsdown config", () => {
const unifiedRuntimeConfig = configs.find(
(entry) => entry.name === TSDOWN_UNIFIED_CONFIG_GROUP,
);
const standaloneRuntimeConfig = configs.find(
(entry) =>
entry.name === TSDOWN_UNIFIED_CONFIG_GROUP &&
entry.dts === false &&
hasWorkerEntry(
entry,
"infra/sqlite-readonly-location.worker",
path.resolve("src/infra/sqlite-readonly-location.worker.ts"),
),
);
const unifiedDeclarationConfigs = TSDOWN_UNIFIED_DTS_CONFIG_GROUPS.map((name) =>
configs.find((entry) => entry.name === name),
);
@ -615,15 +625,28 @@ describe("tsdown config", () => {
expect(packageConfigs).not.toHaveLength(0);
expect(packageConfigs.map((entry) => entry.dts)).toEqual(packageConfigs.map(() => true));
expect(unifiedRuntimeConfig?.dts).toBe(false);
expect(standaloneRuntimeConfig?.dts).toBe(false);
expect(unifiedDeclarationConfigs.every(Boolean)).toBe(true);
const runtimeEntryNames = Object.keys(unifiedRuntimeConfig?.entry ?? {});
expect(runtimeEntryNames).toContain("native-hook-relay/entry");
const declarationEntryNames = runtimeEntryNames.filter(
(name) => name !== "native-hook-relay/entry",
);
const standaloneEntries = Object.entries(standaloneRuntimeConfig?.entry ?? {});
const standaloneNames = new Set(standaloneEntries.map(([name]) => name));
const declarationInputs = Object.fromEntries([
...Object.entries(unifiedRuntimeConfig?.entry ?? {}).filter(
([name]) => name !== "native-hook-relay/entry",
),
...standaloneEntries,
]);
for (const declarationConfig of unifiedDeclarationConfigs) {
expect(declarationConfig?.dts).toMatchObject({ emitDtsOnly: true });
expect(Object.keys(declarationConfig?.entry ?? {})).toEqual(declarationEntryNames);
// Splitting executable graphs keeps all declaration aliases and the shared input order.
expect(declarationConfig?.entry).toEqual(declarationInputs);
expect(
Object.keys(declarationConfig?.entry ?? {}).filter((name) => !standaloneNames.has(name)),
).toEqual(declarationEntryNames);
}
});

View file

@ -1,6 +1,7 @@
// Covers bundling rules encoded in the root tsdown config.
import { readFileSync } from "node:fs";
import path from "node:path";
import { expectDefined } from "@openclaw/normalization-core/expect";
import { bundledPluginRoot } from "openclaw/plugin-sdk/test-fixtures";
import { describe, expect, it } from "vitest";
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../../src/state/openclaw-agent-schema.js";
@ -18,6 +19,10 @@ type TsdownConfigEntry = {
entry?: Record<string, string> | string[];
inputOptions?: TsdownInputOptions;
minify?: unknown;
dts?: boolean | { emitDtsOnly?: boolean };
define?: Record<string, unknown>;
outputOptions?: { codeSplitting?: boolean };
outExtensions?: () => { js: string };
outDir?: string;
plugins?: Array<{ name?: string }>;
};
@ -68,6 +73,16 @@ function entrySources(config: TsdownConfigEntry): Record<string, string> {
return config.entry;
}
function requireSqliteReadOnlyChildGraph(): TsdownConfigEntry {
const graphs = asConfigArray(tsdownConfig).filter(
(config) =>
!(typeof config.dts === "object" && config.dts.emitDtsOnly) &&
entryKeys(config).includes("infra/sqlite-readonly-location.worker"),
);
expect(graphs).toHaveLength(1);
return expectDefined(graphs[0], "read-only snapshot child graph");
}
function bundledEntry(pluginId: string): string {
return `${bundledPluginRoot(pluginId)}/index`;
}
@ -191,7 +206,10 @@ describe("tsdown config", () => {
expect(entrySources(unifiedGraph)["native-hook-relay/entry"]).toBe(
"src/cli/native-hook-relay-entry.ts",
);
expect(inlinePlugins).toHaveLength(3);
expect(requireSqliteReadOnlyChildGraph().plugins).toContainEqual(
expect.objectContaining({ name: STATE_SCHEMA_INLINE_PLUGIN_NAME }),
);
expect(inlinePlugins).toHaveLength(4);
});
it("keeps core, plugin runtime, plugin-sdk, bundled root plugins, and bundled hooks in one dist graph", () => {
@ -207,7 +225,7 @@ describe("tsdown config", () => {
"agents/compaction-planning.worker",
"agents/model-provider-auth.worker",
"config/sessions/session-accessor.sqlite-archive.worker",
"infra/sqlite-readonly-location.worker",
"plugin-sdk/sqlite-runtime",
"state/openclaw-database-verify.worker",
"plugins/memory-state",
"subagent-registry.runtime",
@ -233,6 +251,18 @@ describe("tsdown config", () => {
}
});
it("emits the read-only snapshot child once without sealing its package loaders", () => {
const child = requireSqliteReadOnlyChildGraph();
expect(entrySources(child)).toEqual({
"infra/sqlite-readonly-location.worker": path.resolve(
"src/infra/sqlite-readonly-location.worker.ts",
),
});
expect(child.outputOptions).toEqual({ codeSplitting: false });
expect(child.outExtensions?.().js).toBe(".js");
expect(child.define?.SEALED_RUNTIME_BUILD).toBeUndefined();
});
it("builds the Docker healthcheck as a stable dist entry", () => {
const distGraph = requireUnifiedDistGraph();
@ -340,20 +370,22 @@ describe("tsdown config", () => {
});
it("bundles SDK-owned helpers while retaining fs-safe package ownership", () => {
const unifiedGraph = requireUnifiedDistGraph();
const alwaysBundle = unifiedGraph.deps?.alwaysBundle;
for (const graph of [requireUnifiedDistGraph(), requireSqliteReadOnlyChildGraph()]) {
const alwaysBundle = graph.deps?.alwaysBundle;
const external = graph.inputOptions?.({})?.external;
if (typeof alwaysBundle !== "function" || typeof external !== "function") {
throw new Error("expected runtime graph dependency predicates");
}
if (typeof alwaysBundle !== "function") {
throw new Error("expected unified graph alwaysBundle predicate");
expect(alwaysBundle("@openclaw/fs-safe")).toBe(false);
expect(alwaysBundle("@openclaw/fs-safe/path")).toBe(false);
expect(external("@openclaw/fs-safe/path", undefined, false)).toBe(true);
expect(alwaysBundle("openclaw/plugin-sdk/ssrf-runtime-internal")).toBe(true);
expect(alwaysBundle("openclaw/plugin-sdk/ssrf-runtime")).toBe(false);
expect(alwaysBundle("zod")).toBe(true);
expect(alwaysBundle("zod/v4/core")).toBe(true);
expect(alwaysBundle("not-a-runtime-dependency")).toBe(false);
}
expect(alwaysBundle("@openclaw/fs-safe")).toBe(false);
expect(alwaysBundle("@openclaw/fs-safe/path")).toBe(false);
expect(alwaysBundle("openclaw/plugin-sdk/ssrf-runtime-internal")).toBe(true);
expect(alwaysBundle("openclaw/plugin-sdk/ssrf-runtime")).toBe(false);
expect(alwaysBundle("zod")).toBe(true);
expect(alwaysBundle("zod/v4/core")).toBe(true);
expect(alwaysBundle("not-a-runtime-dependency")).toBe(false);
});
it("suppresses unresolved imports from extension source", () => {

View file

@ -19,6 +19,10 @@ import {
} from "./scripts/lib/plugin-sdk-entries.mts";
import { createRuntimeDependencyOwnershipBuildPlugin } from "./scripts/lib/runtime-dependency-ownership-build-plugin.mts";
import { runtimeProcessBuildEntries } from "./scripts/lib/runtime-process-build-entries.mts";
import {
sharedRuntimeProcessBuildEntries,
standaloneRuntimeProcessBuildEntries,
} from "./scripts/lib/runtime-process-core-build-entries.mts";
import {
createStateSchemaInlinePlugin,
STATE_SCHEMA_INLINE_PLUGIN_NAME,
@ -813,7 +817,7 @@ const configs: UserConfig[] = [
// Build core entrypoints, plugin-sdk subpaths, bundled plugin entrypoints,
// and bundled hooks in one graph so runtime singletons are emitted once.
entry: {
...unifiedDistEntries,
...sharedRuntimeProcessBuildEntries(unifiedDistEntries),
"native-hook-relay/entry": "src/cli/native-hook-relay-entry.ts",
},
deps: unifiedDeps,
@ -828,6 +832,16 @@ const configs: UserConfig[] = [
},
false,
),
nodeBuildConfig(
{
name: TSDOWN_UNIFIED_CONFIG_GROUP,
entry: standaloneRuntimeProcessBuildEntries,
deps: unifiedDeps,
outputOptions: { codeSplitting: false },
plugins: [createStateSchemaInlinePlugin()],
},
false,
),
workerDeployBuildConfig(),
{ ...createManagedHandoffBuildConfig(), name: TSDOWN_UNIFIED_CONFIG_GROUP, env },
nodeBuildConfig(