UNPKG

@clickup/pg-microsharding

Version:
125 lines 6.15 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.waitUntilBackfillCompletes = waitUntilBackfillCompletes; const partition_1 = __importDefault(require("lodash/partition")); const sum_1 = __importDefault(require("lodash/sum")); const getPhysicalReplicas_1 = require("./getPhysicalReplicas"); const getRowCount_1 = require("./getRowCount"); const logging_1 = require("./logging"); const names_1 = require("./names"); const pluralize_1 = require("./pluralize"); const pollDelay_1 = require("./pollDelay"); const quote_1 = require("./quote"); const runSql_1 = require("./runSql"); const secToHuman_1 = require("./secToHuman"); const tookToHuman_1 = require("./tookToHuman"); const MAX_INITIALIZING_TABLES = 4; const STATES = { i: "initializing", d: "data is being copied", f: "finished table copy", s: "synchronized", r: "ready", }; /** * Waits until backfill of the destination tables finishes. * https://www.postgresql.org/docs/14/catalog-pg-subscription-rel.html */ async function waitUntilBackfillCompletes({ fromDsn, tables, toDsn, schema, }, throwIfAborted) { var _a, _b, _c, _d; if (tables.length === 0) { return; } const waitStartedAt = performance.now(); const tableRowCountsTo = new Map(); const firstSeenActiveTables = new Map(); const stats = []; while (true) { stats.splice(0, stats.length); const backfillingTables = await (0, runSql_1.runSql)(toDsn, (0, quote_1.sql) ` SELECT relname, srsubstate FROM pg_subscription_rel JOIN pg_subscription ON pg_subscription.oid = srsubid JOIN pg_class ON pg_class.oid = srrelid JOIN pg_database ON pg_database.oid = subdbid AND datname = current_database() WHERE subname = ${(0, names_1.subName)(schema)} AND srsubstate <> 'r' `); for (const [table] of backfillingTables) { if (!tableRowCountsTo.has(table)) { tableRowCountsTo.set(table, 0); } } const [activeTables, initializingTables] = (0, partition_1.default)(backfillingTables, ([, state]) => state !== "i"); for (const [table, state] of activeTables) { if (!firstSeenActiveTables.has(table)) { firstSeenActiveTables.set(table, performance.now()); } const countFrom = (_a = (await (0, getRowCount_1.getRowCount)({ dsn: fromDsn, schema, table, method: "explain", }))) !== null && _a !== void 0 ? _a : 0; const countTo = (_c = (_b = (await (0, getRowCount_1.getRowCount)({ dsn: toDsn, schema, table, method: "pg_stat_progress_copy", }))) !== null && _b !== void 0 ? _b : tableRowCountsTo.get(table)) !== null && _c !== void 0 ? _c : 0; tableRowCountsTo.set(table, countTo); const percent = countFrom === 0 ? "100" : Math.min(100, Math.round((countTo / countFrom) * 100)); const stateName = (_d = STATES[state]) !== null && _d !== void 0 ? _d : state; const approx = countTo > countFrom ? "~" : ""; const elapsedSec = Math.round((performance.now() - firstSeenActiveTables.get(table)) / 1000); const leftSec = Math.round(Math.max(countFrom === 0 || countTo === 0 ? 0 : (elapsedSec / countTo) * (countFrom - countTo), 0)); stats.push(`${table}: ${stateName}, ${approx}${countTo} of ${approx}${countFrom}, ${approx}${percent}%: elapsed=${(0, secToHuman_1.secToHuman)(elapsedSec)} left=${approx}${(0, secToHuman_1.secToHuman)(leftSec)}`); throwIfAborted(); } if (initializingTables.length > 0) { stats.push(initializingTables .map(([table]) => table) .slice(0, MAX_INITIALIZING_TABLES) .join(", ") + (initializingTables.length > MAX_INITIALIZING_TABLES ? ` and ${(0, pluralize_1.pluralize)(initializingTables.length - MAX_INITIALIZING_TABLES, "other table")}` : "") + ": waiting in max_sync_workers_per_subscription queue"); } const physicalReplicasFrom = await (0, getPhysicalReplicas_1.getPhysicalReplicas)({ dsn: fromDsn }); const physicalReplicasTo = await (0, getPhysicalReplicas_1.getPhysicalReplicas)({ dsn: toDsn }); for (const [srcDst, { clientAddr, userName, applicationName, lagSec, feedbackAgoSec, masterLsn, replicaLsn, gap, },] of [ ...physicalReplicasFrom.map((replica) => ["src", replica]), ...physicalReplicasTo.map((replica) => ["dst", replica]), ]) { const prefix = `physical replica of the ${srcDst} ${userName}@${clientAddr} (${applicationName})`; stats.push(`${prefix}: lag=${(0, secToHuman_1.secToHuman)(lagSec)} master=${masterLsn} replica=${replicaLsn} gap=${gap} feedbackAgo=${(0, secToHuman_1.secToHuman)(feedbackAgoSec)}`); } if (backfillingTables.length === 0) { // Keep the data in stats. break; } (0, logging_1.progress)("The logical tablesync worker is still backfilling:", ...stats.map((str) => `- ${str}`)); await (0, pollDelay_1.pollDelay)(); throwIfAborted(); } logging_1.progress.clear(); const totalTuples = (0, sum_1.default)([...tableRowCountsTo.values()]); logging_1.log.shellCmd({ comment: `Logical tablesync worker backfill completed in ${(0, tookToHuman_1.tookToHuman)(waitStartedAt)}`, cmd: "", input: [ `backfilled ${(0, pluralize_1.pluralize)(tableRowCountsTo.size, "table")}, ~${(0, pluralize_1.pluralize)(totalTuples, "tuple")}`, ...stats, ] .map((str) => `+ ${str}`) .join("\n"), }); } //# sourceMappingURL=waitUntilBackfillCompletes.js.map