Skip to content

Why switch?

Why teams are ripping out custom pg_dump scripts for Subveil.

Shell scripts rot with every migration. Enterprise platforms cost a fortune and take months to land. Subveil is one binary that understands your schema.

Side by side

Pick who you're comparing against.

CapabilityCustom scriptsBash & PythonEnterprise toolsDelphix / RedgateSubveilSingle Go binary
Foreign key handlingHand-ordered; breaks when tables changeSupportedAutomatic topological sort, cycles resolved
Survives schema migrationsBrittle: every migration risks a broken scriptRe-profiling and manual rule upkeepRe-introspects on every run; drift check in CI
Time to first masked sliceDays to write, ongoing to maintainLong, sales-led setupMinutes: go install, init, pull
Cost to adoptFree, paid in engineer timeEnterprise licensing and servicesOpen-source CLI
Deploy footprintGlue of bash, Python and cronHeavy platform and dedicated infrastructureOne static Go binary
Consistent masking across tables and servicesRandom per run; joins driftSupportedDeterministic HMAC
Raw PII leaves your networkDepends on the scriptDepends on deploymentNever: runs locally or in your CI (zero-trust)
Audit evidenceNoneSupportedCryptographic receipt per sync

Competitor characteristics are general and vary by product, edition and deployment. Verify against current vendor documentation.

Under the hood

Why it doesn't break

Topological graph sorting

Subveil reads your foreign keys and builds a dependency graph, then orders tables so every parent is loaded before its children. Row selection follows the same edges, so a slice includes exactly the rows it references and no dangling keys. Add a table or column and the order updates itself on the next run.

  1. pass 1
    tenants
  2. pass 2
    users
    plans
  3. pass 3
    orders
  4. pass 4
    payments

Circular FK breakers

Self-references and mutual foreign keys have no valid linear order. Subveil detects each cycle, loads rows with the cyclic column deferred, then patches the references in a second pass, so the restore never fails on a constraint.

two-pass resolve
-- employees.manager_id → employees.id (self-referential)
​
-- pass 1: insert rows, deferring the cyclic FK
INSERT INTO employees (id, name, manager_id) VALUES (1, 'A', NULL), (2, 'B', NULL);
​
-- pass 2: patch the references once every row exists
UPDATE employees SET manager_id = 1 WHERE id = 2;

Replace the script in an afternoon.

subveil --help

Quickstart