@clickup/pg-microsharding
Version:
Microshards support for PostgreSQL
253 lines (211 loc) • 7.69 kB
text/typescript
#!/usr/bin/env node
import chalk from "chalk";
import mapValues from "lodash/mapValues";
import pickBy from "lodash/pickBy";
import { actionAllocate } from "./actions/actionAllocate";
import { actionCleanup } from "./actions/actionCleanup";
import { actionCopy } from "./actions/actionCopy";
import { actionFactor } from "./actions/actionFactor";
import { actionInstall } from "./actions/actionInstall";
import { actionList } from "./actions/actionList";
import { actionMove } from "./actions/actionMove";
import { actionRebalance } from "./actions/actionRebalance";
import { allocate } from "./api/allocate";
import { cleanup } from "./api/cleanup";
import { factor } from "./api/factor";
import { install } from "./api/install";
import { move } from "./api/move";
import { rebalance } from "./api/rebalance";
import { weights } from "./api/weights";
import { inspectError } from "./internal/inspectError";
import { print } from "./internal/logging";
import { parseArgs } from "./internal/parseArgs";
import { shellQuote } from "./internal/quote";
import { readConfigs } from "./internal/readConfigs";
import { unindent } from "./internal/unindent";
export {
allocate,
cleanup,
factor,
install,
move,
rebalance,
shellQuote,
weights,
};
const HELP = unindent(`
USAGE
pg-microsharding install
[--schema-name-fmt=SCHEMA_NAME_FMT]
[--dsn=DSN | --dsns=DNS1,DSN2,...]
pg-microsharding list | ls
[--weight-sql='SELECT returning weight with optional unit']
[--verbose]
[--dsn=DSN | --dsns=DNS1,DSN2,...]
[--json]
pg-microsharding allocate
--shard=N | --shards=N-M
--migrate-cmd='shell command to run migrations'
--activate={yes | no}
[--dsn=DSN | --dsns=DNS1,DSN2,...]
pg-microsharding factor
--shard=N | --shards=N,M,... | --shards=DSN-PREFIX
--factor=P|+P.Q|-P.Q|"*P.Q"
[--dsn=DSN | --dsns=DNS1,DSN2,...]
pg-microsharding move
--shard=N
--from=DSN-OR-DSN-PREFIX-OR
--to=DSN-OR-DSN-PREFIX
--activate-on-destination={yes | no}
[--wait]
[--max-replication-lag-sec=N]
[--dsns=DNS1,DSN2,...]
[--deactivate-sql='SQL $1 SQL']
pg-microsharding rebalance
--activate-on-destination={yes | no}
[--wait]
[--deactivate-sql='SQL $1 SQL']
[--weight-sql='SELECT returning weight with optional unit']
[--decommission=DSN1,DSN2,...]
[--max-replication-lag-sec=N]
[--parallelism=N]
[--dsn=DSN | --dsns=DNS1,DSN2,...]
pg-microsharding cleanup
[--dsn=DSN | --dsns=DNS1,DSN2,...]
pg-microsharding copy
--schema=SCHEMA-NAME
--from=DSN-OR-DSN-PREFIX-OR
--to=DSN-OR-DSN-PREFIX
[--wait]
[--max-replication-lag-sec=N]
[--dsns=DNS1,DSN2,...]
ENVIRONMENT VARIABLES
The tool receives parameters from command line option, but allows to set
defaults for most of them using environment variables.
Some variables are standard for psql command:
- PGUSER: default database user
- PGPASSWORD: default database password
- PGHOST: default database host
- PGPORT: default database port
- PGDATABASE: default database name
- PGSSLMODE: default SSL mode (e.g. "prefer")
Custom variables:
- DSNS or PGDSNS: default value for --dsns option, comma-separated
list of DSNs (see below)
- MIGRATE_CMD: default value for --migrate-cmd option
- WEIGHT_SQL: default value for --weight-sql option
- DEACTIVATE_SQL: default value for --deactivate-sql option
- PARALLELISM: default value for --parallelism option
- MAX_REPLICATION_LAG_SEC: default value for --max-replication-lag-sec
- SCHEMA_NAME_FMT: default value for --schema-name-fmt (applies only
to install action; if not passed, "sh%04d" is used)
CONFIG FILE
Unless --skip-config flag is passed, the tool also tries to find
pg-microsharding.config.js (or .ts) file in the current and parent
directories. If found, it treats its exports as environment variables
(or as a function that returns environment variables), merging them
into process.env.
DSN AND ADDRESSING DATABASES
Option --dsns, if required, should be a comma separated list of DSNs.
Also, you may pass duplicated DSNs and even DSNs of replicas: the tool
will filter them out and remain only master DSNs in the list.
DSN format examples (parts defaults are from environment variables):
- postgresql://user:pass@hostname/db?options (all parts are optional)
- hostname:port/db (all parts except the hostname are optional)
The tool also tries to find pg-microsharding.config.js (or .ts) file in
the current and parent directories and, if found, treats its exports as
environment variables, merging them into process.env.
REPLICATION LAG PREVENTION
The tool tries hard to not affect the replication lag of the destination
node when moving or rebalancing shards. It waits until the lag drops below
--max-replication-lag-sec seconds before running heavy operations (or
until the user presses Shift+S to force-continue).
Also, if you want the tool to pause explicitly and wait until the user
presses Shift+S before activating the shard on the destination node,
you can use the --wait option.
`);
const ACTIONS = {
allocate: actionAllocate,
cleanup: actionCleanup,
copy: actionCopy,
factor: actionFactor,
install: actionInstall,
list: actionList,
ls: actionList,
move: actionMove,
rebalance: actionRebalance,
};
/**
* Tool main function.
*/
export async function main(argsIn: string[]): Promise<boolean> {
const args = parseArgs(argsIn);
if (!args["skip-config"]) {
for (const config of await readConfigs("pg-microsharding.config")) {
Object.assign(
process.env,
mapValues(
pickBy(
config,
(v) =>
typeof v === "string" ||
typeof v === "number" ||
typeof v === "boolean",
),
String,
),
);
}
}
const DSNS = process.env["PGDSNS"] || process.env["DSNS"];
if (!args["dsns"] && !args["dsn"] && DSNS) {
args["dsns"] = DSNS;
}
const MIGRATE_CMD = process.env["MIGRATE_CMD"];
if (!args["migrate-cmd"] && MIGRATE_CMD) {
args["migrate-cmd"] = MIGRATE_CMD;
}
const WEIGHT_SQL = process.env["WEIGHT_SQL"];
if (!args["weight-sql"] && WEIGHT_SQL) {
args["weight-sql"] = WEIGHT_SQL;
}
const DEACTIVATE_SQL = process.env["DEACTIVATE_SQL"];
if (!args["deactivate-sql"] && DEACTIVATE_SQL) {
args["deactivate-sql"] = DEACTIVATE_SQL;
}
const PARALLELISM = process.env["PARALLELISM"];
if (!args["parallelism"] && PARALLELISM) {
args["parallelism"] = PARALLELISM;
}
const MAX_REPLICATION_LAG_SEC = process.env["MAX_REPLICATION_LAG_SEC"];
if (!args["max-replication-lag-sec"] && MAX_REPLICATION_LAG_SEC) {
args["max-replication-lag-sec"] = MAX_REPLICATION_LAG_SEC;
}
const SCHEMA_NAME_FMT = process.env["SCHEMA_NAME_FMT"];
if (!args["schema-name-fmt"] && SCHEMA_NAME_FMT) {
args["schema-name-fmt"] = SCHEMA_NAME_FMT;
}
const action = args._[0] as keyof typeof ACTIONS;
if (!action) {
print(HELP);
return true;
}
if (action in ACTIONS) {
return ACTIONS[action](args);
}
throw `Unknown action: ${action}`;
}
/**
* A wrapper around main() to call it from a bin script.
*/
export function cli(): void {
main(process.argv.slice(2))
.then((success) => process.exit(success ? 0 : 1))
.catch((e) => {
print(chalk.red(inspectError(e)));
process.exit(1);
});
}
if (require.main === module) {
cli();
}