@clickup/pg-microsharding
Version:
Microshards support for PostgreSQL
123 lines (107 loc) • 3.64 kB
text/typescript
import { basename } from "path";
import chalk from "chalk";
import compact from "lodash/compact";
import { move } from "../api/move";
import { shellQuote } from "../cli";
import { chooseDsns } from "../internal/chooseDsns";
import { getSchemaName } from "../internal/getSchemaName";
import { isTrueValue } from "../internal/isTrueValue";
import { log, print, stdlog } from "../internal/logging";
import { dsnFromToShort } from "../internal/names";
import { normalizeDsns } from "../internal/normalizeDsns";
import type { Args } from "../internal/parseArgs";
import {
isInTmuxSession,
runInTmux,
tryReattachToTmuxSession,
} from "../internal/runInTmux";
/**
* Moves a shard from one database to another with no downtime.
*/
export async function actionMove(args: Args): Promise<boolean> {
await tryReattachToTmuxSession();
const dsns = await normalizeDsns(args["dsns"]);
let shard: number;
if (args["shard"]?.match(/^(\d+)$/)) {
shard = parseInt(args["shard"], 10);
} else {
throw "Please provide --shard, a microshard number to move";
}
const [fromDsns, fromWarnings] = chooseDsns({
patterns: compact([args["from"]]),
existingDsns: dsns,
requireMatch: false,
});
if (fromDsns.length === 0) {
throw "Please provide --from, source DB DSN, as postgresql://user:pass@host/db?options, or a DSN prefix";
}
const [toDsns, toWarnings] = chooseDsns({
patterns: compact([args["to"]]),
existingDsns: dsns,
requireMatch: false,
});
if (toDsns.length === 0) {
throw "Please provide --to, destination DB DSN, as postgresql://user:pass@host/db?options, or a DSN prefix";
}
const activateOnDestination = isTrueValue(args["activate-on-destination"]);
if (activateOnDestination === undefined) {
throw "Please provide --activate-on-destination=yes or --activate-on-destination=no";
}
const deactivateSQL = String(args["deactivate-sql"] || "") || undefined;
const maxReplicationLagSec =
Number(args["max-replication-lag-sec"] ?? "") || undefined;
const wait = args["wait"];
if (isInTmuxSession()) {
const unpipe = stdlog.pipeToNewLogFile(`move-${shard}`);
try {
log(
"$ " +
[basename(process.argv[1]), ...process.argv.slice(2)]
.map(shellQuote)
.join(" "),
);
for (const warning of [...fromWarnings, ...toWarnings]) {
print(warning);
}
const schema = await getSchemaName(fromDsns[0], shard);
if (!schema) {
throw `Can't determine schema name for microshard number ${shard}`;
}
await move({
schema,
fromDsn: fromDsns[0],
toDsn: toDsns[0],
commitAction: activateOnDestination
? "deactivate-activate"
: "rename-to-new",
deactivateSQL,
maxReplicationLagSec,
wait,
});
if (!activateOnDestination) {
print(
"\n" +
chalk.yellow(
"ATTENTION: the schema has been copied, but NOT activated on the destination. " +
"So effectively, it was a dry-run, and the data still lives in the source DB. " +
"To activate the schema on the destination and deactivate on the source, run " +
"the tool with --activate-on-destination=yes option.",
),
);
}
} finally {
await unpipe();
}
} else {
await runInTmux([
[
{
key: shard.toString(),
title: `microshard ${shard} | ${dsnFromToShort(fromDsns[0], toDsns[0])}`,
command: process.argv,
},
],
]);
}
return true;
}