@clickup/pg-microsharding
Version:
Microshards support for PostgreSQL
90 lines • 4.15 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.waitUntilIncrementalCompletes = waitUntilIncrementalCompletes;
const createConnectedClient_1 = require("./createConnectedClient");
const inspectError_1 = require("./inspectError");
const logging_1 = require("./logging");
const names_1 = require("./names");
const parseLsn_1 = require("./parseLsn");
const pollDelay_1 = require("./pollDelay");
const quote_1 = require("./quote");
const tookToHuman_1 = require("./tookToHuman");
/**
* Locks the tables for write and waits until incremental flow to the
* destination tables exhausts.
*
* If succeeded, the function returns a callback which, when called, unlocks the
* tables on the source. This callback must be called after deactivating the
* shard on the source. (But even if it's not called, it'll likely be okay
* still, because the process will end, PG will disconnect, and the tables will
* be auto-unlocked.)
*/
async function waitUntilIncrementalCompletes({ fromDsn, schema, tables, }, throwIfAborted) {
if (tables.length === 0) {
return async () => { };
}
const waitStartedAt = performance.now();
const fromClient = await (0, createConnectedClient_1.createConnectedClient)(fromDsn);
try {
const fromServerVersion = await fromClient.serverVersion();
await fromClient.query("BEGIN");
await fromClient.query("SET LOCAL statement_timeout TO 0");
await fromClient.query("SET LOCAL idle_in_transaction_session_timeout TO 0");
fromServerVersion[0] >= 17 &&
(await fromClient.query("SET LOCAL transaction_timeout TO 0"));
await fromClient.query(`SET LOCAL search_path TO ${schema}`);
throwIfAborted();
const query = (0, quote_1.sql) `LOCK TABLE ONLY ${(0, quote_1.join)(tables.map(({ name }) => (0, quote_1.sql) `${(0, quote_1.ident)(name)}`), ", ")} IN EXCLUSIVE MODE`.toString();
logging_1.log.shellCmd({
comment: "Locking source tables to pause all WRITES",
cmd: `node-postgres ${fromDsn}#${schema}`,
input: query,
});
await fromClient.query(query);
throwIfAborted();
const srcLsn = await fromClient.currentWalInsertLsn();
while (true) {
const dstLsn = await fromClient.confirmedFlushLsn((0, names_1.subName)(schema));
// srcLsn is constant and in the future; dstLsn is in the past, grows
// monotonically and catches up srcLsn eventually. Once srcLsn-dstLsn
// crosses 0, we continue.
const gap = (0, parseLsn_1.subtractLsn)(srcLsn, dstLsn);
(0, logging_1.progress)(`Waiting for dst=${dstLsn} to be greater src=${srcLsn}, gap=${gap}`);
if (gap <= 0) {
break;
}
await (0, pollDelay_1.pollDelay)();
throwIfAborted();
}
}
catch (e) {
try {
await fromClient.query("ROLLBACK");
await fromClient.end();
}
catch (e) {
logging_1.log.error(`Failed to rollback transaction: ${(0, inspectError_1.inspectError)(e)}`);
}
throw e;
}
logging_1.progress.clear();
logging_1.log.shellCmd({
comment: "Incremental logical replication completed",
cmd: "",
input: `Tables were write-locked for ${(0, tookToHuman_1.tookToHuman)(waitStartedAt)}`,
});
return async (status) => {
// It doesn't really matter for the locks, whether we commit or rollback
// here. But in case of a success the "ROLLBACK" message in the logs scare
// people. So we use "COMMIT" in case of success and "ROLLBACK" otherwise.
const query = status === "error" ? "ROLLBACK" : "COMMIT";
logging_1.log.shellCmd({
comment: `Unlocking source tables; write lock was held for ${(0, tookToHuman_1.tookToHuman)(waitStartedAt)}`,
cmd: `node-postgres ${fromDsn}`,
input: query,
});
await fromClient.query(query);
await fromClient.end();
};
}
//# sourceMappingURL=waitUntilIncrementalCompletes.js.map