@clickup/pg-microsharding
Version:
Microshards support for PostgreSQL
129 lines • 6.41 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.waitUntilEverythingIsNotLaggingMuch = waitUntilEverythingIsNotLaggingMuch;
const readline_1 = __importDefault(require("readline"));
const getPhysicalReplicas_1 = require("./getPhysicalReplicas");
const getSlotInfo_1 = require("./getSlotInfo");
const logging_1 = require("./logging");
const names_1 = require("./names");
const pollDelay_1 = require("./pollDelay");
const secToHuman_1 = require("./secToHuman");
const tookToHuman_1 = require("./tookToHuman");
const FORCE_CONTINUE_KEY = {
sequence: "S",
prompt: "press Shift+S to force-continue: not recommended!",
};
/**
* Waits until all physical replicas on a DSN are not lagging for too much, so
* we can e.g. call waitUntilIncrementalCompletes() and then resultCommit().
* Also, if fromDsn is passed, waits for the logical slot to lag not more than
* maxReplicationLagSec time.
*
* For the logical slot replication lag estimation, in the beginning, we
* "latch", i.e. remember the master's LSN, and then wait until the slot replay
* is past that LSN. Once it happens, the amount of time passed is roughly the
* logical replication slot lag in seconds. This is the only way to estimate,
* how many seconds behind is a logical slot.
*
* For physical replicas, we query their replication lag in seconds directly.
*
* If waitForUserToContinue=true, then the function exits ONLY when the user
* told it to do so explicitly by pressing the hotkey.
*/
async function waitUntilEverythingIsNotLaggingMuch({ when, fromDsn, tables, toDsn, schema, maxReplicationLagSec: maxLagSec, waitForUserToContinue, }, throwIfAborted) {
var _a, _b, _c, _d;
if (tables.length === 0) {
return;
}
const waitStartedAt = performance.now();
const rl = readline_1.default.createInterface({
input: process.stdin,
output: process.stdout,
});
try {
let physicalReplicas = undefined;
let slotInfo = undefined;
let stats = [];
let delayPromise = null;
let userForcedContinue = false;
readline_1.default.emitKeypressEvents(process.stdin, rl);
rl.on("SIGINT", () => {
throwIfAborted = () => {
throw "Aborted due to SIGINT";
};
});
(_b = (_a = process.stdin).setRawMode) === null || _b === void 0 ? void 0 : _b.call(_a, true);
process.stdin.on("keypress", (_str, key) => {
if (key.sequence === FORCE_CONTINUE_KEY.sequence) {
userForcedContinue = true;
delayPromise === null || delayPromise === void 0 ? void 0 : delayPromise.clear();
}
});
for (let i = 0, needWait = true; needWait && !userForcedContinue; i++) {
throwIfAborted === null || throwIfAborted === void 0 ? void 0 : throwIfAborted();
physicalReplicas = await (0, getPhysicalReplicas_1.getPhysicalReplicas)({
dsn: toDsn,
comment: i === 0
? `Waiting till ${fromDsn ? "the slot and " : ""}replicas are not lagging much (${when})`
: undefined,
});
slotInfo = fromDsn
? await (0, getSlotInfo_1.getSlotInfo)({ dsn: fromDsn, slotName: (0, names_1.subName)(schema) }, slotInfo)
: undefined;
needWait = waitForUserToContinue;
stats = [];
if (slotInfo) {
const { srcLsn, dstLsn, gap, gapTillLatch, lagSec } = slotInfo;
const prefix = "Logical slot";
const info = `src=${srcLsn} dst=${dstLsn} gap=${gap} gapTillLatch=${gapTillLatch}`;
if (lagSec === null) {
needWait = true;
stats.push(`? ${prefix}: lag is unknown yet (likely LARGE; be patient, estimating); ${info}`);
}
else if (lagSec > maxLagSec) {
needWait = true;
stats.push(`- ${prefix}: lag is ${(0, secToHuman_1.secToHuman)(lagSec)} > ${(0, secToHuman_1.secToHuman)(maxLagSec)}; ${info}`);
}
else {
stats.push(`+ ${prefix}: lag is ${(0, secToHuman_1.secToHuman)(lagSec)} <= ${(0, secToHuman_1.secToHuman)(maxLagSec)}; ${info}`);
}
}
for (const { clientAddr, userName, applicationName, lagSec, feedbackAgoSec, masterLsn, replicaLsn, gap, } of physicalReplicas) {
const prefix = `Physical replica of the dst ${userName}@${clientAddr} (${applicationName})`;
const info = `master=${masterLsn} replica=${replicaLsn} gap=${gap} feedbackAgoSec=${feedbackAgoSec}`;
if (lagSec === null) {
needWait = true;
stats.push(`? ${prefix}: lag is unknown yet; ${info}`);
}
else if (lagSec > maxLagSec) {
needWait = true;
stats.push(`- ${prefix}: lag is ${(0, secToHuman_1.secToHuman)(lagSec)} > ${(0, secToHuman_1.secToHuman)(maxLagSec)}; ${info}`);
}
else {
stats.push(`+ ${prefix}: lag is ${(0, secToHuman_1.secToHuman)(lagSec)} <= ${(0, secToHuman_1.secToHuman)(maxLagSec)}; ${info}`);
}
}
(0, logging_1.progress)(waitForUserToContinue
? `Waiting for the user action: ${FORCE_CONTINUE_KEY.prompt}`
: `Waiting for the lag to stabilize (or ${FORCE_CONTINUE_KEY.prompt}):`, ...stats);
await (delayPromise = (0, pollDelay_1.pollDelay)());
}
logging_1.progress.clear();
logging_1.log.shellCmd({
comment: userForcedContinue
? `Force-continued in ${(0, tookToHuman_1.tookToHuman)(waitStartedAt)}`
: `Everything is caught up in ${(0, tookToHuman_1.tookToHuman)(waitStartedAt)}`,
cmd: "",
input: stats.join("\n"),
});
}
finally {
rl.close();
(_d = (_c = process.stdin).setRawMode) === null || _d === void 0 ? void 0 : _d.call(_c, false);
process.stdin.pause();
}
}
//# sourceMappingURL=waitUntilEverythingIsNotLaggingMuch.js.map