UNPKG

@clickup/pg-microsharding

Version:
95 lines (91 loc) 3.25 kB
import compact from "lodash/compact"; import partition from "lodash/partition"; import { pgDump } from "./names"; import type { Sql } from "./quote"; import { join, shellQuote } from "./quote"; import { runShell } from "./runShell"; // The downside of turning this on is that, if we run pg_dump over the migrated // shard, then it will emit "ALTER TABLE ... ADD CONSTRAINT ... NOT VALID" (i.e. // PG remembers that the FKs were not validated). This is not the end of the // world, but it's not worth the ~5% overall speedup this option gives. const SKIP_FK_VALIDATION = false; /** * Runs pg_dump, does some basic parsing and returns the result. */ export async function getDump({ fromDsn, schema, }: { fromDsn: string; schema: string; }): Promise<{ preData: Sql; primaryKeys: string[]; postData: Sql; }> { const preData = parseDump( await runShell( `${pgDump(fromDsn)} -n ${shellQuote(schema)} --section=pre-data`, null, `Reading pre-data DDL (except indexes) for ${schema} from ${fromDsn}`, ), ); const postDataAll = parseDump( await runShell( `${pgDump(fromDsn)} -n ${shellQuote(schema)} --section=post-data`, null, `Reading post-data DDL (indexes, foreign keys) for ${schema} from ${fromDsn}`, ), ); // For logical replication to work, each table on the subscription part must // have an index that allows the logical worker to find the row it receives // from the publication end. PostgreSQL 16+ can use regular indexes for that, // but the earlier versions require the subscription end to have either a // REPLICA IDENTITY or a PRIMARY KEY. Thus, we extract such indexes from the // post-data section of the dump and create them before creating the // subscription. const [primaryKeys, postData] = partition( postDataAll, (entry) => entry.match( /^ALTER TABLE .* ADD CONSTRAINT .* PRIMARY KEY \([^)]+\);$/s, ) || entry.match( /^CREATE UNIQUE INDEX (.*?) ON .*\nALTER TABLE .*? REPLICA IDENTITY USING INDEX \1;$/s, ), ); return { preData: join(preData, "\n"), primaryKeys, postData: join(postData, "\n"), }; } /** * The text generated by pg_dump consists of entries divided by 3 lines of * "--"-comments. We split the dump into that entries, and only then analyze it * in the further logic. */ function parseDump(dump: string): string[] { const entries = compact( dump .split(/^--\n-- \w+[^\n]*\n--\n/m) .map((entry) => entry.trim()) // Added in PG17+ and is not supported in earlier versions. .map((entry) => entry.replace(/^SET transaction_timeout = [^\n]+/m, "-- $&"), ) // If enabled, makes FKs creation instant: we don't need to check // integrity at creation time, since the data on the source is already // validated. (It will still check at tuples write time though.) .map((entry) => SKIP_FK_VALIDATION ? entry.replace( /^(ALTER TABLE ONLY [^\n]+\n +ADD CONSTRAINT [^\n]+ FOREIGN KEY [^\n]+ REFERENCES [^\n]+)(;)$/s, "$1 NOT VALID$2", ) : entry, ), ); entries[0] = entries[0].replace(/\n\n+/gs, "\n"); return entries; }