@rocicorp/zero 1.8.0-canary.2 → 1.8.0-canary.4
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/z2s/src/compiler.d.ts +2 -1
- package/out/z2s/src/compiler.d.ts.map +1 -1
- package/out/z2s/src/compiler.js +41 -4
- package/out/z2s/src/compiler.js.map +1 -1
- 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-cache/src/services/change-streamer/litestream3-backup-monitor.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/litestream3-backup-monitor.js +22 -3
- package/out/zero-cache/src/services/change-streamer/litestream3-backup-monitor.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.js +21 -2
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +166 -46
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/metrics.d.ts +30 -0
- package/out/zero-cache/src/services/litestream/metrics.d.ts.map +1 -0
- package/out/zero-cache/src/services/litestream/metrics.js +112 -0
- package/out/zero-cache/src/services/litestream/metrics.js.map +1 -0
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zqlite/src/query-builder.js +5 -3
- package/out/zqlite/src/query-builder.js.map +1 -1
- package/package.json +1 -1
|
@@ -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 };
|