@clickup/pg-microsharding
Version:
Microshards support for PostgreSQL
125 lines • 6.15 kB
JavaScript
;
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