@rocicorp/zero 1.8.0-canary.2 → 1.8.0-canary.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/observability/metrics.d.ts +4 -0
- package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/observability/metrics.js +16 -6
- package/out/zero-cache/src/observability/metrics.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +247 -40
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/package.json +1 -1
package/out/zero/package.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
var package_default = {
|
|
2
2
|
name: "@rocicorp/zero",
|
|
3
|
-
version: "1.8.0-canary.
|
|
3
|
+
version: "1.8.0-canary.3",
|
|
4
4
|
description: "Zero is a web framework for serverless web development.",
|
|
5
5
|
homepage: "https://zero.rocicorp.dev",
|
|
6
6
|
bugs: { "url": "https://bugs.rocicorp.dev" },
|
package/out/zero/package.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":["{\n \"name\": \"@rocicorp/zero\",\n \"version\": \"1.8.0-canary.
|
|
1
|
+
{"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":["{\n \"name\": \"@rocicorp/zero\",\n \"version\": \"1.8.0-canary.3\",\n \"description\": \"Zero is a web framework for serverless web development.\",\n \"homepage\": \"https://zero.rocicorp.dev\",\n \"bugs\": {\n \"url\": \"https://bugs.rocicorp.dev\"\n },\n \"license\": \"Apache-2.0\",\n \"author\": \"Rocicorp, Inc.\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/rocicorp/mono.git\",\n \"directory\": \"packages/zero\"\n },\n \"bin\": {\n \"analyze-query\": \"./out/zero/src/analyze-query.js\",\n \"ast-to-zql\": \"./out/zero/src/ast-to-zql.js\",\n \"transform-query\": \"./out/zero/src/transform-query.js\",\n \"zero-build-schema\": \"./out/zero/src/build-schema.js\",\n \"zero-cache\": \"./out/zero/src/cli.js\",\n \"zero-cache-dev\": \"./out/zero/src/zero-cache-dev.js\",\n \"zero-deploy-permissions\": \"./out/zero/src/deploy-permissions.js\",\n \"zero-out\": \"./out/zero/src/zero-out.js\"\n },\n \"files\": [\n \"out\",\n \"!*.tsbuildinfo\"\n ],\n \"type\": \"module\",\n \"main\": \"out/zero/src/zero.js\",\n \"module\": \"out/zero/src/zero.js\",\n \"types\": \"out/zero/src/zero.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./out/zero/src/zero.d.ts\",\n \"default\": \"./out/zero/src/zero.js\"\n },\n \"./analyze\": {\n \"types\": \"./out/zero/src/analyze.d.ts\",\n \"default\": \"./out/zero/src/analyze.js\"\n },\n \"./bindings\": {\n \"types\": \"./out/zero/src/bindings.d.ts\",\n \"default\": \"./out/zero/src/bindings.js\"\n },\n \"./change-protocol/v0\": {\n \"types\": \"./out/zero/src/change-protocol/v0.d.ts\",\n \"default\": \"./out/zero/src/change-protocol/v0.js\"\n },\n \"./expo-sqlite\": {\n \"types\": \"./out/zero/src/expo-sqlite.d.ts\",\n \"default\": \"./out/zero/src/expo-sqlite.js\"\n },\n \"./op-sqlite\": {\n \"types\": \"./out/zero/src/op-sqlite.d.ts\",\n \"default\": \"./out/zero/src/op-sqlite.js\"\n },\n \"./pg\": {\n \"types\": \"./out/zero/src/pg.d.ts\",\n \"default\": \"./out/zero/src/pg.js\"\n },\n \"./react\": {\n \"types\": \"./out/zero/src/react.d.ts\",\n \"default\": \"./out/zero/src/react.js\"\n },\n \"./react-native\": {\n \"types\": \"./out/zero/src/react-native.d.ts\",\n \"default\": \"./out/zero/src/react-native.js\"\n },\n \"./server\": {\n \"types\": \"./out/zero/src/server.d.ts\",\n \"default\": \"./out/zero/src/server.js\"\n },\n \"./server/adapters/drizzle\": {\n \"types\": \"./out/zero/src/adapters/drizzle.d.ts\",\n \"default\": \"./out/zero/src/adapters/drizzle.js\"\n },\n \"./server/adapters/kysely\": {\n \"types\": \"./out/zero/src/adapters/kysely.d.ts\",\n \"default\": \"./out/zero/src/adapters/kysely.js\"\n },\n \"./server/adapters/prisma\": {\n \"types\": \"./out/zero/src/adapters/prisma.d.ts\",\n \"default\": \"./out/zero/src/adapters/prisma.js\"\n },\n \"./server/adapters/pg\": {\n \"types\": \"./out/zero/src/adapters/pg.d.ts\",\n \"default\": \"./out/zero/src/adapters/pg.js\"\n },\n \"./server/adapters/postgresjs\": {\n \"types\": \"./out/zero/src/adapters/postgresjs.d.ts\",\n \"default\": \"./out/zero/src/adapters/postgresjs.js\"\n },\n \"./solid\": {\n \"types\": \"./out/zero/src/solid.d.ts\",\n \"default\": \"./out/zero/src/solid.js\"\n },\n \"./sqlite\": {\n \"types\": \"./out/zero/src/sqlite.d.ts\",\n \"default\": \"./out/zero/src/sqlite.js\"\n },\n \"./zqlite\": {\n \"types\": \"./out/zero/src/zqlite.d.ts\",\n \"default\": \"./out/zero/src/zqlite.js\"\n }\n },\n \"scripts\": {\n \"build\": \"node --experimental-strip-types --no-warnings tool/build.ts\",\n \"build:watch\": \"node --experimental-strip-types --no-warnings tool/build.ts --watch\",\n \"check-types\": \"tsc -p tsconfig.client.json && tsc -p tsconfig.server.json\",\n \"check-types:client:watch\": \"tsc -p tsconfig.client.json --watch\",\n \"check-types:server:watch\": \"tsc -p tsconfig.server.json --watch\",\n \"format\": \"oxfmt .\",\n \"check-format\": \"oxfmt --check .\",\n \"lint\": \"oxlint --quiet --config ../../oxlint.config.ts src/\",\n \"docs\": \"node --experimental-strip-types --no-warnings tool/generate-docs.ts\",\n \"docs:server\": \"node --watch --experimental-strip-types --no-warnings tool/generate-docs.ts --server\",\n \"fmt\": \"oxfmt .\",\n \"check-fmt\": \"oxfmt --check .\"\n },\n \"dependencies\": {\n \"@badrap/valita\": \"0.3.11\",\n \"@databases/escape-identifier\": \"^1.0.3\",\n \"@databases/sql\": \"^3.3.0\",\n \"@dotenvx/dotenvx\": \"^1.39.0\",\n \"@drdgvhbh/postgres-error-codes\": \"^0.0.6\",\n \"@fastify/cors\": \"^10.0.0\",\n \"@fastify/websocket\": \"^11.0.0\",\n \"@google-cloud/precise-date\": \"^4.0.0\",\n \"@opentelemetry/api\": \"^1.9.0\",\n \"@opentelemetry/api-logs\": \"^0.218.0\",\n \"@opentelemetry/auto-instrumentations-node\": \"^0.76.0\",\n \"@opentelemetry/exporter-metrics-otlp-http\": \"^0.218.0\",\n \"@opentelemetry/resources\": \"^2.7.1\",\n \"@opentelemetry/sdk-metrics\": \"^2.7.1\",\n \"@opentelemetry/sdk-node\": \"^0.218.0\",\n \"@opentelemetry/sdk-trace-node\": \"^2.7.1\",\n \"@opentelemetry/semantic-conventions\": \"^1.41.1\",\n \"@postgresql-typed/oids\": \"^0.2.0\",\n \"@rocicorp/lock\": \"^1.0.4\",\n \"@rocicorp/logger\": \"^6.1.0\",\n \"@rocicorp/resolver\": \"^1.0.2\",\n \"@rocicorp/zero-sqlite3\": \"^1.1.2\",\n \"@standard-schema/spec\": \"^1.0.0\",\n \"@types/basic-auth\": \"^1.1.8\",\n \"@types/ws\": \"^8.5.12\",\n \"basic-auth\": \"^2.0.1\",\n \"chalk-template\": \"^1.1.0\",\n \"chokidar\": \"^4.0.1\",\n \"cloudevents\": \"^10.0.0\",\n \"command-line-args\": \"^6.0.1\",\n \"command-line-usage\": \"^7.0.3\",\n \"compare-utf8\": \"^0.2.0\",\n \"defu\": \"^6.1.4\",\n \"eventemitter3\": \"^5.0.1\",\n \"fastify\": \"^5.0.0\",\n \"is-in-subnet\": \"^4.0.1\",\n \"jose\": \"^5.9.3\",\n \"js-xxhash\": \"^4.0.0\",\n \"json-custom-numbers\": \"^3.1.1\",\n \"kasi\": \"^1.1.0\",\n \"nanoid\": \"^5.1.2\",\n \"oxfmt\": \"^0.45.0\",\n \"parse-prometheus-text-format\": \"^1.1.1\",\n \"pg-format\": \"npm:pg-format-fix@^1.0.5\",\n \"postgres\": \"3.4.7\",\n \"semver\": \"^7.5.4\",\n \"url-pattern\": \"^1.0.3\",\n \"urlpattern-polyfill\": \"^10.1.0\",\n \"ws\": \"^8.18.1\"\n },\n \"devDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"@vitest/runner\": \"^4.1.6\",\n \"analyze-query\": \"workspace:*\",\n \"ast-to-zql\": \"workspace:*\",\n \"expo-sqlite\": \">=15\",\n \"replicache\": \"workspace:*\",\n \"shared\": \"workspace:*\",\n \"syncpack\": \"^14.3.0\",\n \"typedoc\": \"^0.28.17\",\n \"typedoc-plugin-markdown\": \"^4.10.0\",\n \"typescript\": \"~6.0.2\",\n \"vite\": \"^8.0.13\",\n \"vitest\": \"^4.1.6\",\n \"zero-cache\": \"workspace:*\",\n \"zero-client\": \"workspace:*\",\n \"zero-pg\": \"workspace:*\",\n \"zero-react\": \"workspace:*\",\n \"zero-server\": \"workspace:*\",\n \"zero-solid\": \"workspace:*\",\n \"zqlite\": \"workspace:*\"\n },\n \"peerDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"drizzle-orm\": \"^0.45.2\",\n \"expo-sqlite\": \">=15\",\n \"kysely\": \"^0.28.17\",\n \"pg\": \"^8.16.3\",\n \"react\": \"^19.2.6\",\n \"solid-js\": \"^1.9.4\"\n },\n \"peerDependenciesMeta\": {\n \"@op-engineering/op-sqlite\": {\n \"optional\": true\n },\n \"drizzle-orm\": {\n \"optional\": true\n },\n \"expo-sqlite\": {\n \"optional\": true\n },\n \"kysely\": {\n \"optional\": true\n },\n \"pg\": {\n \"optional\": true\n },\n \"react\": {\n \"optional\": true\n },\n \"solid-js\": {\n \"optional\": true\n }\n },\n \"engines\": {\n \"node\": \">=22\"\n },\n \"packageManager\": \"pnpm@11.5.3\"\n}\n"],"mappings":""}
|
|
@@ -3,6 +3,9 @@ export type Category = 'replication' | 'replica' | 'sync' | 'mutation' | 'server
|
|
|
3
3
|
type Options = MetricOptions & {
|
|
4
4
|
description: string;
|
|
5
5
|
};
|
|
6
|
+
type HistogramOptions = Options & {
|
|
7
|
+
bucketBoundaries: number[];
|
|
8
|
+
};
|
|
6
9
|
export declare function getOrCreateUpDownCounter(category: Category, name: string, description: string): UpDownCounter;
|
|
7
10
|
export declare function getOrCreateUpDownCounter(category: Category, name: string, opts: Options): UpDownCounter;
|
|
8
11
|
/**
|
|
@@ -22,6 +25,7 @@ export type LatencyHistogram = {
|
|
|
22
25
|
*/
|
|
23
26
|
recordMs(durationMs: number, attributes?: Parameters<Histogram['record']>[1]): void;
|
|
24
27
|
};
|
|
28
|
+
export declare function getOrCreateHistogram(category: Category, name: string, opts: HistogramOptions): LatencyHistogram;
|
|
25
29
|
/**
|
|
26
30
|
* Creates (or retrieves) a latency histogram for the given metric.
|
|
27
31
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/observability/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EAET,aAAa,EACb,eAAe,EACf,aAAa,EACd,MAAM,oBAAoB,CAAC;AAK5B,MAAM,MAAM,QAAQ,GAChB,aAAa,GACb,SAAS,GACT,MAAM,GACN,UAAU,GACV,QAAQ,CAAC;AAIb,KAAK,OAAO,GAAG,aAAa,GAAG;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/observability/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EAET,aAAa,EACb,eAAe,EACf,aAAa,EACd,MAAM,oBAAoB,CAAC;AAK5B,MAAM,MAAM,QAAQ,GAChB,aAAa,GACb,SAAS,GACT,MAAM,GACN,UAAU,GACV,QAAQ,CAAC;AAIb,KAAK,OAAO,GAAG,aAAa,GAAG;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,CAAC;AACrD,KAAK,gBAAgB,GAAG,OAAO,GAAG;IAAC,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAAC,CAAC;AA4B/D,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,aAAa,CAAC;AACjB,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,aAAa,CAAC;AAcjB;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;;;;OAMG;IACH,QAAQ,CACN,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GAC9C,IAAI,CAAC;CACT,CAAC;AAoBF,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,gBAAgB,GACrB,gBAAgB,CAelB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,gBAAgB,CAMlB;AAID,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;AACX,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC;AAgBX,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,eAAe,CAAC;AACnB,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,eAAe,CAAC"}
|
|
@@ -47,7 +47,18 @@ var LATENCY_HISTOGRAM_BOUNDARIES_S = [
|
|
|
47
47
|
10,
|
|
48
48
|
30
|
|
49
49
|
];
|
|
50
|
-
var
|
|
50
|
+
var histograms = cache();
|
|
51
|
+
function getOrCreateHistogram(category, name, opts) {
|
|
52
|
+
const { bucketBoundaries, ...metricOptions } = opts;
|
|
53
|
+
const h = histograms(name, (name) => getMeter().createHistogram(`zero.${category}.${name}`, {
|
|
54
|
+
...metricOptions,
|
|
55
|
+
advice: {
|
|
56
|
+
...metricOptions.advice,
|
|
57
|
+
explicitBucketBoundaries: bucketBoundaries
|
|
58
|
+
}
|
|
59
|
+
}));
|
|
60
|
+
return { recordMs: (durationMs, attributes) => h.record(durationMs / 1e3, attributes) };
|
|
61
|
+
}
|
|
51
62
|
/**
|
|
52
63
|
* Creates (or retrieves) a latency histogram for the given metric.
|
|
53
64
|
*
|
|
@@ -67,12 +78,11 @@ var latencyHistograms = cache();
|
|
|
67
78
|
* ```
|
|
68
79
|
*/
|
|
69
80
|
function getOrCreateLatencyHistogram(category, name, description) {
|
|
70
|
-
|
|
81
|
+
return getOrCreateHistogram(category, name, {
|
|
71
82
|
description,
|
|
72
83
|
unit: "s",
|
|
73
|
-
|
|
74
|
-
})
|
|
75
|
-
return { recordMs: (durationMs, attributes) => h.record(durationMs / 1e3, attributes) };
|
|
84
|
+
bucketBoundaries: LATENCY_HISTOGRAM_BOUNDARIES_S
|
|
85
|
+
});
|
|
76
86
|
}
|
|
77
87
|
var counters = cache();
|
|
78
88
|
function getOrCreateCounter(category, name, opts) {
|
|
@@ -83,6 +93,6 @@ function getOrCreateGauge(category, name, opts) {
|
|
|
83
93
|
return gauges(name, (name) => getMeter().createObservableGauge(`zero.${category}.${name}`, typeof opts === "string" ? { description: opts } : opts));
|
|
84
94
|
}
|
|
85
95
|
//#endregion
|
|
86
|
-
export { getOrCreateCounter, getOrCreateGauge, getOrCreateLatencyHistogram, getOrCreateUpDownCounter };
|
|
96
|
+
export { getOrCreateCounter, getOrCreateGauge, getOrCreateHistogram, getOrCreateLatencyHistogram, getOrCreateUpDownCounter };
|
|
87
97
|
|
|
88
98
|
//# sourceMappingURL=metrics.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.js","names":[],"sources":["../../../../../zero-cache/src/observability/metrics.ts"],"sourcesContent":["import type {\n Counter,\n Histogram,\n Meter,\n MetricOptions,\n ObservableGauge,\n UpDownCounter,\n} from '@opentelemetry/api';\nimport {metrics} from '@opentelemetry/api';\n\n// intentional lazy initialization so it is not started before the SDK is started.\n\nexport type Category =\n | 'replication' // postgres to replica\n | 'replica' // health of replica and litestream backup\n | 'sync' // replica to client\n | 'mutation'\n | 'server';\n\nlet meter: Meter | undefined;\n\ntype Options = MetricOptions & {description: string};\n\nfunction getMeter() {\n if (!meter) {\n meter = metrics.getMeter('zero');\n }\n return meter;\n}\n\nfunction cache<TRet>(): (\n name: string,\n creator: (name: string) => TRet,\n) => TRet {\n const instruments = new Map<string, TRet>();\n return (name: string, creator: (name: string) => TRet) => {\n const existing = instruments.get(name);\n if (existing) {\n return existing;\n }\n\n const ret = creator(name);\n instruments.set(name, ret);\n return ret;\n };\n}\n\nconst upDownCounters = cache<UpDownCounter>();\n\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n description: string,\n): UpDownCounter;\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n opts: Options,\n): UpDownCounter;\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n opts: string | Options,\n): UpDownCounter {\n return upDownCounters(name, name =>\n getMeter().createUpDownCounter(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n\n/**\n * A latency histogram whose {@link recordMs} method accepts raw millisecond\n * durations and converts them to seconds internally.\n *\n * Use {@link getOrCreateLatencyHistogram} to create one — the unit (`'s'`),\n * bucket boundaries, and ms→s conversion are all baked in\n */\nexport type LatencyHistogram = {\n /**\n * Record a duration. Pass the raw elapsed milliseconds — the conversion to\n * seconds (required by the `unit: 's'` OTel histogram) is handled internally.\n *\n * @param durationMs Elapsed time in **milliseconds** (do NOT pre-divide).\n * @param attributes Optional OTel attributes to attach to the observation.\n */\n recordMs(\n durationMs: number,\n attributes?: Parameters<Histogram['record']>[1],\n ): void;\n};\n\n/**\n * Bucket boundaries (in seconds) for zero's latency histograms.\n *\n * The operational range is 1 ms – 5,000 ms (including customers actively\n * tuning queries). ~2× logarithmic steps give proportionally consistent\n * `histogram_quantile` accuracy regardless of where values cluster within\n * that range. 10,000 ms and 30,000 ms are overflow catchers for truly broken\n * states.\n *\n * 1 ms, 2 ms, 5 ms, 10 ms, 20 ms, 50 ms, 100 ms, 200 ms, 500 ms,\n * 1 s, 2 s, 5 s, 10 s, 30 s\n */\nconst LATENCY_HISTOGRAM_BOUNDARIES_S = [\n 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30,\n];\n\nconst
|
|
1
|
+
{"version":3,"file":"metrics.js","names":[],"sources":["../../../../../zero-cache/src/observability/metrics.ts"],"sourcesContent":["import type {\n Counter,\n Histogram,\n Meter,\n MetricOptions,\n ObservableGauge,\n UpDownCounter,\n} from '@opentelemetry/api';\nimport {metrics} from '@opentelemetry/api';\n\n// intentional lazy initialization so it is not started before the SDK is started.\n\nexport type Category =\n | 'replication' // postgres to replica\n | 'replica' // health of replica and litestream backup\n | 'sync' // replica to client\n | 'mutation'\n | 'server';\n\nlet meter: Meter | undefined;\n\ntype Options = MetricOptions & {description: string};\ntype HistogramOptions = Options & {bucketBoundaries: number[]};\n\nfunction getMeter() {\n if (!meter) {\n meter = metrics.getMeter('zero');\n }\n return meter;\n}\n\nfunction cache<TRet>(): (\n name: string,\n creator: (name: string) => TRet,\n) => TRet {\n const instruments = new Map<string, TRet>();\n return (name: string, creator: (name: string) => TRet) => {\n const existing = instruments.get(name);\n if (existing) {\n return existing;\n }\n\n const ret = creator(name);\n instruments.set(name, ret);\n return ret;\n };\n}\n\nconst upDownCounters = cache<UpDownCounter>();\n\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n description: string,\n): UpDownCounter;\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n opts: Options,\n): UpDownCounter;\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n opts: string | Options,\n): UpDownCounter {\n return upDownCounters(name, name =>\n getMeter().createUpDownCounter(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n\n/**\n * A latency histogram whose {@link recordMs} method accepts raw millisecond\n * durations and converts them to seconds internally.\n *\n * Use {@link getOrCreateLatencyHistogram} to create one — the unit (`'s'`),\n * bucket boundaries, and ms→s conversion are all baked in\n */\nexport type LatencyHistogram = {\n /**\n * Record a duration. Pass the raw elapsed milliseconds — the conversion to\n * seconds (required by the `unit: 's'` OTel histogram) is handled internally.\n *\n * @param durationMs Elapsed time in **milliseconds** (do NOT pre-divide).\n * @param attributes Optional OTel attributes to attach to the observation.\n */\n recordMs(\n durationMs: number,\n attributes?: Parameters<Histogram['record']>[1],\n ): void;\n};\n\n/**\n * Bucket boundaries (in seconds) for zero's latency histograms.\n *\n * The operational range is 1 ms – 5,000 ms (including customers actively\n * tuning queries). ~2× logarithmic steps give proportionally consistent\n * `histogram_quantile` accuracy regardless of where values cluster within\n * that range. 10,000 ms and 30,000 ms are overflow catchers for truly broken\n * states.\n *\n * 1 ms, 2 ms, 5 ms, 10 ms, 20 ms, 50 ms, 100 ms, 200 ms, 500 ms,\n * 1 s, 2 s, 5 s, 10 s, 30 s\n */\nconst LATENCY_HISTOGRAM_BOUNDARIES_S = [\n 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30,\n];\n\nconst histograms = cache<Histogram>();\n\nexport function getOrCreateHistogram(\n category: Category,\n name: string,\n opts: HistogramOptions,\n): LatencyHistogram {\n const {bucketBoundaries, ...metricOptions} = opts;\n const h = histograms(name, name =>\n getMeter().createHistogram(`zero.${category}.${name}`, {\n ...metricOptions,\n advice: {\n ...metricOptions.advice,\n explicitBucketBoundaries: bucketBoundaries,\n },\n }),\n );\n return {\n recordMs: (durationMs, attributes) =>\n h.record(durationMs / 1000, attributes),\n };\n}\n\n/**\n * Creates (or retrieves) a latency histogram for the given metric.\n *\n * - `unit` is always `'s'` (seconds), matching the OTel convention.\n * - Bucket boundaries are pre-set for zero's typical operation range\n * (1 ms – 5 s); see {@link LATENCY_HISTOGRAM_BOUNDARIES_S}.\n * - The returned {@link LatencyHistogram} accepts **milliseconds** via\n * `recordMs()`, so callers never need to divide by 1000.\n *\n * @example\n * ```ts\n * readonly #hydrationTime = getOrCreateLatencyHistogram(\n * 'sync', 'hydration-time', 'Time to hydrate a query.',\n * );\n * // ...\n * this.#hydrationTime.recordMs(performance.now() - start);\n * ```\n */\nexport function getOrCreateLatencyHistogram(\n category: Category,\n name: string,\n description: string,\n): LatencyHistogram {\n return getOrCreateHistogram(category, name, {\n description,\n unit: 's',\n bucketBoundaries: LATENCY_HISTOGRAM_BOUNDARIES_S,\n });\n}\n\nconst counters = cache<Counter>();\n\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n description: string,\n): Counter;\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n opts: Options,\n): Counter;\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n opts: string | Options,\n): Counter {\n return counters(name, name =>\n getMeter().createCounter(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n\nconst gauges = cache<ObservableGauge>();\n\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n description: string,\n): ObservableGauge;\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n opts: Options,\n): ObservableGauge;\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n opts: string | Options,\n): ObservableGauge {\n return gauges(name, name =>\n getMeter().createObservableGauge(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n"],"mappings":";;AAmBA,IAAI;AAKJ,SAAS,WAAW;CAClB,IAAI,CAAC,OACH,QAAQ,QAAQ,SAAS,MAAM;CAEjC,OAAO;AACT;AAEA,SAAS,QAGC;CACR,MAAM,8BAAc,IAAI,IAAkB;CAC1C,QAAQ,MAAc,YAAoC;EACxD,MAAM,WAAW,YAAY,IAAI,IAAI;EACrC,IAAI,UACF,OAAO;EAGT,MAAM,MAAM,QAAQ,IAAI;EACxB,YAAY,IAAI,MAAM,GAAG;EACzB,OAAO;CACT;AACF;AAEA,IAAM,iBAAiB,MAAqB;AAY5C,SAAgB,yBACd,UACA,MACA,MACe;CACf,OAAO,eAAe,OAAM,SAC1B,SAAS,EAAE,oBACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF;;;;;;;;;;;;;AAmCA,IAAM,iCAAiC;CACrC;CAAO;CAAO;CAAO;CAAM;CAAM;CAAM;CAAK;CAAK;CAAK;CAAG;CAAG;CAAG;CAAI;AACrE;AAEA,IAAM,aAAa,MAAiB;AAEpC,SAAgB,qBACd,UACA,MACA,MACkB;CAClB,MAAM,EAAC,kBAAkB,GAAG,kBAAiB;CAC7C,MAAM,IAAI,WAAW,OAAM,SACzB,SAAS,EAAE,gBAAgB,QAAQ,SAAS,GAAG,QAAQ;EACrD,GAAG;EACH,QAAQ;GACN,GAAG,cAAc;GACjB,0BAA0B;EAC5B;CACF,CAAC,CACH;CACA,OAAO,EACL,WAAW,YAAY,eACrB,EAAE,OAAO,aAAa,KAAM,UAAU,EAC1C;AACF;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,4BACd,UACA,MACA,aACkB;CAClB,OAAO,qBAAqB,UAAU,MAAM;EAC1C;EACA,MAAM;EACN,kBAAkB;CACpB,CAAC;AACH;AAEA,IAAM,WAAW,MAAe;AAYhC,SAAgB,mBACd,UACA,MACA,MACS;CACT,OAAO,SAAS,OAAM,SACpB,SAAS,EAAE,cACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF;AAEA,IAAM,SAAS,MAAuB;AAYtC,SAAgB,iBACd,UACA,MACA,MACiB;CACjB,OAAO,OAAO,OAAM,SAClB,SAAS,EAAE,sBACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initial-sync.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/pg/initial-sync.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,0CAA0C,CAAC;AAGzE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,0CAA0C,CAAC;AAC7E,OAAO,EAAC,QAAQ,EAAC,MAAM,iCAAiC,CAAC;AAoBzD,OAAO,KAAK,EAAC,SAAS,EAAE,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"initial-sync.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/pg/initial-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@ import { liteTableName } from "../../../types/names.js";
|
|
|
8
8
|
import { mapPostgresToLite, mapPostgresToLiteIndex } from "../../../db/pg-to-lite.js";
|
|
9
9
|
import { ColumnMetadataStore } from "../../replicator/schema/column-metadata.js";
|
|
10
10
|
import { listIndexes, listTables } from "../../../db/lite-tables.js";
|
|
11
|
+
import { getOrCreateCounter, getOrCreateHistogram } from "../../../observability/metrics.js";
|
|
11
12
|
import { initReplicationState } from "../../replicator/schema/replication-state.js";
|
|
12
13
|
import { id } from "../../../types/sql.js";
|
|
13
14
|
import { connectPgClient, pgClient } from "../../../types/pg.js";
|
|
@@ -36,17 +37,22 @@ import { pipeline as pipeline$1 } from "node:stream/promises";
|
|
|
36
37
|
async function initialSync(lc, shard, tx, upstreamURI, syncOptions, context) {
|
|
37
38
|
if (!ALLOWED_APP_ID_CHARACTERS.test(shard.appID)) throw new Error("The App ID may only consist of lower-case letters, numbers, and the underscore character");
|
|
38
39
|
const { tableCopyWorkers, profileCopy, textCopy = false, replicationSlotFailover = false, shadow } = syncOptions;
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
});
|
|
40
|
+
const syncMode = shadow ? "shadow" : "initial";
|
|
41
|
+
const copyFormat = textCopy ? "text" : "binary";
|
|
42
|
+
const start = performance.now();
|
|
43
|
+
let sql;
|
|
44
|
+
let replicationSession;
|
|
45
45
|
const replicaID = Date.now().toString();
|
|
46
46
|
let slotName;
|
|
47
47
|
const statusPublisher = ReplicationStatusPublisher.forRunningTransaction(tx, shadow ? async () => {} : void 0).publish(lc, "Initializing");
|
|
48
48
|
let releaseShadowSnapshot;
|
|
49
49
|
try {
|
|
50
|
+
const copyProfiler = profileCopy ? await CpuProfiler.connect() : null;
|
|
51
|
+
sql = await connectPgClient(lc, upstreamURI, "initial-sync");
|
|
52
|
+
replicationSession = shadow ? void 0 : pgClient(lc, upstreamURI, "initial-sync-replication-session", {
|
|
53
|
+
["fetch_types"]: false,
|
|
54
|
+
connection: { replication: "database" }
|
|
55
|
+
});
|
|
50
56
|
const pgVersion = await checkUpstreamConfig(sql);
|
|
51
57
|
const { publications } = shadow ? await getInternalShardConfig(sql, shard) : await ensurePublishedTables(lc, sql, shard);
|
|
52
58
|
lc.info?.(`Upstream is setup with publications [${publications}]`);
|
|
@@ -67,7 +73,6 @@ async function initialSync(lc, shard, tx, upstreamURI, syncOptions, context) {
|
|
|
67
73
|
}
|
|
68
74
|
const initialVersion = toStateVersionString(lsn);
|
|
69
75
|
initReplicationState(tx, publications, initialVersion, context);
|
|
70
|
-
const start = performance.now();
|
|
71
76
|
const published = await runTx(sql, async (tx) => {
|
|
72
77
|
await tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshot}'`);
|
|
73
78
|
return getPublicationInfo(tx, publications);
|
|
@@ -89,16 +94,12 @@ async function initialSync(lc, shard, tx, upstreamURI, syncOptions, context) {
|
|
|
89
94
|
const downloads = await Promise.all(tables.map((spec) => copiers.processReadTask((db, lc) => getInitialDownloadState(lc, db, spec, shadow !== void 0))));
|
|
90
95
|
statusPublisher.publish(lc, "Initializing", `Copying ${numTables} upstream tables at version ${initialVersion}`, 5e3, () => ({ downloadStatus: downloads.map(({ status }) => status) }));
|
|
91
96
|
copyProfiler?.start();
|
|
92
|
-
const
|
|
97
|
+
const copyStart = performance.now();
|
|
98
|
+
const copyResults = await Promise.all(downloads.map((table) => copiers.processReadTask((db, lc) => copy(lc, table, copyPool, db, tx, textCopy, syncMode, sampleRate, maxRowsPerTable))));
|
|
99
|
+
const copyElapsed = performance.now() - copyStart;
|
|
93
100
|
copyProfiler?.stopAndDispose(lc, "initial-copy");
|
|
94
101
|
copiers.setDone();
|
|
95
|
-
const
|
|
96
|
-
rows: acc.rows + curr.rows,
|
|
97
|
-
flushTime: acc.flushTime + curr.flushTime
|
|
98
|
-
}), {
|
|
99
|
-
rows: 0,
|
|
100
|
-
flushTime: 0
|
|
101
|
-
});
|
|
102
|
+
const copySummary = initialSyncCopySummary(copyResults, copyElapsed);
|
|
102
103
|
statusPublisher.publish(lc, "Indexing", `Creating ${indexes.length} indexes`, 5e3);
|
|
103
104
|
const indexStart = performance.now();
|
|
104
105
|
createLiteIndices(lc, tx, indexes);
|
|
@@ -108,16 +109,45 @@ async function initialSync(lc, shard, tx, upstreamURI, syncOptions, context) {
|
|
|
108
109
|
else {
|
|
109
110
|
assert(shadow, "expected to be in shadow sync if there is no slotName");
|
|
110
111
|
const rowsByTable = /* @__PURE__ */ new Map();
|
|
111
|
-
for (let i = 0; i < downloads.length; i++) rowsByTable.set(downloads[i].status.table,
|
|
112
|
+
for (let i = 0; i < downloads.length; i++) rowsByTable.set(downloads[i].status.table, copyResults[i].rows);
|
|
112
113
|
verifyShadowReplica(lc, tx, published, rowsByTable);
|
|
113
114
|
}
|
|
114
115
|
const elapsed = performance.now() - start;
|
|
115
|
-
|
|
116
|
+
const copyOtherMs = Math.max(0, elapsed - copySummary.flushMs - index);
|
|
117
|
+
recordInitialSyncRunMetrics({
|
|
118
|
+
durationMs: elapsed,
|
|
119
|
+
rows: copySummary.rows,
|
|
120
|
+
copyBytes: copySummary.copyBytes,
|
|
121
|
+
copyMs: copySummary.copyMs,
|
|
122
|
+
copyOtherMs,
|
|
123
|
+
flushMs: copySummary.flushMs,
|
|
124
|
+
indexMs: index
|
|
125
|
+
}, {
|
|
126
|
+
result: "success",
|
|
127
|
+
syncMode,
|
|
128
|
+
copyFormat
|
|
129
|
+
});
|
|
130
|
+
lc.info?.(`Synced ${copySummary.rows.toLocaleString()} rows of ${numTables} tables in ${publications} up to ${lsn} (flush: ${copySummary.flushMs.toFixed(3)}, index: ${index.toFixed(3)}, total: ${elapsed.toFixed(3)} ms)`, {
|
|
131
|
+
syncMode,
|
|
132
|
+
copyFormat,
|
|
133
|
+
publications,
|
|
134
|
+
lsn,
|
|
135
|
+
...copySummary,
|
|
136
|
+
indexes: indexes.length,
|
|
137
|
+
indexMs: index,
|
|
138
|
+
copyOtherMs,
|
|
139
|
+
totalMs: elapsed
|
|
140
|
+
});
|
|
116
141
|
} finally {
|
|
117
142
|
copyPool.end().catch((e) => lc.warn?.(`Error closing copyPool`, e));
|
|
118
143
|
}
|
|
119
144
|
} catch (e) {
|
|
120
|
-
|
|
145
|
+
recordInitialSyncRunMetrics({ durationMs: performance.now() - start }, {
|
|
146
|
+
result: "error",
|
|
147
|
+
syncMode,
|
|
148
|
+
copyFormat
|
|
149
|
+
});
|
|
150
|
+
if (slotName && sql) {
|
|
121
151
|
lc.warn?.(`dropping replication slot ${slotName}`, e);
|
|
122
152
|
await sql`
|
|
123
153
|
SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots
|
|
@@ -129,7 +159,7 @@ async function initialSync(lc, shard, tx, upstreamURI, syncOptions, context) {
|
|
|
129
159
|
statusPublisher.stop();
|
|
130
160
|
if (releaseShadowSnapshot) await releaseShadowSnapshot().catch((e) => lc.warn?.(`Error releasing shadow snapshot`, e));
|
|
131
161
|
if (replicationSession) await replicationSession.end();
|
|
132
|
-
await sql.end();
|
|
162
|
+
if (sql) await sql.end();
|
|
133
163
|
}
|
|
134
164
|
}
|
|
135
165
|
/**
|
|
@@ -368,6 +398,113 @@ function makeDownloadStatements(table, cols, sampleRate, maxRowsPerTable, select
|
|
|
368
398
|
getTotalBytes: `SELECT ${totalBytes} AS "totalBytes" ${fromTable}`
|
|
369
399
|
};
|
|
370
400
|
}
|
|
401
|
+
var INITIAL_SYNC_DURATION_HISTOGRAM_BOUNDARIES_S = [
|
|
402
|
+
1,
|
|
403
|
+
2,
|
|
404
|
+
5,
|
|
405
|
+
10,
|
|
406
|
+
30,
|
|
407
|
+
60,
|
|
408
|
+
120,
|
|
409
|
+
300,
|
|
410
|
+
600,
|
|
411
|
+
1200,
|
|
412
|
+
2400,
|
|
413
|
+
3600,
|
|
414
|
+
7200
|
|
415
|
+
];
|
|
416
|
+
var SLOW_COPY_FLUSH_MS = 1e4;
|
|
417
|
+
function initialSyncRuns() {
|
|
418
|
+
return getOrCreateCounter("replication", "initial_sync_runs", "Initial sync runs, labeled by result.");
|
|
419
|
+
}
|
|
420
|
+
function initialSyncDuration() {
|
|
421
|
+
return initialSyncDurationHistogram("initial_sync_duration", "Wall-clock duration of an initial sync run, labeled by result.");
|
|
422
|
+
}
|
|
423
|
+
function initialSyncCopyDuration() {
|
|
424
|
+
return initialSyncDurationHistogram("initial_sync_copy_duration", "Wall-clock duration of the COPY phase for a successful initial sync run.");
|
|
425
|
+
}
|
|
426
|
+
function initialSyncCopyOtherDuration() {
|
|
427
|
+
return initialSyncDurationHistogram("initial_sync_copy_other_duration", "Initial sync total duration excluding SQLite flush and index time for a successful run.");
|
|
428
|
+
}
|
|
429
|
+
function initialSyncFlushDuration() {
|
|
430
|
+
return initialSyncDurationHistogram("initial_sync_flush_duration", "Total SQLite flush time for a successful initial sync run.");
|
|
431
|
+
}
|
|
432
|
+
function initialSyncIndexDuration() {
|
|
433
|
+
return initialSyncDurationHistogram("initial_sync_index_duration", "SQLite index creation time for a successful initial sync run.");
|
|
434
|
+
}
|
|
435
|
+
function initialSyncRows() {
|
|
436
|
+
return getOrCreateCounter("replication", "initial_sync_rows", "Rows copied during successful initial sync runs.");
|
|
437
|
+
}
|
|
438
|
+
function initialSyncCopyStream() {
|
|
439
|
+
return getOrCreateCounter("replication", "initial_sync_copy_stream", {
|
|
440
|
+
description: "PostgreSQL COPY stream bytes processed during initial sync, including in-progress and failed runs.",
|
|
441
|
+
unit: "bytes"
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
function initialSyncCompletedCopyStream() {
|
|
445
|
+
return getOrCreateCounter("replication", "initial_sync_completed_copy_stream", {
|
|
446
|
+
description: "PostgreSQL COPY stream bytes processed during successful initial sync runs.",
|
|
447
|
+
unit: "bytes"
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
function initialSyncCopyChunks() {
|
|
451
|
+
return getOrCreateCounter("replication", "initial_sync_copy_chunks", "PostgreSQL COPY stream chunks processed during initial sync.");
|
|
452
|
+
}
|
|
453
|
+
function initialSyncDurationHistogram(name, description) {
|
|
454
|
+
return getOrCreateHistogram("replication", name, {
|
|
455
|
+
description,
|
|
456
|
+
unit: "s",
|
|
457
|
+
bucketBoundaries: INITIAL_SYNC_DURATION_HISTOGRAM_BOUNDARIES_S
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
function initialSyncMetricAttrs(attrs) {
|
|
461
|
+
return {
|
|
462
|
+
sync_mode: attrs.syncMode,
|
|
463
|
+
copy_format: attrs.copyFormat
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function initialSyncRunMetricAttrs(attrs) {
|
|
467
|
+
return {
|
|
468
|
+
...initialSyncMetricAttrs(attrs),
|
|
469
|
+
result: attrs.result
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
function recordInitialSyncRunMetrics(stats, attrs) {
|
|
473
|
+
const labels = initialSyncRunMetricAttrs(attrs);
|
|
474
|
+
initialSyncRuns().add(1, labels);
|
|
475
|
+
initialSyncDuration().recordMs(stats.durationMs, labels);
|
|
476
|
+
if (attrs.result === "success") {
|
|
477
|
+
if (stats.copyMs !== void 0) initialSyncCopyDuration().recordMs(stats.copyMs, labels);
|
|
478
|
+
if (stats.copyOtherMs !== void 0) initialSyncCopyOtherDuration().recordMs(stats.copyOtherMs, labels);
|
|
479
|
+
if (stats.flushMs !== void 0) initialSyncFlushDuration().recordMs(stats.flushMs, labels);
|
|
480
|
+
if (stats.indexMs !== void 0) initialSyncIndexDuration().recordMs(stats.indexMs, labels);
|
|
481
|
+
if (stats.rows !== void 0 && stats.rows > 0) initialSyncRows().add(stats.rows, labels);
|
|
482
|
+
if (stats.copyBytes !== void 0 && stats.copyBytes > 0) initialSyncCompletedCopyStream().add(stats.copyBytes, labels);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function initialSyncCopySummary(results, copyMs) {
|
|
486
|
+
const totals = results.reduce((acc, curr) => {
|
|
487
|
+
acc.rows += curr.rows;
|
|
488
|
+
acc.flushMs += curr.flushMs;
|
|
489
|
+
acc.copyBytes += curr.copyBytes;
|
|
490
|
+
return acc;
|
|
491
|
+
}, {
|
|
492
|
+
rows: 0,
|
|
493
|
+
flushMs: 0,
|
|
494
|
+
copyBytes: 0
|
|
495
|
+
});
|
|
496
|
+
return {
|
|
497
|
+
tables: results.length,
|
|
498
|
+
rows: totals.rows,
|
|
499
|
+
copyMs,
|
|
500
|
+
flushMs: totals.flushMs,
|
|
501
|
+
copyBytes: totals.copyBytes
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function logSlowCopyFlush(lc, details) {
|
|
505
|
+
if (details.elapsedMs < SLOW_COPY_FLUSH_MS || details.flushedRows === 0) return;
|
|
506
|
+
lc.info?.("initial-sync table copy slow flush", details);
|
|
507
|
+
}
|
|
371
508
|
async function getInitialDownloadState(lc, sql, spec, skipTotals) {
|
|
372
509
|
const start = performance.now();
|
|
373
510
|
const table = liteTableName(spec);
|
|
@@ -403,13 +540,21 @@ async function getInitialDownloadState(lc, sql, spec, skipTotals) {
|
|
|
403
540
|
lc.info?.(`Computed initial download state for ${table} (${elapsed} ms)`, { state: state.status });
|
|
404
541
|
return state;
|
|
405
542
|
}
|
|
406
|
-
function copy(lc, { spec: table, status }, dbClient, from, to, textCopy, sampleRate, maxRowsPerTable) {
|
|
407
|
-
if (textCopy) return copyText(lc, table, status, dbClient, from, to, sampleRate, maxRowsPerTable);
|
|
408
|
-
return copyBinary(lc, table, status, from, to, sampleRate, maxRowsPerTable);
|
|
543
|
+
function copy(lc, { spec: table, status }, dbClient, from, to, textCopy, syncMode, sampleRate, maxRowsPerTable) {
|
|
544
|
+
if (textCopy) return copyText(lc, table, status, dbClient, from, to, syncMode, sampleRate, maxRowsPerTable);
|
|
545
|
+
return copyBinary(lc, table, status, from, to, syncMode, sampleRate, maxRowsPerTable);
|
|
409
546
|
}
|
|
410
|
-
async function copyBinary(lc, table, status, from, to, sampleRate, maxRowsPerTable) {
|
|
547
|
+
async function copyBinary(lc, table, status, from, to, syncMode, sampleRate, maxRowsPerTable) {
|
|
411
548
|
const start = performance.now();
|
|
412
|
-
|
|
549
|
+
const copyFormat = "binary";
|
|
550
|
+
const metricLabels = initialSyncMetricAttrs({
|
|
551
|
+
syncMode,
|
|
552
|
+
copyFormat
|
|
553
|
+
});
|
|
554
|
+
const copyStreamMetric = initialSyncCopyStream();
|
|
555
|
+
const copyChunksMetric = initialSyncCopyChunks();
|
|
556
|
+
let flushMs = 0;
|
|
557
|
+
let copyBytes = 0;
|
|
413
558
|
const tableName = liteTableName(table);
|
|
414
559
|
const orderedColumns = Object.entries(table.columns);
|
|
415
560
|
const columnNames = orderedColumns.map(([c]) => c);
|
|
@@ -428,7 +573,7 @@ async function copyBinary(lc, table, status, from, to, sampleRate, maxRowsPerTab
|
|
|
428
573
|
let pendingRows = 0;
|
|
429
574
|
let pendingSize = 0;
|
|
430
575
|
function flush() {
|
|
431
|
-
const
|
|
576
|
+
const flushStart = performance.now();
|
|
432
577
|
const flushedRows = pendingRows;
|
|
433
578
|
const flushedSize = pendingSize;
|
|
434
579
|
let l = 0;
|
|
@@ -438,9 +583,21 @@ async function copyBinary(lc, table, status, from, to, sampleRate, maxRowsPerTab
|
|
|
438
583
|
for (let i = 0; i < flushedValues; i++) pendingValues[i] = void 0;
|
|
439
584
|
pendingSize = 0;
|
|
440
585
|
status.rows += flushedRows;
|
|
441
|
-
const elapsed = performance.now() -
|
|
442
|
-
|
|
586
|
+
const elapsed = performance.now() - flushStart;
|
|
587
|
+
flushMs += elapsed;
|
|
443
588
|
lc.debug?.(`flushed ${flushedRows} ${tableName} rows (${flushedSize} bytes) in ${elapsed.toFixed(3)} ms`);
|
|
589
|
+
logSlowCopyFlush(lc, {
|
|
590
|
+
schema: table.schema,
|
|
591
|
+
table: table.name,
|
|
592
|
+
replicaTable: tableName,
|
|
593
|
+
syncMode,
|
|
594
|
+
copyFormat,
|
|
595
|
+
elapsedMs: elapsed,
|
|
596
|
+
flushedRows,
|
|
597
|
+
flushedBytes: flushedSize,
|
|
598
|
+
rows: status.rows,
|
|
599
|
+
copyBytes
|
|
600
|
+
});
|
|
444
601
|
}
|
|
445
602
|
const binaryParser = new BinaryCopyParser();
|
|
446
603
|
let col = 0;
|
|
@@ -449,8 +606,12 @@ async function copyBinary(lc, table, status, from, to, sampleRate, maxRowsPerTab
|
|
|
449
606
|
highWaterMark: BUFFERED_SIZE_THRESHOLD,
|
|
450
607
|
write(chunk, _encoding, callback) {
|
|
451
608
|
try {
|
|
609
|
+
copyBytes += chunk.length;
|
|
610
|
+
copyStreamMetric.add(chunk.length, metricLabels);
|
|
611
|
+
copyChunksMetric.add(1, metricLabels);
|
|
452
612
|
for (const fieldBuf of binaryParser.parse(chunk)) {
|
|
453
|
-
|
|
613
|
+
const fieldSize = fieldBuf === null ? 4 : fieldBuf.length;
|
|
614
|
+
pendingSize += fieldSize;
|
|
454
615
|
pendingValues[pendingRows * valuesPerRow + col] = fieldBuf === null ? null : decoders[col](fieldBuf);
|
|
455
616
|
if (++col === decoders.length) {
|
|
456
617
|
col = 0;
|
|
@@ -472,15 +633,34 @@ async function copyBinary(lc, table, status, from, to, sampleRate, maxRowsPerTab
|
|
|
472
633
|
}
|
|
473
634
|
}));
|
|
474
635
|
const elapsed = performance.now() - start;
|
|
475
|
-
|
|
476
|
-
|
|
636
|
+
const result = {
|
|
637
|
+
schema: table.schema,
|
|
638
|
+
table: table.name,
|
|
639
|
+
replicaTable: tableName,
|
|
640
|
+
syncMode,
|
|
641
|
+
copyFormat,
|
|
642
|
+
columnCount: columnNames.length,
|
|
477
643
|
rows: status.rows,
|
|
478
|
-
|
|
644
|
+
estimatedRows: status.totalRows,
|
|
645
|
+
estimatedBytes: status.totalBytes,
|
|
646
|
+
flushMs,
|
|
647
|
+
elapsedMs: elapsed,
|
|
648
|
+
copyBytes
|
|
479
649
|
};
|
|
650
|
+
lc.info?.(`Finished copying ${status.rows} rows into ${tableName} (flush: ${flushMs.toFixed(3)} ms) (total: ${elapsed.toFixed(3)} ms) `, result);
|
|
651
|
+
return result;
|
|
480
652
|
}
|
|
481
|
-
async function copyText(lc, table, status, dbClient, from, to, sampleRate, maxRowsPerTable) {
|
|
653
|
+
async function copyText(lc, table, status, dbClient, from, to, syncMode, sampleRate, maxRowsPerTable) {
|
|
482
654
|
const start = performance.now();
|
|
483
|
-
|
|
655
|
+
const copyFormat = "text";
|
|
656
|
+
const metricLabels = initialSyncMetricAttrs({
|
|
657
|
+
syncMode,
|
|
658
|
+
copyFormat
|
|
659
|
+
});
|
|
660
|
+
const copyStreamMetric = initialSyncCopyStream();
|
|
661
|
+
const copyChunksMetric = initialSyncCopyChunks();
|
|
662
|
+
let flushMs = 0;
|
|
663
|
+
let copyBytes = 0;
|
|
484
664
|
const tableName = liteTableName(table);
|
|
485
665
|
const orderedColumns = Object.entries(table.columns);
|
|
486
666
|
const columnNames = orderedColumns.map(([c]) => c);
|
|
@@ -498,7 +678,7 @@ async function copyText(lc, table, status, dbClient, from, to, sampleRate, maxRo
|
|
|
498
678
|
let pendingRows = 0;
|
|
499
679
|
let pendingSize = 0;
|
|
500
680
|
function flush() {
|
|
501
|
-
const
|
|
681
|
+
const flushStart = performance.now();
|
|
502
682
|
const flushedRows = pendingRows;
|
|
503
683
|
const flushedSize = pendingSize;
|
|
504
684
|
let l = 0;
|
|
@@ -508,9 +688,21 @@ async function copyText(lc, table, status, dbClient, from, to, sampleRate, maxRo
|
|
|
508
688
|
for (let i = 0; i < flushedValues; i++) pendingValues[i] = void 0;
|
|
509
689
|
pendingSize = 0;
|
|
510
690
|
status.rows += flushedRows;
|
|
511
|
-
const elapsed = performance.now() -
|
|
512
|
-
|
|
691
|
+
const elapsed = performance.now() - flushStart;
|
|
692
|
+
flushMs += elapsed;
|
|
513
693
|
lc.debug?.(`flushed ${flushedRows} ${tableName} rows (${flushedSize} bytes) in ${elapsed.toFixed(3)} ms`);
|
|
694
|
+
logSlowCopyFlush(lc, {
|
|
695
|
+
schema: table.schema,
|
|
696
|
+
table: table.name,
|
|
697
|
+
replicaTable: tableName,
|
|
698
|
+
syncMode,
|
|
699
|
+
copyFormat,
|
|
700
|
+
elapsedMs: elapsed,
|
|
701
|
+
flushedRows,
|
|
702
|
+
flushedBytes: flushedSize,
|
|
703
|
+
rows: status.rows,
|
|
704
|
+
copyBytes
|
|
705
|
+
});
|
|
514
706
|
}
|
|
515
707
|
lc.info?.(`Starting text copy stream of ${tableName}:`, select);
|
|
516
708
|
const pgParsers = await getTypeParsers(dbClient, { returnJsonAsString: true });
|
|
@@ -524,8 +716,12 @@ async function copyText(lc, table, status, dbClient, from, to, sampleRate, maxRo
|
|
|
524
716
|
highWaterMark: BUFFERED_SIZE_THRESHOLD,
|
|
525
717
|
write(chunk, _encoding, callback) {
|
|
526
718
|
try {
|
|
719
|
+
copyBytes += chunk.length;
|
|
720
|
+
copyStreamMetric.add(chunk.length, metricLabels);
|
|
721
|
+
copyChunksMetric.add(1, metricLabels);
|
|
527
722
|
for (const text of tsvParser.parse(chunk)) {
|
|
528
|
-
|
|
723
|
+
const fieldSize = text === null ? 4 : text.length;
|
|
724
|
+
pendingSize += fieldSize;
|
|
529
725
|
pendingValues[pendingRows * valuesPerRow + col] = text === null ? null : parsers[col](text);
|
|
530
726
|
if (++col === parsers.length) {
|
|
531
727
|
col = 0;
|
|
@@ -547,11 +743,22 @@ async function copyText(lc, table, status, dbClient, from, to, sampleRate, maxRo
|
|
|
547
743
|
}
|
|
548
744
|
}));
|
|
549
745
|
const elapsed = performance.now() - start;
|
|
550
|
-
|
|
551
|
-
|
|
746
|
+
const result = {
|
|
747
|
+
schema: table.schema,
|
|
748
|
+
table: table.name,
|
|
749
|
+
replicaTable: tableName,
|
|
750
|
+
syncMode,
|
|
751
|
+
copyFormat,
|
|
752
|
+
columnCount: columnNames.length,
|
|
552
753
|
rows: status.rows,
|
|
553
|
-
|
|
754
|
+
estimatedRows: status.totalRows,
|
|
755
|
+
estimatedBytes: status.totalBytes,
|
|
756
|
+
flushMs,
|
|
757
|
+
elapsedMs: elapsed,
|
|
758
|
+
copyBytes
|
|
554
759
|
};
|
|
760
|
+
lc.info?.(`Finished copying ${status.rows} rows into ${tableName} (flush: ${flushMs.toFixed(3)} ms) (total: ${elapsed.toFixed(3)} ms) `, result);
|
|
761
|
+
return result;
|
|
555
762
|
}
|
|
556
763
|
//#endregion
|
|
557
764
|
export { initialSync, makeBinarySelectExprs, makeDownloadStatements, shadowInitialSync };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initial-sync.js","names":[],"sources":["../../../../../../../zero-cache/src/services/change-source/pg/initial-sync.ts"],"sourcesContent":["import {mkdtemp, rm} from 'node:fs/promises';\nimport {platform, tmpdir} from 'node:os';\nimport {join} from 'node:path';\nimport {Writable} from 'node:stream';\nimport {pipeline} from 'node:stream/promises';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {assert} from '../../../../../shared/src/asserts.ts';\nimport type {JSONObject} from '../../../../../shared/src/bigint-json.ts';\nimport {must} from '../../../../../shared/src/must.ts';\nimport {equals} from '../../../../../shared/src/set-utils.ts';\nimport type {DownloadStatus} from '../../../../../zero-events/src/status.ts';\nimport {Database} from '../../../../../zqlite/src/db.ts';\nimport {\n createLiteIndexStatement,\n createLiteTableStatement,\n} from '../../../db/create.ts';\nimport {listIndexes, listTables} from '../../../db/lite-tables.ts';\nimport * as Mode from '../../../db/mode-enum.ts';\nimport {\n BinaryCopyParser,\n hasBinaryDecoder,\n makeBinaryDecoder,\n textCastDecoder,\n} from '../../../db/pg-copy-binary.ts';\nimport {TsvParser} from '../../../db/pg-copy.ts';\nimport {\n mapPostgresToLite,\n mapPostgresToLiteIndex,\n} from '../../../db/pg-to-lite.ts';\nimport {getTypeParsers} from '../../../db/pg-type-parser.ts';\nimport {runTx} from '../../../db/run-transaction.ts';\nimport type {IndexSpec, PublishedTableSpec} from '../../../db/specs.ts';\nimport {importSnapshot, TransactionPool} from '../../../db/transaction-pool.ts';\nimport {\n JSON_STRINGIFIED,\n liteValue,\n type LiteValueType,\n} from '../../../types/lite.ts';\nimport {liteTableName} from '../../../types/names.ts';\nimport {PG_15, PG_17} from '../../../types/pg-versions.ts';\nimport {\n connectPgClient,\n pgClient,\n type PostgresDB,\n type PostgresTransaction,\n type PostgresValueType,\n} from '../../../types/pg.ts';\nimport {CpuProfiler} from '../../../types/profiler.ts';\nimport type {ShardConfig} from '../../../types/shards.ts';\nimport {ALLOWED_APP_ID_CHARACTERS} from '../../../types/shards.ts';\nimport {id} from '../../../types/sql.ts';\nimport {ReplicationStatusPublisher} from '../../replicator/replication-status.ts';\nimport {ColumnMetadataStore} from '../../replicator/schema/column-metadata.ts';\nimport {initReplicationState} from '../../replicator/schema/replication-state.ts';\nimport {toStateVersionString} from './lsn.ts';\nimport {createReplicaAndSlot} from './replication-slots.ts';\nimport {ensureShardSchema} from './schema/init.ts';\nimport {getPublicationInfo} from './schema/published.ts';\nimport {\n dropShard,\n getInternalShardConfig,\n initReplica,\n validatePublications,\n} from './schema/shard.ts';\n\nexport type InitialSyncOptions = {\n tableCopyWorkers: number;\n profileCopy?: boolean | undefined;\n textCopy?: boolean | undefined;\n replicationSlotFailover?: boolean | undefined;\n /**\n * When set, run initial sync in \"shadow\" mode for verification: skip all\n * upstream mutations (no replication slot, no addReplica, no dropShard, no\n * slot drop on failure), suppress status events, and optionally sample\n * rows from each table via TABLESAMPLE BERNOULLI + LIMIT. The caller is\n * responsible for providing (and discarding) a throwaway SQLite `tx`.\n */\n shadow?:\n | {\n /** 0 < rate <= 1. When 1, no TABLESAMPLE clause is added. */\n sampleRate: number;\n /**\n * LIMIT N cap appended after TABLESAMPLE. Required: shadow sync is\n * for verification only, so every run must commit to a row budget.\n */\n maxRowsPerTable: number;\n }\n | undefined;\n};\n\n/** Server context to store with the initial sync metadata for debugging. */\nexport type ServerContext = JSONObject;\n\nexport async function initialSync(\n lc: LogContext,\n shard: ShardConfig,\n tx: Database,\n upstreamURI: string,\n syncOptions: InitialSyncOptions,\n context: ServerContext,\n) {\n if (!ALLOWED_APP_ID_CHARACTERS.test(shard.appID)) {\n throw new Error(\n 'The App ID may only consist of lower-case letters, numbers, and the underscore character',\n );\n }\n const {\n tableCopyWorkers,\n profileCopy,\n textCopy = false,\n replicationSlotFailover = false,\n shadow,\n } = syncOptions;\n const copyProfiler = profileCopy ? await CpuProfiler.connect() : null;\n const sql = await connectPgClient(lc, upstreamURI, 'initial-sync');\n // Replication session is only needed to create a replication slot in the\n // real path. In shadow mode we export a snapshot on a normal connection\n // instead, so no replication session is opened.\n const replicationSession = shadow\n ? undefined\n : pgClient(lc, upstreamURI, 'initial-sync-replication-session', {\n ['fetch_types']: false, // Necessary for the streaming protocol\n connection: {replication: 'database'}, // https://www.postgresql.org/docs/current/protocol-replication.html\n });\n\n const replicaID = Date.now().toString();\n let slotName: string | undefined; // undefined === shadow\n const statusPublisher = ReplicationStatusPublisher.forRunningTransaction(\n tx,\n shadow ? async () => {} : undefined,\n ).publish(lc, 'Initializing');\n let releaseShadowSnapshot: (() => Promise<void>) | undefined;\n try {\n const pgVersion = await checkUpstreamConfig(sql);\n\n // In shadow mode we assume the shard is already initialized and just\n // read back the existing publications. `ensurePublishedTables` would\n // otherwise run DDL and potentially call `dropShard`, which must never\n // happen during a shadow run.\n const {publications} = shadow\n ? await getInternalShardConfig(sql, shard)\n : await ensurePublishedTables(lc, sql, shard);\n lc.info?.(`Upstream is setup with publications [${publications}]`);\n\n const {database, host} = sql.options;\n lc.info?.(\n shadow\n ? `acquiring exported snapshot on ${database}@${host} (shadow mode)`\n : `opening replication session to ${database}@${host}`,\n );\n\n let snapshot: string;\n let lsn: string;\n\n if (shadow) {\n const acquired = await acquireExportedSnapshotForShadowSync(\n lc,\n upstreamURI,\n );\n snapshot = acquired.snapshot;\n lsn = acquired.lsn;\n releaseShadowSnapshot = acquired.release;\n } else {\n const slot = await createReplicaAndSlot(\n lc,\n sql,\n must(replicationSession),\n shard,\n replicaID,\n replicationSlotFailover && pgVersion >= PG_17,\n );\n snapshot = slot.snapshot_name;\n lsn = slot.consistent_point;\n slotName = slot.slot_name;\n }\n\n const initialVersion = toStateVersionString(lsn);\n\n initReplicationState(tx, publications, initialVersion, context);\n\n // Run up to MAX_WORKERS to copy of tables at the replication slot's snapshot.\n const start = performance.now();\n // Retrieve the published schema at the consistent_point.\n const published = await runTx(\n sql,\n async tx => {\n await tx.unsafe(/* sql*/ `SET TRANSACTION SNAPSHOT '${snapshot}'`);\n return getPublicationInfo(tx, publications);\n },\n {mode: Mode.READONLY},\n );\n // Note: If this throws, initial-sync is aborted.\n validatePublications(lc, published);\n\n // Now that tables have been validated, kick off the copiers.\n const {tables, indexes} = published;\n const numTables = tables.length;\n if (platform() === 'win32' && tableCopyWorkers < numTables) {\n lc.warn?.(\n `Increasing the number of copy workers from ${tableCopyWorkers} to ` +\n `${numTables} to work around a Node/Postgres connection bug`,\n );\n }\n const numWorkers =\n platform() === 'win32'\n ? numTables\n : Math.min(tableCopyWorkers, numTables);\n\n const copyPool = await connectPgClient(\n lc,\n upstreamURI,\n 'initial-sync-copy-worker',\n {\n max: numWorkers,\n ['max_lifetime']: 120 * 60, // set a long (2h) limit for COPY streaming\n },\n );\n const copiers = startTableCopyWorkers(\n lc,\n copyPool,\n snapshot,\n numWorkers,\n numTables,\n );\n try {\n createLiteTables(tx, tables, initialVersion);\n const sampleRate = shadow?.sampleRate;\n const maxRowsPerTable = shadow?.maxRowsPerTable;\n const downloads = await Promise.all(\n tables.map(spec =>\n copiers.processReadTask((db, lc) =>\n getInitialDownloadState(lc, db, spec, shadow !== undefined),\n ),\n ),\n );\n statusPublisher.publish(\n lc,\n 'Initializing',\n `Copying ${numTables} upstream tables at version ${initialVersion}`,\n 5000,\n () => ({downloadStatus: downloads.map(({status}) => status)}),\n );\n\n void copyProfiler?.start();\n const rowCounts = await Promise.all(\n downloads.map(table =>\n copiers.processReadTask((db, lc) =>\n copy(\n lc,\n table,\n copyPool,\n db,\n tx,\n textCopy,\n sampleRate,\n maxRowsPerTable,\n ),\n ),\n ),\n );\n void copyProfiler?.stopAndDispose(lc, 'initial-copy');\n copiers.setDone();\n\n const total = rowCounts.reduce(\n (acc, curr) => ({\n rows: acc.rows + curr.rows,\n flushTime: acc.flushTime + curr.flushTime,\n }),\n {rows: 0, flushTime: 0},\n );\n\n statusPublisher.publish(\n lc,\n 'Indexing',\n `Creating ${indexes.length} indexes`,\n 5000,\n );\n const indexStart = performance.now();\n createLiteIndices(lc, tx, indexes);\n const index = performance.now() - indexStart;\n lc.info?.(`Created indexes (${index.toFixed(3)} ms)`);\n\n if (slotName && replicaID) {\n await initReplica(sql, shard, replicaID, published, context);\n } else {\n assert(shadow, 'expected to be in shadow sync if there is no slotName');\n const rowsByTable = new Map<string, number>();\n for (let i = 0; i < downloads.length; i++) {\n rowsByTable.set(downloads[i].status.table, rowCounts[i].rows);\n }\n verifyShadowReplica(lc, tx, published, rowsByTable);\n }\n\n const elapsed = performance.now() - start;\n lc.info?.(\n `Synced ${total.rows.toLocaleString()} rows of ${numTables} tables in ${publications} up to ${lsn} ` +\n `(flush: ${total.flushTime.toFixed(3)}, index: ${index.toFixed(3)}, total: ${elapsed.toFixed(3)} ms)`,\n );\n } finally {\n // All meaningful errors are handled at the processReadTask() call site.\n void copyPool.end().catch(e => lc.warn?.(`Error closing copyPool`, e));\n }\n } catch (e) {\n if (slotName) {\n // If initial-sync did not succeed, make a best effort to drop the\n // orphaned replication slot to avoid running out of slots in\n // pathological cases that result in repeated failures.\n lc.warn?.(`dropping replication slot ${slotName}`, e);\n await sql`\n SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots\n WHERE slot_name = ${slotName};\n `.catch(e => lc.warn?.(`Unable to drop replication slot ${slotName}`, e));\n }\n await statusPublisher.publishAndThrowError(lc, 'Initializing', e);\n } finally {\n statusPublisher.stop();\n if (releaseShadowSnapshot) {\n await releaseShadowSnapshot().catch(e =>\n lc.warn?.(`Error releasing shadow snapshot`, e),\n );\n }\n if (replicationSession) {\n await replicationSession.end();\n }\n await sql.end();\n }\n}\n\nexport type ShadowSyncOptions = {\n sampleRate: number;\n maxRowsPerTable: number;\n /**\n * Parent directory for the throwaway SQLite replica. Defaults to the OS\n * tmpdir. Primarily for tests that need to isolate the scratch directory.\n */\n parentDir?: string | undefined;\n};\n\n/**\n * Exercises the initial-sync code path against a sample of rows from every\n * published table, writing into a throwaway SQLite database that is deleted\n * when the run ends. Produces zero upstream mutations: no replication slot,\n * no `addReplica`, no `dropShard`, no status events.\n *\n * Intended to be invoked periodically so that if a customer ever needs a\n * full reset, we have recent confidence that `initialSync` still works.\n * The shard must already be initialized upstream.\n */\nexport async function shadowInitialSync(\n lc: LogContext,\n shard: ShardConfig,\n upstreamURI: string,\n shadow: ShadowSyncOptions,\n context: ServerContext,\n syncOptions?: Pick<InitialSyncOptions, 'textCopy'>,\n): Promise<void> {\n const dir = await mkdtemp(\n join(shadow.parentDir ?? tmpdir(), 'zero-shadow-sync-'),\n );\n const dbPath = join(dir, 'shadow-replica.db');\n const db = new Database(lc, dbPath);\n try {\n await initialSync(\n lc,\n shard,\n db,\n upstreamURI,\n {\n // Shadow sync copies small samples, so one worker is plenty —\n // no reason to burn additional upstream connections.\n tableCopyWorkers: 1,\n textCopy: syncOptions?.textCopy,\n shadow,\n },\n context,\n );\n } finally {\n try {\n db.close();\n } catch (e) {\n lc.warn?.(`Error closing shadow replica db`, e);\n }\n await rm(dir, {recursive: true, force: true}).catch(e =>\n lc.warn?.(`Error cleaning up shadow replica dir ${dir}`, e),\n );\n }\n}\n\nasync function checkUpstreamConfig(sql: PostgresDB) {\n const {walLevel, version} = (\n await sql<{walLevel: string; version: number}[]>`\n SELECT current_setting('wal_level') as \"walLevel\", \n current_setting('server_version_num') as \"version\";\n `\n )[0];\n\n if (walLevel !== 'logical') {\n throw new Error(\n `Postgres must be configured with \"wal_level = logical\" (currently: \"${walLevel})`,\n );\n }\n if (version < PG_15) {\n throw new Error(\n `Must be running Postgres 15 or higher (currently: \"${version}\")`,\n );\n }\n return version;\n}\n\nasync function ensurePublishedTables(\n lc: LogContext,\n sql: PostgresDB,\n shard: ShardConfig,\n validate = true,\n): Promise<{publications: string[]}> {\n const {database, host} = sql.options;\n lc.info?.(`Ensuring upstream PUBLICATION on ${database}@${host}`);\n\n await ensureShardSchema(lc, sql, shard);\n const {publications} = await getInternalShardConfig(sql, shard);\n\n if (validate) {\n let valid = false;\n const nonInternalPublications = publications.filter(\n p => !p.startsWith('_'),\n );\n const exists = await sql`\n SELECT pubname FROM pg_publication WHERE pubname IN ${sql(publications)}\n `.values();\n if (exists.length !== publications.length) {\n lc.warn?.(\n `some configured publications [${publications}] are missing: ` +\n `[${exists.flat()}]. resyncing`,\n );\n } else if (\n !equals(new Set(shard.publications), new Set(nonInternalPublications))\n ) {\n lc.warn?.(\n `requested publications [${shard.publications}] differ from previous` +\n `publications [${nonInternalPublications}]. resyncing`,\n );\n } else {\n valid = true;\n }\n if (!valid) {\n await sql.unsafe(dropShard(shard.appID, shard.shardNum));\n return ensurePublishedTables(lc, sql, shard, false);\n }\n }\n return {publications};\n}\n\nfunction startTableCopyWorkers(\n lc: LogContext,\n db: PostgresDB,\n snapshot: string,\n numWorkers: number,\n numTables: number,\n): TransactionPool {\n const {init} = importSnapshot(snapshot);\n const tableCopiers = new TransactionPool(lc, {\n mode: Mode.READONLY,\n init,\n initialWorkers: numWorkers,\n });\n tableCopiers.run(db);\n\n lc.info?.(`Started ${numWorkers} workers to copy ${numTables} tables`);\n\n if (parseInt(process.versions.node) < 22) {\n lc.warn?.(\n `\\n\\n\\n` +\n `Older versions of Node have a bug that results in an unresponsive\\n` +\n `Postgres connection after running certain combinations of COPY commands.\\n` +\n `If initial sync hangs, run zero-cache with Node v22+. This has the additional\\n` +\n `benefit of being consistent with the Node version run in the production container image.` +\n `\\n\\n\\n`,\n );\n }\n return tableCopiers;\n}\n\n/**\n * Shadow-mode alternative to `createReplicationSlot`: opens a dedicated\n * READ ONLY REPEATABLE READ transaction on a normal connection, exports the\n * snapshot and captures the current WAL LSN, then holds the transaction\n * open until `release()` is called. The held transaction keeps the snapshot\n * importable by the table-copy workers for the duration of the COPY phase.\n *\n * Idle-in-transaction timeout is disabled locally so the exporter doesn't\n * get killed while workers are still importing.\n */\nasync function acquireExportedSnapshotForShadowSync(\n lc: LogContext,\n upstreamURI: string,\n): Promise<{\n snapshot: string;\n lsn: string;\n release: () => Promise<void>;\n}> {\n const holder = await connectPgClient(\n lc,\n upstreamURI,\n 'shadow-initial-sync-snapshot',\n {\n max: 1,\n },\n );\n const ready = resolver<{snapshot: string; lsn: string}>();\n const release = resolver<void>();\n const held = holder\n .begin(Mode.READONLY, async tx => {\n await tx`SET LOCAL idle_in_transaction_session_timeout = 0`.execute();\n const [row] = await tx<{snapshot: string; lsn: string}[]>`\n SELECT pg_export_snapshot() AS snapshot,\n pg_current_wal_lsn()::text AS lsn`;\n ready.resolve(row);\n await release.promise;\n })\n .catch(e => ready.reject(e));\n\n let snapshot: string;\n let lsn: string;\n try {\n ({snapshot, lsn} = await ready.promise);\n } catch (e) {\n await holder\n .end()\n .catch(err =>\n lc.warn?.(`Error ending shadow snapshot holder after failure`, err),\n );\n throw e;\n }\n lc.info?.(\n `Exported snapshot ${snapshot} at LSN ${lsn} (shadow initial sync)`,\n );\n return {\n snapshot,\n lsn,\n release: async () => {\n release.resolve();\n try {\n await held;\n } catch (e) {\n lc.warn?.(`snapshot holder transaction ended with error`, e);\n }\n await holder.end();\n },\n };\n}\n\nfunction createLiteTables(\n tx: Database,\n tables: PublishedTableSpec[],\n initialVersion: string,\n) {\n // TODO: Figure out how to reuse the ChangeProcessor here to avoid\n // duplicating the ColumnMetadata logic.\n const columnMetadata = must(ColumnMetadataStore.getInstance(tx));\n for (const t of tables) {\n tx.exec(createLiteTableStatement(mapPostgresToLite(t, initialVersion)));\n const tableName = liteTableName(t);\n for (const [colName, colSpec] of Object.entries(t.columns)) {\n columnMetadata.insert(tableName, colName, colSpec);\n }\n }\n}\n\nfunction createLiteIndices(lc: LogContext, tx: Database, indices: IndexSpec[]) {\n for (const [i, index] of indices.entries()) {\n const stmt = createLiteIndexStatement(mapPostgresToLiteIndex(index));\n lc.info?.(`Creating index ${i + 1}/${indices.length}: ${stmt}`);\n const start = performance.now();\n tx.exec(stmt);\n lc.info?.(\n `Created index ${i + 1}/${indices.length} ` +\n `(${(performance.now() - start).toFixed(3)} ms): ${stmt}`,\n );\n }\n}\n\n/**\n * Runs structural assertions over a just-synced replica and throws if any\n * fail. Only called in shadow mode — a successful return means the replica\n * is schema-complete, row-count consistent, and its column metadata is in\n * sync with its lite schema.\n *\n * Note: this intentionally does NOT verify ZQL-queryability. Tables that\n * `computeZqlSpecs` drops (no PK / no all-NOT-NULL unique index, unsupported\n * column types, etc.) are silently skipped in production too — there's\n * nothing shadow-specific about them, so failing here would diverge from\n * prod over an upstream-schema condition prod accepts.\n *\n * Exported for testing.\n */\nexport function verifyShadowReplica(\n lc: LogContext,\n db: Database,\n published: {tables: PublishedTableSpec[]; indexes: IndexSpec[]},\n rowsByTable: ReadonlyMap<string, number>,\n): void {\n const issues: string[] = [];\n let columnsChecked = 0;\n let rowsChecked = 0;\n\n // 1. Schema completeness: every published table exists in the replica\n // with at least the expected column set.\n const liteTables = listTables(db);\n const liteTableByName = new Map(liteTables.map(t => [t.name, t]));\n for (const pt of published.tables) {\n const name = liteTableName(pt);\n const lite = liteTableByName.get(name);\n if (!lite) {\n issues.push(`missing table in replica: ${name}`);\n continue;\n }\n for (const col of Object.keys(pt.columns)) {\n columnsChecked++;\n if (!(col in lite.columns)) {\n issues.push(`column missing in replica table ${name}: ${col}`);\n }\n }\n }\n\n // Every published index exists in the replica.\n const liteIndexNames = new Set(listIndexes(db).map(i => i.name));\n for (const ix of published.indexes) {\n const mapped = mapPostgresToLiteIndex(ix);\n if (!liteIndexNames.has(mapped.name)) {\n issues.push(\n `missing index in replica: ${mapped.name} on ${mapped.tableName}`,\n );\n }\n }\n\n // 2. Row counts: SQLite COUNT(*) matches the in-memory copy counter.\n for (const [table, expected] of rowsByTable) {\n try {\n const [row] = db\n .prepare(`SELECT COUNT(*) as count FROM \"${table}\"`)\n .all<{count: number}>();\n if (row.count !== expected) {\n issues.push(\n `row count mismatch for table ${table}: ` +\n `copy counter reported ${expected}, replica has ${row.count}`,\n );\n } else {\n rowsChecked += row.count;\n }\n } catch (e) {\n issues.push(`could not count rows in table ${table}: ${String(e)}`);\n }\n }\n\n // 3. Column metadata: every published column has a _zero.column_metadata row.\n const meta = must(ColumnMetadataStore.getInstance(db));\n for (const pt of published.tables) {\n const name = liteTableName(pt);\n const rows = meta.getTable(name);\n for (const col of Object.keys(pt.columns)) {\n if (!rows.has(col)) {\n issues.push(`missing column_metadata row for ${name}.${col}`);\n }\n }\n }\n\n if (issues.length) {\n throw new Error(\n `Shadow replica verification failed (${issues.length} issue(s)):\\n` +\n issues.map(i => ` - ${i}`).join('\\n'),\n );\n }\n\n lc.info?.(\n `Shadow replica verification passed: ` +\n `${published.tables.length} tables, ` +\n `${published.indexes.length} indexes, ` +\n `${columnsChecked} columns, ` +\n `${rowsChecked.toLocaleString()} rows`,\n );\n}\n\n// Verified empirically that batches of 50 seem to be the sweet spot,\n// similar to the report in https://sqlite.org/forum/forumpost/8878a512d3652655\n//\n// Exported for testing.\nexport const INSERT_BATCH_SIZE = 50;\n\nconst MB = 1024 * 1024;\nconst MAX_BUFFERED_ROWS = 10_000;\nconst BUFFERED_SIZE_THRESHOLD = 8 * MB;\n\nexport type DownloadStatements = {\n select: string;\n getTotalRows: string;\n getTotalBytes: string;\n};\n\n/**\n * Produces ` TABLESAMPLE BERNOULLI(n)` when `sampleRate` is < 1, else `''`.\n * Row-level Bernoulli sampling is used (rather than SYSTEM) because it\n * produces a more uniform sample and, unlike SYSTEM, still returns rows\n * for small tables at low rates.\n */\nfunction tableSampleClause(sampleRate: number | undefined): string {\n if (sampleRate === undefined || sampleRate >= 1) {\n return '';\n }\n // Round away float noise (e.g. 0.3 * 100 = 30.000000000000004) while still\n // preserving sub-integer rates like 0.001 (= 0.1%).\n const pct = parseFloat((sampleRate * 100).toFixed(6));\n return /*sql*/ ` TABLESAMPLE BERNOULLI(${pct})`;\n}\n\nfunction limitClause(maxRowsPerTable: number | undefined): string {\n return maxRowsPerTable !== undefined\n ? /*sql*/ ` LIMIT ${maxRowsPerTable}`\n : '';\n}\n\n/**\n * Returns the SELECT column expressions for binary COPY, casting columns\n * without a known binary decoder to `::text`.\n */\nexport function makeBinarySelectExprs(\n table: PublishedTableSpec,\n cols: string[],\n): string[] {\n return cols.map(col => {\n const spec = table.columns[col];\n return hasBinaryDecoder(spec) ? id(col) : `${id(col)}::text`;\n });\n}\n\nexport function makeDownloadStatements(\n table: PublishedTableSpec,\n cols: string[],\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n selectExprs?: string[] | undefined,\n): DownloadStatements {\n const filterConditions = Object.values(table.publications)\n .map(({rowFilter}) => rowFilter)\n .filter(f => !!f); // remove nulls\n const where =\n filterConditions.length === 0\n ? ''\n : /*sql*/ `WHERE ${filterConditions.join(' OR ')}`;\n const sample = tableSampleClause(sampleRate);\n const limit = limitClause(maxRowsPerTable);\n const fromTable = /*sql*/ `FROM ${id(table.schema)}.${id(table.name)}${sample} ${where}`;\n const select = /*sql*/ `SELECT ${(selectExprs ?? cols.map(id)).join(',')} ${fromTable}${limit}`;\n if (limit) {\n // With LIMIT, wrap counts/sums in a subquery so they reflect the\n // capped rowset rather than the full (sampled) table.\n const bytesExpr = cols\n .map(col => `COALESCE(pg_column_size(${id(col)}), 0)`)\n .join(' + ');\n return {\n select,\n getTotalRows: /*sql*/ `SELECT COUNT(*)::bigint AS \"totalRows\" FROM (SELECT 1 AS _ ${fromTable}${limit}) s`,\n getTotalBytes: /*sql*/ `SELECT COALESCE(SUM(b), 0)::bigint AS \"totalBytes\" FROM (SELECT (${bytesExpr}) AS b ${fromTable}${limit}) s`,\n };\n }\n const totalBytes = `(${cols.map(col => `SUM(COALESCE(pg_column_size(${id(col)}), 0))`).join(' + ')})`;\n return {\n select,\n getTotalRows: /*sql*/ `SELECT COUNT(*) AS \"totalRows\" ${fromTable}`,\n getTotalBytes: /*sql*/ `SELECT ${totalBytes} AS \"totalBytes\" ${fromTable}`,\n };\n}\n\ntype DownloadState = {\n spec: PublishedTableSpec;\n status: DownloadStatus;\n};\n\n// Exported for testing.\nexport async function getInitialDownloadState(\n lc: LogContext,\n sql: PostgresDB,\n spec: PublishedTableSpec,\n skipTotals: boolean,\n): Promise<DownloadState> {\n const start = performance.now();\n const table = liteTableName(spec);\n const columns = Object.keys(spec.columns);\n if (skipTotals) {\n // Shadow sync suppresses status events, so the pg_class\n // estimates would be computed and thrown away.\n return {\n spec,\n status: {table, columns, rows: 0, totalRows: 0, totalBytes: 0},\n };\n }\n // Use pg_class estimates instead of expensive COUNT(*) and\n // SUM(pg_column_size(...)) full table scans. The exact values are only\n // used for progress reporting, so estimates are sufficient.\n const qualifiedName = `${id(spec.schema)}.${id(spec.name)}`;\n const estimateResult = await sql<\n {totalRows: number; totalBytes: number}[]\n >`SELECT GREATEST(reltuples, 0)::float8 AS \"totalRows\",\n pg_table_size(oid)::float8 AS \"totalBytes\"\n FROM pg_class\n WHERE oid = ${qualifiedName}::regclass`;\n\n const {totalRows, totalBytes} = estimateResult[0] ?? {\n totalRows: 0,\n totalBytes: 0,\n };\n\n const state: DownloadState = {\n spec,\n status: {\n table,\n columns,\n rows: 0,\n totalRows,\n totalBytes,\n },\n };\n const elapsed = (performance.now() - start).toFixed(3);\n lc.info?.(`Computed initial download state for ${table} (${elapsed} ms)`, {\n state: state.status,\n });\n return state;\n}\n\nfunction copy(\n lc: LogContext,\n {spec: table, status}: DownloadState,\n dbClient: PostgresDB,\n from: PostgresTransaction,\n to: Database,\n textCopy: boolean,\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n) {\n if (textCopy) {\n return copyText(\n lc,\n table,\n status,\n dbClient,\n from,\n to,\n sampleRate,\n maxRowsPerTable,\n );\n }\n return copyBinary(lc, table, status, from, to, sampleRate, maxRowsPerTable);\n}\n\nasync function copyBinary(\n lc: LogContext,\n table: PublishedTableSpec,\n status: DownloadStatus,\n from: PostgresTransaction,\n to: Database,\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n) {\n const start = performance.now();\n let flushTime = 0;\n\n const tableName = liteTableName(table);\n const orderedColumns = Object.entries(table.columns);\n\n const columnNames = orderedColumns.map(([c]) => c);\n const columnSpecs = orderedColumns.map(([_name, spec]) => spec);\n const insertColumnList = columnNames.map(c => id(c)).join(',');\n\n const valuesSql =\n columnNames.length > 0 ? `(${'?,'.repeat(columnNames.length - 1)}?)` : '()';\n const insertSql = /*sql*/ `\n INSERT INTO \"${tableName}\" (${insertColumnList}) VALUES ${valuesSql}`;\n const insertStmt = to.prepare(insertSql);\n const insertBatchStmt = to.prepare(\n insertSql + `,${valuesSql}`.repeat(INSERT_BATCH_SIZE - 1),\n );\n\n // Build SELECT with ::text casts for columns without a known binary decoder.\n const select = makeDownloadStatements(\n table,\n columnNames,\n sampleRate,\n maxRowsPerTable,\n makeBinarySelectExprs(table, columnNames),\n ).select;\n\n const decoders = orderedColumns.map(([, spec]) =>\n hasBinaryDecoder(spec) ? makeBinaryDecoder(spec) : textCastDecoder,\n );\n\n const valuesPerRow = columnSpecs.length;\n const valuesPerBatch = valuesPerRow * INSERT_BATCH_SIZE;\n\n const pendingValues: LiteValueType[] = Array.from({\n length: MAX_BUFFERED_ROWS * valuesPerRow,\n });\n let pendingRows = 0;\n let pendingSize = 0;\n\n function flush() {\n const start = performance.now();\n const flushedRows = pendingRows;\n const flushedSize = pendingSize;\n\n let l = 0;\n for (; pendingRows > INSERT_BATCH_SIZE; pendingRows -= INSERT_BATCH_SIZE) {\n insertBatchStmt.run(pendingValues.slice(l, (l += valuesPerBatch)));\n }\n for (; pendingRows > 0; pendingRows--) {\n insertStmt.run(pendingValues.slice(l, (l += valuesPerRow)));\n }\n const flushedValues = flushedRows * valuesPerRow;\n for (let i = 0; i < flushedValues; i++) {\n pendingValues[i] = undefined as unknown as LiteValueType;\n }\n pendingSize = 0;\n status.rows += flushedRows;\n\n const elapsed = performance.now() - start;\n flushTime += elapsed;\n lc.debug?.(\n `flushed ${flushedRows} ${tableName} rows (${flushedSize} bytes) in ${elapsed.toFixed(3)} ms`,\n );\n }\n\n const binaryParser = new BinaryCopyParser();\n let col = 0;\n\n lc.info?.(`Starting binary copy stream of ${tableName}:`, select);\n\n await pipeline(\n await from\n .unsafe(`COPY (${select}) TO STDOUT WITH (FORMAT binary)`)\n .readable(),\n new Writable({\n highWaterMark: BUFFERED_SIZE_THRESHOLD,\n\n write(\n chunk: Buffer,\n _encoding: string,\n callback: (error?: Error) => void,\n ) {\n try {\n for (const fieldBuf of binaryParser.parse(chunk)) {\n pendingSize += fieldBuf === null ? 4 : fieldBuf.length;\n pendingValues[pendingRows * valuesPerRow + col] =\n fieldBuf === null ? null : decoders[col](fieldBuf);\n\n if (++col === decoders.length) {\n col = 0;\n if (\n ++pendingRows >= MAX_BUFFERED_ROWS - valuesPerRow ||\n pendingSize >= BUFFERED_SIZE_THRESHOLD\n ) {\n flush();\n }\n }\n }\n callback();\n } catch (e) {\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n\n final: (callback: (error?: Error) => void) => {\n try {\n flush();\n callback();\n } catch (e) {\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n }),\n );\n\n const elapsed = performance.now() - start;\n lc.info?.(\n `Finished copying ${status.rows} rows into ${tableName} ` +\n `(flush: ${flushTime.toFixed(3)} ms) (total: ${elapsed.toFixed(3)} ms) `,\n );\n return {rows: status.rows, flushTime};\n}\n\nasync function copyText(\n lc: LogContext,\n table: PublishedTableSpec,\n status: DownloadStatus,\n dbClient: PostgresDB,\n from: PostgresTransaction,\n to: Database,\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n) {\n const start = performance.now();\n let flushTime = 0;\n\n const tableName = liteTableName(table);\n const orderedColumns = Object.entries(table.columns);\n\n const columnNames = orderedColumns.map(([c]) => c);\n const columnSpecs = orderedColumns.map(([_name, spec]) => spec);\n const insertColumnList = columnNames.map(c => id(c)).join(',');\n\n const valuesSql =\n columnNames.length > 0 ? `(${'?,'.repeat(columnNames.length - 1)}?)` : '()';\n const insertSql = /*sql*/ `\n INSERT INTO \"${tableName}\" (${insertColumnList}) VALUES ${valuesSql}`;\n const insertStmt = to.prepare(insertSql);\n const insertBatchStmt = to.prepare(\n insertSql + `,${valuesSql}`.repeat(INSERT_BATCH_SIZE - 1),\n );\n\n const {select} = makeDownloadStatements(\n table,\n columnNames,\n sampleRate,\n maxRowsPerTable,\n );\n const valuesPerRow = columnSpecs.length;\n const valuesPerBatch = valuesPerRow * INSERT_BATCH_SIZE;\n\n const pendingValues: LiteValueType[] = Array.from({\n length: MAX_BUFFERED_ROWS * valuesPerRow,\n });\n let pendingRows = 0;\n let pendingSize = 0;\n\n function flush() {\n const start = performance.now();\n const flushedRows = pendingRows;\n const flushedSize = pendingSize;\n\n let l = 0;\n for (; pendingRows > INSERT_BATCH_SIZE; pendingRows -= INSERT_BATCH_SIZE) {\n insertBatchStmt.run(pendingValues.slice(l, (l += valuesPerBatch)));\n }\n for (; pendingRows > 0; pendingRows--) {\n insertStmt.run(pendingValues.slice(l, (l += valuesPerRow)));\n }\n const flushedValues = flushedRows * valuesPerRow;\n for (let i = 0; i < flushedValues; i++) {\n pendingValues[i] = undefined as unknown as LiteValueType;\n }\n pendingSize = 0;\n status.rows += flushedRows;\n\n const elapsed = performance.now() - start;\n flushTime += elapsed;\n lc.debug?.(\n `flushed ${flushedRows} ${tableName} rows (${flushedSize} bytes) in ${elapsed.toFixed(3)} ms`,\n );\n }\n\n lc.info?.(`Starting text copy stream of ${tableName}:`, select);\n const pgParsers = await getTypeParsers(dbClient, {returnJsonAsString: true});\n const parsers = columnSpecs.map(c => {\n const pgParse = pgParsers.getTypeParser(c.typeOID);\n return (val: string) =>\n liteValue(\n pgParse(val) as PostgresValueType,\n c.dataType,\n JSON_STRINGIFIED,\n );\n });\n\n const tsvParser = new TsvParser();\n let col = 0;\n\n await pipeline(\n await from.unsafe(`COPY (${select}) TO STDOUT`).readable(),\n new Writable({\n highWaterMark: BUFFERED_SIZE_THRESHOLD,\n\n write(\n chunk: Buffer,\n _encoding: string,\n callback: (error?: Error) => void,\n ) {\n try {\n for (const text of tsvParser.parse(chunk)) {\n pendingSize += text === null ? 4 : text.length;\n pendingValues[pendingRows * valuesPerRow + col] =\n text === null ? null : parsers[col](text);\n\n if (++col === parsers.length) {\n col = 0;\n if (\n ++pendingRows >= MAX_BUFFERED_ROWS - valuesPerRow ||\n pendingSize >= BUFFERED_SIZE_THRESHOLD\n ) {\n flush();\n }\n }\n }\n callback();\n } catch (e) {\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n\n final: (callback: (error?: Error) => void) => {\n try {\n flush();\n callback();\n } catch (e) {\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n }),\n );\n\n const elapsed = performance.now() - start;\n lc.info?.(\n `Finished copying ${status.rows} rows into ${tableName} ` +\n `(flush: ${flushTime.toFixed(3)} ms) (total: ${elapsed.toFixed(3)} ms) `,\n );\n return {rows: status.rows, flushTime};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"initial-sync.js","names":[],"sources":["../../../../../../../zero-cache/src/services/change-source/pg/initial-sync.ts"],"sourcesContent":["import {mkdtemp, rm} from 'node:fs/promises';\nimport {platform, tmpdir} from 'node:os';\nimport {join} from 'node:path';\nimport {Writable} from 'node:stream';\nimport {pipeline} from 'node:stream/promises';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {assert} from '../../../../../shared/src/asserts.ts';\nimport type {JSONObject} from '../../../../../shared/src/bigint-json.ts';\nimport {must} from '../../../../../shared/src/must.ts';\nimport {equals} from '../../../../../shared/src/set-utils.ts';\nimport type {DownloadStatus} from '../../../../../zero-events/src/status.ts';\nimport {Database} from '../../../../../zqlite/src/db.ts';\nimport {\n createLiteIndexStatement,\n createLiteTableStatement,\n} from '../../../db/create.ts';\nimport {listIndexes, listTables} from '../../../db/lite-tables.ts';\nimport * as Mode from '../../../db/mode-enum.ts';\nimport {\n BinaryCopyParser,\n hasBinaryDecoder,\n makeBinaryDecoder,\n textCastDecoder,\n} from '../../../db/pg-copy-binary.ts';\nimport {TsvParser} from '../../../db/pg-copy.ts';\nimport {\n mapPostgresToLite,\n mapPostgresToLiteIndex,\n} from '../../../db/pg-to-lite.ts';\nimport {getTypeParsers} from '../../../db/pg-type-parser.ts';\nimport {runTx} from '../../../db/run-transaction.ts';\nimport type {IndexSpec, PublishedTableSpec} from '../../../db/specs.ts';\nimport {importSnapshot, TransactionPool} from '../../../db/transaction-pool.ts';\nimport {\n getOrCreateCounter,\n getOrCreateHistogram,\n} from '../../../observability/metrics.ts';\nimport {\n JSON_STRINGIFIED,\n liteValue,\n type LiteValueType,\n} from '../../../types/lite.ts';\nimport {liteTableName} from '../../../types/names.ts';\nimport {PG_15, PG_17} from '../../../types/pg-versions.ts';\nimport {\n connectPgClient,\n pgClient,\n type PostgresDB,\n type PostgresTransaction,\n type PostgresValueType,\n} from '../../../types/pg.ts';\nimport {CpuProfiler} from '../../../types/profiler.ts';\nimport type {ShardConfig} from '../../../types/shards.ts';\nimport {ALLOWED_APP_ID_CHARACTERS} from '../../../types/shards.ts';\nimport {id} from '../../../types/sql.ts';\nimport {ReplicationStatusPublisher} from '../../replicator/replication-status.ts';\nimport {ColumnMetadataStore} from '../../replicator/schema/column-metadata.ts';\nimport {initReplicationState} from '../../replicator/schema/replication-state.ts';\nimport {toStateVersionString} from './lsn.ts';\nimport {createReplicaAndSlot} from './replication-slots.ts';\nimport {ensureShardSchema} from './schema/init.ts';\nimport {getPublicationInfo} from './schema/published.ts';\nimport {\n dropShard,\n getInternalShardConfig,\n initReplica,\n validatePublications,\n} from './schema/shard.ts';\n\nexport type InitialSyncOptions = {\n tableCopyWorkers: number;\n profileCopy?: boolean | undefined;\n textCopy?: boolean | undefined;\n replicationSlotFailover?: boolean | undefined;\n /**\n * When set, run initial sync in \"shadow\" mode for verification: skip all\n * upstream mutations (no replication slot, no addReplica, no dropShard, no\n * slot drop on failure), suppress status events, and optionally sample\n * rows from each table via TABLESAMPLE BERNOULLI + LIMIT. The caller is\n * responsible for providing (and discarding) a throwaway SQLite `tx`.\n */\n shadow?:\n | {\n /** 0 < rate <= 1. When 1, no TABLESAMPLE clause is added. */\n sampleRate: number;\n /**\n * LIMIT N cap appended after TABLESAMPLE. Required: shadow sync is\n * for verification only, so every run must commit to a row budget.\n */\n maxRowsPerTable: number;\n }\n | undefined;\n};\n\n/** Server context to store with the initial sync metadata for debugging. */\nexport type ServerContext = JSONObject;\n\nexport async function initialSync(\n lc: LogContext,\n shard: ShardConfig,\n tx: Database,\n upstreamURI: string,\n syncOptions: InitialSyncOptions,\n context: ServerContext,\n) {\n if (!ALLOWED_APP_ID_CHARACTERS.test(shard.appID)) {\n throw new Error(\n 'The App ID may only consist of lower-case letters, numbers, and the underscore character',\n );\n }\n const {\n tableCopyWorkers,\n profileCopy,\n textCopy = false,\n replicationSlotFailover = false,\n shadow,\n } = syncOptions;\n const syncMode: InitialSyncMode = shadow ? 'shadow' : 'initial';\n const copyFormat: CopyFormat = textCopy ? 'text' : 'binary';\n const start = performance.now();\n let sql: PostgresDB | undefined;\n let replicationSession: PostgresDB | undefined;\n const replicaID = Date.now().toString();\n let slotName: string | undefined; // undefined === shadow\n const statusPublisher = ReplicationStatusPublisher.forRunningTransaction(\n tx,\n shadow ? async () => {} : undefined,\n ).publish(lc, 'Initializing');\n let releaseShadowSnapshot: (() => Promise<void>) | undefined;\n try {\n const copyProfiler = profileCopy ? await CpuProfiler.connect() : null;\n sql = await connectPgClient(lc, upstreamURI, 'initial-sync');\n // Replication session is only needed to create a replication slot in the\n // real path. In shadow mode we export a snapshot on a normal connection\n // instead, so no replication session is opened.\n replicationSession = shadow\n ? undefined\n : pgClient(lc, upstreamURI, 'initial-sync-replication-session', {\n ['fetch_types']: false, // Necessary for the streaming protocol\n connection: {replication: 'database'}, // https://www.postgresql.org/docs/current/protocol-replication.html\n });\n\n const pgVersion = await checkUpstreamConfig(sql);\n\n // In shadow mode we assume the shard is already initialized and just\n // read back the existing publications. `ensurePublishedTables` would\n // otherwise run DDL and potentially call `dropShard`, which must never\n // happen during a shadow run.\n const {publications} = shadow\n ? await getInternalShardConfig(sql, shard)\n : await ensurePublishedTables(lc, sql, shard);\n lc.info?.(`Upstream is setup with publications [${publications}]`);\n\n const {database, host} = sql.options;\n lc.info?.(\n shadow\n ? `acquiring exported snapshot on ${database}@${host} (shadow mode)`\n : `opening replication session to ${database}@${host}`,\n );\n\n let snapshot: string;\n let lsn: string;\n\n if (shadow) {\n const acquired = await acquireExportedSnapshotForShadowSync(\n lc,\n upstreamURI,\n );\n snapshot = acquired.snapshot;\n lsn = acquired.lsn;\n releaseShadowSnapshot = acquired.release;\n } else {\n const slot = await createReplicaAndSlot(\n lc,\n sql,\n must(replicationSession),\n shard,\n replicaID,\n replicationSlotFailover && pgVersion >= PG_17,\n );\n snapshot = slot.snapshot_name;\n lsn = slot.consistent_point;\n slotName = slot.slot_name;\n }\n\n const initialVersion = toStateVersionString(lsn);\n\n initReplicationState(tx, publications, initialVersion, context);\n\n // Run up to MAX_WORKERS to copy of tables at the replication slot's snapshot.\n // Retrieve the published schema at the consistent_point.\n const published = await runTx(\n sql,\n async tx => {\n await tx.unsafe(/* sql*/ `SET TRANSACTION SNAPSHOT '${snapshot}'`);\n return getPublicationInfo(tx, publications);\n },\n {mode: Mode.READONLY},\n );\n // Note: If this throws, initial-sync is aborted.\n validatePublications(lc, published);\n\n // Now that tables have been validated, kick off the copiers.\n const {tables, indexes} = published;\n const numTables = tables.length;\n if (platform() === 'win32' && tableCopyWorkers < numTables) {\n lc.warn?.(\n `Increasing the number of copy workers from ${tableCopyWorkers} to ` +\n `${numTables} to work around a Node/Postgres connection bug`,\n );\n }\n const numWorkers =\n platform() === 'win32'\n ? numTables\n : Math.min(tableCopyWorkers, numTables);\n\n const copyPool = await connectPgClient(\n lc,\n upstreamURI,\n 'initial-sync-copy-worker',\n {\n max: numWorkers,\n ['max_lifetime']: 120 * 60, // set a long (2h) limit for COPY streaming\n },\n );\n const copiers = startTableCopyWorkers(\n lc,\n copyPool,\n snapshot,\n numWorkers,\n numTables,\n );\n try {\n createLiteTables(tx, tables, initialVersion);\n const sampleRate = shadow?.sampleRate;\n const maxRowsPerTable = shadow?.maxRowsPerTable;\n const downloads = await Promise.all(\n tables.map(spec =>\n copiers.processReadTask((db, lc) =>\n getInitialDownloadState(lc, db, spec, shadow !== undefined),\n ),\n ),\n );\n statusPublisher.publish(\n lc,\n 'Initializing',\n `Copying ${numTables} upstream tables at version ${initialVersion}`,\n 5000,\n () => ({downloadStatus: downloads.map(({status}) => status)}),\n );\n\n void copyProfiler?.start();\n const copyStart = performance.now();\n const copyResults = await Promise.all(\n downloads.map(table =>\n copiers.processReadTask((db, lc) =>\n copy(\n lc,\n table,\n copyPool,\n db,\n tx,\n textCopy,\n syncMode,\n sampleRate,\n maxRowsPerTable,\n ),\n ),\n ),\n );\n const copyElapsed = performance.now() - copyStart;\n void copyProfiler?.stopAndDispose(lc, 'initial-copy');\n copiers.setDone();\n\n const copySummary = initialSyncCopySummary(copyResults, copyElapsed);\n\n statusPublisher.publish(\n lc,\n 'Indexing',\n `Creating ${indexes.length} indexes`,\n 5000,\n );\n const indexStart = performance.now();\n createLiteIndices(lc, tx, indexes);\n const index = performance.now() - indexStart;\n lc.info?.(`Created indexes (${index.toFixed(3)} ms)`);\n\n if (slotName && replicaID) {\n await initReplica(sql, shard, replicaID, published, context);\n } else {\n assert(shadow, 'expected to be in shadow sync if there is no slotName');\n const rowsByTable = new Map<string, number>();\n for (let i = 0; i < downloads.length; i++) {\n rowsByTable.set(downloads[i].status.table, copyResults[i].rows);\n }\n verifyShadowReplica(lc, tx, published, rowsByTable);\n }\n\n const elapsed = performance.now() - start;\n const copyOtherMs = Math.max(0, elapsed - copySummary.flushMs - index);\n recordInitialSyncRunMetrics(\n {\n durationMs: elapsed,\n rows: copySummary.rows,\n copyBytes: copySummary.copyBytes,\n copyMs: copySummary.copyMs,\n copyOtherMs,\n flushMs: copySummary.flushMs,\n indexMs: index,\n },\n {\n result: 'success',\n syncMode,\n copyFormat,\n },\n );\n lc.info?.(\n `Synced ${copySummary.rows.toLocaleString()} rows of ${numTables} tables in ${publications} up to ${lsn} ` +\n `(flush: ${copySummary.flushMs.toFixed(3)}, index: ${index.toFixed(3)}, total: ${elapsed.toFixed(3)} ms)`,\n {\n syncMode,\n copyFormat,\n publications,\n lsn,\n ...copySummary,\n indexes: indexes.length,\n indexMs: index,\n copyOtherMs,\n totalMs: elapsed,\n },\n );\n } finally {\n // All meaningful errors are handled at the processReadTask() call site.\n void copyPool.end().catch(e => lc.warn?.(`Error closing copyPool`, e));\n }\n } catch (e) {\n recordInitialSyncRunMetrics(\n {durationMs: performance.now() - start},\n {\n result: 'error',\n syncMode,\n copyFormat,\n },\n );\n if (slotName && sql) {\n // If initial-sync did not succeed, make a best effort to drop the\n // orphaned replication slot to avoid running out of slots in\n // pathological cases that result in repeated failures.\n lc.warn?.(`dropping replication slot ${slotName}`, e);\n await sql`\n SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots\n WHERE slot_name = ${slotName};\n `.catch(e => lc.warn?.(`Unable to drop replication slot ${slotName}`, e));\n }\n await statusPublisher.publishAndThrowError(lc, 'Initializing', e);\n } finally {\n statusPublisher.stop();\n if (releaseShadowSnapshot) {\n await releaseShadowSnapshot().catch(e =>\n lc.warn?.(`Error releasing shadow snapshot`, e),\n );\n }\n if (replicationSession) {\n await replicationSession.end();\n }\n if (sql) {\n await sql.end();\n }\n }\n}\n\nexport type ShadowSyncOptions = {\n sampleRate: number;\n maxRowsPerTable: number;\n /**\n * Parent directory for the throwaway SQLite replica. Defaults to the OS\n * tmpdir. Primarily for tests that need to isolate the scratch directory.\n */\n parentDir?: string | undefined;\n};\n\n/**\n * Exercises the initial-sync code path against a sample of rows from every\n * published table, writing into a throwaway SQLite database that is deleted\n * when the run ends. Produces zero upstream mutations: no replication slot,\n * no `addReplica`, no `dropShard`, no status events.\n *\n * Intended to be invoked periodically so that if a customer ever needs a\n * full reset, we have recent confidence that `initialSync` still works.\n * The shard must already be initialized upstream.\n */\nexport async function shadowInitialSync(\n lc: LogContext,\n shard: ShardConfig,\n upstreamURI: string,\n shadow: ShadowSyncOptions,\n context: ServerContext,\n syncOptions?: Pick<InitialSyncOptions, 'textCopy'>,\n): Promise<void> {\n const dir = await mkdtemp(\n join(shadow.parentDir ?? tmpdir(), 'zero-shadow-sync-'),\n );\n const dbPath = join(dir, 'shadow-replica.db');\n const db = new Database(lc, dbPath);\n try {\n await initialSync(\n lc,\n shard,\n db,\n upstreamURI,\n {\n // Shadow sync copies small samples, so one worker is plenty —\n // no reason to burn additional upstream connections.\n tableCopyWorkers: 1,\n textCopy: syncOptions?.textCopy,\n shadow,\n },\n context,\n );\n } finally {\n try {\n db.close();\n } catch (e) {\n lc.warn?.(`Error closing shadow replica db`, e);\n }\n await rm(dir, {recursive: true, force: true}).catch(e =>\n lc.warn?.(`Error cleaning up shadow replica dir ${dir}`, e),\n );\n }\n}\n\nasync function checkUpstreamConfig(sql: PostgresDB) {\n const {walLevel, version} = (\n await sql<{walLevel: string; version: number}[]>`\n SELECT current_setting('wal_level') as \"walLevel\", \n current_setting('server_version_num') as \"version\";\n `\n )[0];\n\n if (walLevel !== 'logical') {\n throw new Error(\n `Postgres must be configured with \"wal_level = logical\" (currently: \"${walLevel})`,\n );\n }\n if (version < PG_15) {\n throw new Error(\n `Must be running Postgres 15 or higher (currently: \"${version}\")`,\n );\n }\n return version;\n}\n\nasync function ensurePublishedTables(\n lc: LogContext,\n sql: PostgresDB,\n shard: ShardConfig,\n validate = true,\n): Promise<{publications: string[]}> {\n const {database, host} = sql.options;\n lc.info?.(`Ensuring upstream PUBLICATION on ${database}@${host}`);\n\n await ensureShardSchema(lc, sql, shard);\n const {publications} = await getInternalShardConfig(sql, shard);\n\n if (validate) {\n let valid = false;\n const nonInternalPublications = publications.filter(\n p => !p.startsWith('_'),\n );\n const exists = await sql`\n SELECT pubname FROM pg_publication WHERE pubname IN ${sql(publications)}\n `.values();\n if (exists.length !== publications.length) {\n lc.warn?.(\n `some configured publications [${publications}] are missing: ` +\n `[${exists.flat()}]. resyncing`,\n );\n } else if (\n !equals(new Set(shard.publications), new Set(nonInternalPublications))\n ) {\n lc.warn?.(\n `requested publications [${shard.publications}] differ from previous` +\n `publications [${nonInternalPublications}]. resyncing`,\n );\n } else {\n valid = true;\n }\n if (!valid) {\n await sql.unsafe(dropShard(shard.appID, shard.shardNum));\n return ensurePublishedTables(lc, sql, shard, false);\n }\n }\n return {publications};\n}\n\nfunction startTableCopyWorkers(\n lc: LogContext,\n db: PostgresDB,\n snapshot: string,\n numWorkers: number,\n numTables: number,\n): TransactionPool {\n const {init} = importSnapshot(snapshot);\n const tableCopiers = new TransactionPool(lc, {\n mode: Mode.READONLY,\n init,\n initialWorkers: numWorkers,\n });\n tableCopiers.run(db);\n\n lc.info?.(`Started ${numWorkers} workers to copy ${numTables} tables`);\n\n if (parseInt(process.versions.node) < 22) {\n lc.warn?.(\n `\\n\\n\\n` +\n `Older versions of Node have a bug that results in an unresponsive\\n` +\n `Postgres connection after running certain combinations of COPY commands.\\n` +\n `If initial sync hangs, run zero-cache with Node v22+. This has the additional\\n` +\n `benefit of being consistent with the Node version run in the production container image.` +\n `\\n\\n\\n`,\n );\n }\n return tableCopiers;\n}\n\n/**\n * Shadow-mode alternative to `createReplicationSlot`: opens a dedicated\n * READ ONLY REPEATABLE READ transaction on a normal connection, exports the\n * snapshot and captures the current WAL LSN, then holds the transaction\n * open until `release()` is called. The held transaction keeps the snapshot\n * importable by the table-copy workers for the duration of the COPY phase.\n *\n * Idle-in-transaction timeout is disabled locally so the exporter doesn't\n * get killed while workers are still importing.\n */\nasync function acquireExportedSnapshotForShadowSync(\n lc: LogContext,\n upstreamURI: string,\n): Promise<{\n snapshot: string;\n lsn: string;\n release: () => Promise<void>;\n}> {\n const holder = await connectPgClient(\n lc,\n upstreamURI,\n 'shadow-initial-sync-snapshot',\n {\n max: 1,\n },\n );\n const ready = resolver<{snapshot: string; lsn: string}>();\n const release = resolver<void>();\n const held = holder\n .begin(Mode.READONLY, async tx => {\n await tx`SET LOCAL idle_in_transaction_session_timeout = 0`.execute();\n const [row] = await tx<{snapshot: string; lsn: string}[]>`\n SELECT pg_export_snapshot() AS snapshot,\n pg_current_wal_lsn()::text AS lsn`;\n ready.resolve(row);\n await release.promise;\n })\n .catch(e => ready.reject(e));\n\n let snapshot: string;\n let lsn: string;\n try {\n ({snapshot, lsn} = await ready.promise);\n } catch (e) {\n await holder\n .end()\n .catch(err =>\n lc.warn?.(`Error ending shadow snapshot holder after failure`, err),\n );\n throw e;\n }\n lc.info?.(\n `Exported snapshot ${snapshot} at LSN ${lsn} (shadow initial sync)`,\n );\n return {\n snapshot,\n lsn,\n release: async () => {\n release.resolve();\n try {\n await held;\n } catch (e) {\n lc.warn?.(`snapshot holder transaction ended with error`, e);\n }\n await holder.end();\n },\n };\n}\n\nfunction createLiteTables(\n tx: Database,\n tables: PublishedTableSpec[],\n initialVersion: string,\n) {\n // TODO: Figure out how to reuse the ChangeProcessor here to avoid\n // duplicating the ColumnMetadata logic.\n const columnMetadata = must(ColumnMetadataStore.getInstance(tx));\n for (const t of tables) {\n tx.exec(createLiteTableStatement(mapPostgresToLite(t, initialVersion)));\n const tableName = liteTableName(t);\n for (const [colName, colSpec] of Object.entries(t.columns)) {\n columnMetadata.insert(tableName, colName, colSpec);\n }\n }\n}\n\nfunction createLiteIndices(lc: LogContext, tx: Database, indices: IndexSpec[]) {\n for (const [i, index] of indices.entries()) {\n const stmt = createLiteIndexStatement(mapPostgresToLiteIndex(index));\n lc.info?.(`Creating index ${i + 1}/${indices.length}: ${stmt}`);\n const start = performance.now();\n tx.exec(stmt);\n lc.info?.(\n `Created index ${i + 1}/${indices.length} ` +\n `(${(performance.now() - start).toFixed(3)} ms): ${stmt}`,\n );\n }\n}\n\n/**\n * Runs structural assertions over a just-synced replica and throws if any\n * fail. Only called in shadow mode — a successful return means the replica\n * is schema-complete, row-count consistent, and its column metadata is in\n * sync with its lite schema.\n *\n * Note: this intentionally does NOT verify ZQL-queryability. Tables that\n * `computeZqlSpecs` drops (no PK / no all-NOT-NULL unique index, unsupported\n * column types, etc.) are silently skipped in production too — there's\n * nothing shadow-specific about them, so failing here would diverge from\n * prod over an upstream-schema condition prod accepts.\n *\n * Exported for testing.\n */\nexport function verifyShadowReplica(\n lc: LogContext,\n db: Database,\n published: {tables: PublishedTableSpec[]; indexes: IndexSpec[]},\n rowsByTable: ReadonlyMap<string, number>,\n): void {\n const issues: string[] = [];\n let columnsChecked = 0;\n let rowsChecked = 0;\n\n // 1. Schema completeness: every published table exists in the replica\n // with at least the expected column set.\n const liteTables = listTables(db);\n const liteTableByName = new Map(liteTables.map(t => [t.name, t]));\n for (const pt of published.tables) {\n const name = liteTableName(pt);\n const lite = liteTableByName.get(name);\n if (!lite) {\n issues.push(`missing table in replica: ${name}`);\n continue;\n }\n for (const col of Object.keys(pt.columns)) {\n columnsChecked++;\n if (!(col in lite.columns)) {\n issues.push(`column missing in replica table ${name}: ${col}`);\n }\n }\n }\n\n // Every published index exists in the replica.\n const liteIndexNames = new Set(listIndexes(db).map(i => i.name));\n for (const ix of published.indexes) {\n const mapped = mapPostgresToLiteIndex(ix);\n if (!liteIndexNames.has(mapped.name)) {\n issues.push(\n `missing index in replica: ${mapped.name} on ${mapped.tableName}`,\n );\n }\n }\n\n // 2. Row counts: SQLite COUNT(*) matches the in-memory copy counter.\n for (const [table, expected] of rowsByTable) {\n try {\n const [row] = db\n .prepare(`SELECT COUNT(*) as count FROM \"${table}\"`)\n .all<{count: number}>();\n if (row.count !== expected) {\n issues.push(\n `row count mismatch for table ${table}: ` +\n `copy counter reported ${expected}, replica has ${row.count}`,\n );\n } else {\n rowsChecked += row.count;\n }\n } catch (e) {\n issues.push(`could not count rows in table ${table}: ${String(e)}`);\n }\n }\n\n // 3. Column metadata: every published column has a _zero.column_metadata row.\n const meta = must(ColumnMetadataStore.getInstance(db));\n for (const pt of published.tables) {\n const name = liteTableName(pt);\n const rows = meta.getTable(name);\n for (const col of Object.keys(pt.columns)) {\n if (!rows.has(col)) {\n issues.push(`missing column_metadata row for ${name}.${col}`);\n }\n }\n }\n\n if (issues.length) {\n throw new Error(\n `Shadow replica verification failed (${issues.length} issue(s)):\\n` +\n issues.map(i => ` - ${i}`).join('\\n'),\n );\n }\n\n lc.info?.(\n `Shadow replica verification passed: ` +\n `${published.tables.length} tables, ` +\n `${published.indexes.length} indexes, ` +\n `${columnsChecked} columns, ` +\n `${rowsChecked.toLocaleString()} rows`,\n );\n}\n\n// Verified empirically that batches of 50 seem to be the sweet spot,\n// similar to the report in https://sqlite.org/forum/forumpost/8878a512d3652655\n//\n// Exported for testing.\nexport const INSERT_BATCH_SIZE = 50;\n\nconst MB = 1024 * 1024;\nconst MAX_BUFFERED_ROWS = 10_000;\nconst BUFFERED_SIZE_THRESHOLD = 8 * MB;\n\nexport type DownloadStatements = {\n select: string;\n getTotalRows: string;\n getTotalBytes: string;\n};\n\n/**\n * Produces ` TABLESAMPLE BERNOULLI(n)` when `sampleRate` is < 1, else `''`.\n * Row-level Bernoulli sampling is used (rather than SYSTEM) because it\n * produces a more uniform sample and, unlike SYSTEM, still returns rows\n * for small tables at low rates.\n */\nfunction tableSampleClause(sampleRate: number | undefined): string {\n if (sampleRate === undefined || sampleRate >= 1) {\n return '';\n }\n // Round away float noise (e.g. 0.3 * 100 = 30.000000000000004) while still\n // preserving sub-integer rates like 0.001 (= 0.1%).\n const pct = parseFloat((sampleRate * 100).toFixed(6));\n return /*sql*/ ` TABLESAMPLE BERNOULLI(${pct})`;\n}\n\nfunction limitClause(maxRowsPerTable: number | undefined): string {\n return maxRowsPerTable !== undefined\n ? /*sql*/ ` LIMIT ${maxRowsPerTable}`\n : '';\n}\n\n/**\n * Returns the SELECT column expressions for binary COPY, casting columns\n * without a known binary decoder to `::text`.\n */\nexport function makeBinarySelectExprs(\n table: PublishedTableSpec,\n cols: string[],\n): string[] {\n return cols.map(col => {\n const spec = table.columns[col];\n return hasBinaryDecoder(spec) ? id(col) : `${id(col)}::text`;\n });\n}\n\nexport function makeDownloadStatements(\n table: PublishedTableSpec,\n cols: string[],\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n selectExprs?: string[] | undefined,\n): DownloadStatements {\n const filterConditions = Object.values(table.publications)\n .map(({rowFilter}) => rowFilter)\n .filter(f => !!f); // remove nulls\n const where =\n filterConditions.length === 0\n ? ''\n : /*sql*/ `WHERE ${filterConditions.join(' OR ')}`;\n const sample = tableSampleClause(sampleRate);\n const limit = limitClause(maxRowsPerTable);\n const fromTable = /*sql*/ `FROM ${id(table.schema)}.${id(table.name)}${sample} ${where}`;\n const select = /*sql*/ `SELECT ${(selectExprs ?? cols.map(id)).join(',')} ${fromTable}${limit}`;\n if (limit) {\n // With LIMIT, wrap counts/sums in a subquery so they reflect the\n // capped rowset rather than the full (sampled) table.\n const bytesExpr = cols\n .map(col => `COALESCE(pg_column_size(${id(col)}), 0)`)\n .join(' + ');\n return {\n select,\n getTotalRows: /*sql*/ `SELECT COUNT(*)::bigint AS \"totalRows\" FROM (SELECT 1 AS _ ${fromTable}${limit}) s`,\n getTotalBytes: /*sql*/ `SELECT COALESCE(SUM(b), 0)::bigint AS \"totalBytes\" FROM (SELECT (${bytesExpr}) AS b ${fromTable}${limit}) s`,\n };\n }\n const totalBytes = `(${cols.map(col => `SUM(COALESCE(pg_column_size(${id(col)}), 0))`).join(' + ')})`;\n return {\n select,\n getTotalRows: /*sql*/ `SELECT COUNT(*) AS \"totalRows\" ${fromTable}`,\n getTotalBytes: /*sql*/ `SELECT ${totalBytes} AS \"totalBytes\" ${fromTable}`,\n };\n}\n\ntype DownloadState = {\n spec: PublishedTableSpec;\n status: DownloadStatus;\n};\n\ntype CopyFormat = 'binary' | 'text';\ntype InitialSyncMode = 'initial' | 'shadow';\n\ntype InitialSyncMetricAttrs = {\n syncMode: InitialSyncMode;\n copyFormat: CopyFormat;\n};\n\ntype InitialSyncRunMetricAttrs = {\n syncMode: InitialSyncMode;\n copyFormat: CopyFormat;\n result: 'success' | 'error';\n};\n\ntype CopyResult = {\n schema: string;\n table: string;\n replicaTable: string;\n syncMode: InitialSyncMode;\n copyFormat: CopyFormat;\n columnCount: number;\n rows: number;\n estimatedRows: number;\n estimatedBytes: number | undefined;\n flushMs: number;\n elapsedMs: number;\n copyBytes: number;\n};\n\nconst INITIAL_SYNC_DURATION_HISTOGRAM_BOUNDARIES_S = [\n 1, 2, 5, 10, 30, 60, 120, 300, 600, 1200, 2400, 3600, 7200,\n];\nconst SLOW_COPY_FLUSH_MS = 10_000;\n\n// change-streamer imports this module before startOtelAuto() runs, so create\n// instruments lazily to avoid binding them to OTel's no-op meter provider.\nfunction initialSyncRuns() {\n return getOrCreateCounter(\n 'replication',\n 'initial_sync_runs',\n 'Initial sync runs, labeled by result.',\n );\n}\n\nfunction initialSyncDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_duration',\n 'Wall-clock duration of an initial sync run, labeled by result.',\n );\n}\n\nfunction initialSyncCopyDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_copy_duration',\n 'Wall-clock duration of the COPY phase for a successful initial sync run.',\n );\n}\n\nfunction initialSyncCopyOtherDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_copy_other_duration',\n 'Initial sync total duration excluding SQLite flush and index time for a successful run.',\n );\n}\n\nfunction initialSyncFlushDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_flush_duration',\n 'Total SQLite flush time for a successful initial sync run.',\n );\n}\n\nfunction initialSyncIndexDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_index_duration',\n 'SQLite index creation time for a successful initial sync run.',\n );\n}\n\nfunction initialSyncRows() {\n return getOrCreateCounter(\n 'replication',\n 'initial_sync_rows',\n 'Rows copied during successful initial sync runs.',\n );\n}\n\nfunction initialSyncCopyStream() {\n return getOrCreateCounter('replication', 'initial_sync_copy_stream', {\n description:\n 'PostgreSQL COPY stream bytes processed during initial sync, including in-progress and failed runs.',\n unit: 'bytes',\n });\n}\n\nfunction initialSyncCompletedCopyStream() {\n return getOrCreateCounter(\n 'replication',\n 'initial_sync_completed_copy_stream',\n {\n description:\n 'PostgreSQL COPY stream bytes processed during successful initial sync runs.',\n unit: 'bytes',\n },\n );\n}\n\nfunction initialSyncCopyChunks() {\n return getOrCreateCounter(\n 'replication',\n 'initial_sync_copy_chunks',\n 'PostgreSQL COPY stream chunks processed during initial sync.',\n );\n}\n\nfunction initialSyncDurationHistogram(name: string, description: string) {\n return getOrCreateHistogram('replication', name, {\n description,\n unit: 's',\n bucketBoundaries: INITIAL_SYNC_DURATION_HISTOGRAM_BOUNDARIES_S,\n });\n}\n\nfunction initialSyncMetricAttrs(attrs: InitialSyncMetricAttrs) {\n return {\n sync_mode: attrs.syncMode,\n copy_format: attrs.copyFormat,\n };\n}\n\nfunction initialSyncRunMetricAttrs(attrs: InitialSyncRunMetricAttrs) {\n return {\n ...initialSyncMetricAttrs(attrs),\n result: attrs.result,\n };\n}\n\nfunction recordInitialSyncRunMetrics(\n stats: {\n durationMs: number;\n rows?: number | undefined;\n copyBytes?: number | undefined;\n copyMs?: number | undefined;\n copyOtherMs?: number | undefined;\n flushMs?: number | undefined;\n indexMs?: number | undefined;\n },\n attrs: InitialSyncRunMetricAttrs,\n) {\n const labels = initialSyncRunMetricAttrs(attrs);\n initialSyncRuns().add(1, labels);\n initialSyncDuration().recordMs(stats.durationMs, labels);\n if (attrs.result === 'success') {\n if (stats.copyMs !== undefined) {\n initialSyncCopyDuration().recordMs(stats.copyMs, labels);\n }\n if (stats.copyOtherMs !== undefined) {\n initialSyncCopyOtherDuration().recordMs(stats.copyOtherMs, labels);\n }\n if (stats.flushMs !== undefined) {\n initialSyncFlushDuration().recordMs(stats.flushMs, labels);\n }\n if (stats.indexMs !== undefined) {\n initialSyncIndexDuration().recordMs(stats.indexMs, labels);\n }\n if (stats.rows !== undefined && stats.rows > 0) {\n initialSyncRows().add(stats.rows, labels);\n }\n if (stats.copyBytes !== undefined && stats.copyBytes > 0) {\n initialSyncCompletedCopyStream().add(stats.copyBytes, labels);\n }\n }\n}\n\nfunction initialSyncCopySummary(\n results: readonly CopyResult[],\n copyMs: number,\n) {\n const totals = results.reduce(\n (acc, curr) => {\n acc.rows += curr.rows;\n acc.flushMs += curr.flushMs;\n acc.copyBytes += curr.copyBytes;\n return acc;\n },\n {\n rows: 0,\n flushMs: 0,\n copyBytes: 0,\n },\n );\n return {\n tables: results.length,\n rows: totals.rows,\n copyMs,\n flushMs: totals.flushMs,\n copyBytes: totals.copyBytes,\n };\n}\n\nfunction logSlowCopyFlush(\n lc: LogContext,\n details: {\n schema: string;\n table: string;\n replicaTable: string;\n syncMode: InitialSyncMode;\n copyFormat: CopyFormat;\n elapsedMs: number;\n flushedRows: number;\n flushedBytes: number;\n rows: number;\n copyBytes: number;\n },\n) {\n if (details.elapsedMs < SLOW_COPY_FLUSH_MS || details.flushedRows === 0) {\n return;\n }\n lc.info?.('initial-sync table copy slow flush', details);\n}\n\n// Exported for testing.\nexport async function getInitialDownloadState(\n lc: LogContext,\n sql: PostgresDB,\n spec: PublishedTableSpec,\n skipTotals: boolean,\n): Promise<DownloadState> {\n const start = performance.now();\n const table = liteTableName(spec);\n const columns = Object.keys(spec.columns);\n if (skipTotals) {\n // Shadow sync suppresses status events, so the pg_class\n // estimates would be computed and thrown away.\n return {\n spec,\n status: {table, columns, rows: 0, totalRows: 0, totalBytes: 0},\n };\n }\n // Use pg_class estimates instead of expensive COUNT(*) and\n // SUM(pg_column_size(...)) full table scans. The exact values are only\n // used for progress reporting, so estimates are sufficient.\n const qualifiedName = `${id(spec.schema)}.${id(spec.name)}`;\n const estimateResult = await sql<\n {totalRows: number; totalBytes: number}[]\n >`SELECT GREATEST(reltuples, 0)::float8 AS \"totalRows\",\n pg_table_size(oid)::float8 AS \"totalBytes\"\n FROM pg_class\n WHERE oid = ${qualifiedName}::regclass`;\n\n const {totalRows, totalBytes} = estimateResult[0] ?? {\n totalRows: 0,\n totalBytes: 0,\n };\n\n const state: DownloadState = {\n spec,\n status: {\n table,\n columns,\n rows: 0,\n totalRows,\n totalBytes,\n },\n };\n const elapsed = (performance.now() - start).toFixed(3);\n lc.info?.(`Computed initial download state for ${table} (${elapsed} ms)`, {\n state: state.status,\n });\n return state;\n}\n\nfunction copy(\n lc: LogContext,\n {spec: table, status}: DownloadState,\n dbClient: PostgresDB,\n from: PostgresTransaction,\n to: Database,\n textCopy: boolean,\n syncMode: InitialSyncMode,\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n): Promise<CopyResult> {\n if (textCopy) {\n return copyText(\n lc,\n table,\n status,\n dbClient,\n from,\n to,\n syncMode,\n sampleRate,\n maxRowsPerTable,\n );\n }\n return copyBinary(\n lc,\n table,\n status,\n from,\n to,\n syncMode,\n sampleRate,\n maxRowsPerTable,\n );\n}\n\nasync function copyBinary(\n lc: LogContext,\n table: PublishedTableSpec,\n status: DownloadStatus,\n from: PostgresTransaction,\n to: Database,\n syncMode: InitialSyncMode,\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n): Promise<CopyResult> {\n const start = performance.now();\n const copyFormat: CopyFormat = 'binary';\n const metricLabels = initialSyncMetricAttrs({syncMode, copyFormat});\n const copyStreamMetric = initialSyncCopyStream();\n const copyChunksMetric = initialSyncCopyChunks();\n let flushMs = 0;\n let copyBytes = 0;\n\n const tableName = liteTableName(table);\n const orderedColumns = Object.entries(table.columns);\n\n const columnNames = orderedColumns.map(([c]) => c);\n const columnSpecs = orderedColumns.map(([_name, spec]) => spec);\n const insertColumnList = columnNames.map(c => id(c)).join(',');\n\n const valuesSql =\n columnNames.length > 0 ? `(${'?,'.repeat(columnNames.length - 1)}?)` : '()';\n const insertSql = /*sql*/ `\n INSERT INTO \"${tableName}\" (${insertColumnList}) VALUES ${valuesSql}`;\n const insertStmt = to.prepare(insertSql);\n const insertBatchStmt = to.prepare(\n insertSql + `,${valuesSql}`.repeat(INSERT_BATCH_SIZE - 1),\n );\n\n // Build SELECT with ::text casts for columns without a known binary decoder.\n const select = makeDownloadStatements(\n table,\n columnNames,\n sampleRate,\n maxRowsPerTable,\n makeBinarySelectExprs(table, columnNames),\n ).select;\n\n const decoders = orderedColumns.map(([, spec]) =>\n hasBinaryDecoder(spec) ? makeBinaryDecoder(spec) : textCastDecoder,\n );\n\n const valuesPerRow = columnSpecs.length;\n const valuesPerBatch = valuesPerRow * INSERT_BATCH_SIZE;\n\n const pendingValues: LiteValueType[] = Array.from({\n length: MAX_BUFFERED_ROWS * valuesPerRow,\n });\n let pendingRows = 0;\n let pendingSize = 0;\n\n function flush() {\n const flushStart = performance.now();\n const flushedRows = pendingRows;\n const flushedSize = pendingSize;\n\n let l = 0;\n for (; pendingRows > INSERT_BATCH_SIZE; pendingRows -= INSERT_BATCH_SIZE) {\n insertBatchStmt.run(pendingValues.slice(l, (l += valuesPerBatch)));\n }\n for (; pendingRows > 0; pendingRows--) {\n insertStmt.run(pendingValues.slice(l, (l += valuesPerRow)));\n }\n const flushedValues = flushedRows * valuesPerRow;\n for (let i = 0; i < flushedValues; i++) {\n pendingValues[i] = undefined as unknown as LiteValueType;\n }\n pendingSize = 0;\n status.rows += flushedRows;\n\n const elapsed = performance.now() - flushStart;\n flushMs += elapsed;\n lc.debug?.(\n `flushed ${flushedRows} ${tableName} rows (${flushedSize} bytes) in ${elapsed.toFixed(3)} ms`,\n );\n logSlowCopyFlush(lc, {\n schema: table.schema,\n table: table.name,\n replicaTable: tableName,\n syncMode,\n copyFormat,\n elapsedMs: elapsed,\n flushedRows,\n flushedBytes: flushedSize,\n rows: status.rows,\n copyBytes,\n });\n }\n\n const binaryParser = new BinaryCopyParser();\n let col = 0;\n\n lc.info?.(`Starting binary copy stream of ${tableName}:`, select);\n\n await pipeline(\n await from\n .unsafe(`COPY (${select}) TO STDOUT WITH (FORMAT binary)`)\n .readable(),\n new Writable({\n highWaterMark: BUFFERED_SIZE_THRESHOLD,\n\n write(\n chunk: Buffer,\n _encoding: string,\n callback: (error?: Error) => void,\n ) {\n try {\n copyBytes += chunk.length;\n copyStreamMetric.add(chunk.length, metricLabels);\n copyChunksMetric.add(1, metricLabels);\n for (const fieldBuf of binaryParser.parse(chunk)) {\n const fieldSize = fieldBuf === null ? 4 : fieldBuf.length;\n pendingSize += fieldSize;\n pendingValues[pendingRows * valuesPerRow + col] =\n fieldBuf === null ? null : decoders[col](fieldBuf);\n\n if (++col === decoders.length) {\n col = 0;\n if (\n ++pendingRows >= MAX_BUFFERED_ROWS - valuesPerRow ||\n pendingSize >= BUFFERED_SIZE_THRESHOLD\n ) {\n flush();\n }\n }\n }\n callback();\n } catch (e) {\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n\n final: (callback: (error?: Error) => void) => {\n try {\n flush();\n callback();\n } catch (e) {\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n }),\n );\n\n const elapsed = performance.now() - start;\n const result = {\n schema: table.schema,\n table: table.name,\n replicaTable: tableName,\n syncMode,\n copyFormat,\n columnCount: columnNames.length,\n rows: status.rows,\n estimatedRows: status.totalRows,\n estimatedBytes: status.totalBytes,\n flushMs,\n elapsedMs: elapsed,\n copyBytes,\n } satisfies CopyResult;\n\n lc.info?.(\n `Finished copying ${status.rows} rows into ${tableName} ` +\n `(flush: ${flushMs.toFixed(3)} ms) (total: ${elapsed.toFixed(3)} ms) `,\n result,\n );\n return result;\n}\n\nasync function copyText(\n lc: LogContext,\n table: PublishedTableSpec,\n status: DownloadStatus,\n dbClient: PostgresDB,\n from: PostgresTransaction,\n to: Database,\n syncMode: InitialSyncMode,\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n): Promise<CopyResult> {\n const start = performance.now();\n const copyFormat: CopyFormat = 'text';\n const metricLabels = initialSyncMetricAttrs({syncMode, copyFormat});\n const copyStreamMetric = initialSyncCopyStream();\n const copyChunksMetric = initialSyncCopyChunks();\n let flushMs = 0;\n let copyBytes = 0;\n\n const tableName = liteTableName(table);\n const orderedColumns = Object.entries(table.columns);\n\n const columnNames = orderedColumns.map(([c]) => c);\n const columnSpecs = orderedColumns.map(([_name, spec]) => spec);\n const insertColumnList = columnNames.map(c => id(c)).join(',');\n\n const valuesSql =\n columnNames.length > 0 ? `(${'?,'.repeat(columnNames.length - 1)}?)` : '()';\n const insertSql = /*sql*/ `\n INSERT INTO \"${tableName}\" (${insertColumnList}) VALUES ${valuesSql}`;\n const insertStmt = to.prepare(insertSql);\n const insertBatchStmt = to.prepare(\n insertSql + `,${valuesSql}`.repeat(INSERT_BATCH_SIZE - 1),\n );\n\n const {select} = makeDownloadStatements(\n table,\n columnNames,\n sampleRate,\n maxRowsPerTable,\n );\n const valuesPerRow = columnSpecs.length;\n const valuesPerBatch = valuesPerRow * INSERT_BATCH_SIZE;\n\n const pendingValues: LiteValueType[] = Array.from({\n length: MAX_BUFFERED_ROWS * valuesPerRow,\n });\n let pendingRows = 0;\n let pendingSize = 0;\n\n function flush() {\n const flushStart = performance.now();\n const flushedRows = pendingRows;\n const flushedSize = pendingSize;\n\n let l = 0;\n for (; pendingRows > INSERT_BATCH_SIZE; pendingRows -= INSERT_BATCH_SIZE) {\n insertBatchStmt.run(pendingValues.slice(l, (l += valuesPerBatch)));\n }\n for (; pendingRows > 0; pendingRows--) {\n insertStmt.run(pendingValues.slice(l, (l += valuesPerRow)));\n }\n const flushedValues = flushedRows * valuesPerRow;\n for (let i = 0; i < flushedValues; i++) {\n pendingValues[i] = undefined as unknown as LiteValueType;\n }\n pendingSize = 0;\n status.rows += flushedRows;\n\n const elapsed = performance.now() - flushStart;\n flushMs += elapsed;\n lc.debug?.(\n `flushed ${flushedRows} ${tableName} rows (${flushedSize} bytes) in ${elapsed.toFixed(3)} ms`,\n );\n logSlowCopyFlush(lc, {\n schema: table.schema,\n table: table.name,\n replicaTable: tableName,\n syncMode,\n copyFormat,\n elapsedMs: elapsed,\n flushedRows,\n flushedBytes: flushedSize,\n rows: status.rows,\n copyBytes,\n });\n }\n\n lc.info?.(`Starting text copy stream of ${tableName}:`, select);\n const pgParsers = await getTypeParsers(dbClient, {returnJsonAsString: true});\n const parsers = columnSpecs.map(c => {\n const pgParse = pgParsers.getTypeParser(c.typeOID);\n return (val: string) =>\n liteValue(\n pgParse(val) as PostgresValueType,\n c.dataType,\n JSON_STRINGIFIED,\n );\n });\n\n const tsvParser = new TsvParser();\n let col = 0;\n\n await pipeline(\n await from.unsafe(`COPY (${select}) TO STDOUT`).readable(),\n new Writable({\n highWaterMark: BUFFERED_SIZE_THRESHOLD,\n\n write(\n chunk: Buffer,\n _encoding: string,\n callback: (error?: Error) => void,\n ) {\n try {\n copyBytes += chunk.length;\n copyStreamMetric.add(chunk.length, metricLabels);\n copyChunksMetric.add(1, metricLabels);\n for (const text of tsvParser.parse(chunk)) {\n const fieldSize = text === null ? 4 : text.length;\n pendingSize += fieldSize;\n pendingValues[pendingRows * valuesPerRow + col] =\n text === null ? null : parsers[col](text);\n\n if (++col === parsers.length) {\n col = 0;\n if (\n ++pendingRows >= MAX_BUFFERED_ROWS - valuesPerRow ||\n pendingSize >= BUFFERED_SIZE_THRESHOLD\n ) {\n flush();\n }\n }\n }\n callback();\n } catch (e) {\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n\n final: (callback: (error?: Error) => void) => {\n try {\n flush();\n callback();\n } catch (e) {\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n }),\n );\n\n const elapsed = performance.now() - start;\n const result = {\n schema: table.schema,\n table: table.name,\n replicaTable: tableName,\n syncMode,\n copyFormat,\n columnCount: columnNames.length,\n rows: status.rows,\n estimatedRows: status.totalRows,\n estimatedBytes: status.totalBytes,\n flushMs,\n elapsedMs: elapsed,\n copyBytes,\n } satisfies CopyResult;\n lc.info?.(\n `Finished copying ${status.rows} rows into ${tableName} ` +\n `(flush: ${flushMs.toFixed(3)} ms) (total: ${elapsed.toFixed(3)} ms) `,\n result,\n );\n return result;\n}\n"],"mappings":"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"}
|