Skip to content
Quickstart

Operations & troubleshooting

This page collects everything you need to run Brume in production-grade conditions: performance, ops, and the most common errors.

BRUME_PARALLELISM (default 20) sets the maximum size of the connection pools Brume opens to the source and to the target — and so caps how many queries run at once on each side.

Terminal window
BRUME_PARALLELISM=8 brume execute

Lower it to spare a busy production database: every connection is a backend process on the source. Raise it only when both databases have headroom.

fk_depth is a radius around the rows you select, counted in foreign-key hops towards parents and children: each hop can multiply the rows copied. Start with 1, check the per-table counts of brume plan, and raise it only when a test needs rows that are missing.

On a large schema, orphan_policy: ALLOW_ORPHANS keeps the radius strict instead of copying every parent the copied rows need — at the cost of dangling foreign keys (see orphan_policy).

Brume streams rows — it does not load tables in memory. The two memory-heavy spots are:

  • JSONB columns with deeply nested json_paths — parsing/serialization happens row-by-row but JVM heap grows with object size.
  • linked_columns groups with high cardinality — the deterministic mapping is cached in memory.

Start with the default JVM heap and increase only if you observe OutOfMemoryError in the logs.

In order of preference:

  1. A secret manager (Vault, AWS Secrets Manager, GCP Secret Manager, SOPS-encrypted file).
  2. CI provider secrets (GitHub Actions encrypted secrets, GitLab masked variables).
  3. .env.brume file in a directory with strict POSIX permissions (chmod 600, owned by the brume user only).

Never check .env.brume into git. The template generated by brume init is safe to commit; the filled-in file is not.

Rotation breaks determinism — the same source value will land on a different fake. If any downstream system depends on the determinism (joins on HASH-ed columns, references to FPE_ID-mapped IDs in bug reports, etc.), plan it as a coordinated cutover:

  1. Pick a date.
  2. Stop refreshing downstream environments on the day of the rotation.
  3. Rotate the secrets.
  4. Re-run brume execute on all downstream environments.
  5. Notify users that previous fake IDs are no longer valid.

Brume 2.0 changes some pseudonymized values compared with 1.x — fake phone numbers, fakes on UNIQUE columns, timestamps in .sql dumps (see Determinism and versions). Like a secret rotation, plan the upgrade as a cutover: re-run brume execute on every downstream environment you join or compare.

Brume 2.1 changes no pseudonymized value: upgrading from 2.0 needs no new run. FAKE now follows foreign keys without a linked_columns entry — existing entries keep working.

See .env.brume reference for the SQL grants.

Brume supports PostgreSQL 12, 13, 14, 15, 16, 17 and 18 on both source and target.

The pg_dump binary used by Brume (for schema replication) must be on PATH and its major version must be greater than or equal to the source’s. If your source is Postgres 17, install postgresql-client-17 or newer.

brume execute checks both before copying anything, and stops with PREFLIGHT_PG_DUMP_NOT_FOUND or PREFLIGHT_PG_DUMP_VERSION_MISMATCH — see Preflight checks.

FlagLevel
--quietERROR
(default)INFO
--verboseDEBUG

The final per-table report is always printed to stdout, regardless of level.

Use --json for machine ingestion (SIEM, log aggregator, CI artifact):

Terminal window
brume execute --json 2> logs.jsonl

Each line is a JSON object with timestamp, level, event, table, and contextual fields.

FATAL: connection refused (host=db.prod.internal port=5432)

The source or target is unreachable. The error code tells which one: CONNECTION_SOURCE_UNREACHABLE or CONNECTION_TARGET_UNREACHABLE (brume plan only checks the source). Check VPN, security groups, sslmode.

The pg_dump on PATH is older than the source server. brume execute checks this before copying anything. Install a postgresql-client-NN package whose major version is at least the source’s.

configuration error: column `users.email` declares strategy FAKE but no `type`

Add type: EMAIL (or whichever type fits). See strategies and types.

configuration error: column `users.created_at` is NOT NULL but strategy is NULLIFY

Either pick a non-null strategy (FAKE, MASK, HASH, KEEP) or ALTER the column to be nullable on the target.

warning: path `$.contact.phone` never resolved across 12,498 rows of `users.metadata`

The path is wrong or doesn’t exist in any row of the column. Inspect a few rows manually and adjust the path. Brume continues processing — this is a warning, not a fatal error.

warning: BRUME_HMAC_SECRET matches the value shipped by `brume init` — replace it before running `execute`

Introduced in Brume 1.6. The run isn’t blocked, but any pseudonymization produced with the placeholder is reversible by anyone with access to the Brume source. Generate a fresh high-entropy value (e.g. openssl rand -base64 48 for BRUME_HMAC_SECRET, openssl rand -hex 16 for BRUME_FPE_KEY — 32 characters) and rotate the .env.brume.

plan is heuristic: it scans column names for PII-looking patterns (*_email, *_phone, *_name, address, iban, …). Review every flagged column and either:

  • Add a rule in brume.yml, or
  • Confirm explicitly with a strategy: KEEP rule to document the intent.

[ORPHANS] (with orphan_policy: ALLOW_ORPHANS)

Section titled “[ORPHANS] (with orphan_policy: ALLOW_ORPHANS)”
[ORPHANS] 13 row(s) of order_items reference 5 row(s) of products outside fk_depth via product_id

Copied rows reference rows beyond the fk_depth radius, which are not copied. Joins on that foreign key lose them, and re-creating the constraint on the target fails. Raise fk_depth, add the referenced table to extraction.tables, or switch back to the default orphan_policy: KEEP_PARENTS.

[UNIQUE_COLLISION] public.holiday : 14 ligne(s) non écrite(s) — leur valeur pseudonymisée entre en collision avec une autre ligne sur une contrainte UNIQUE

Two rows got the same pseudonymized value on a UNIQUE column (here a DATE_SHIFT on dense dates). The run goes on and the lost rows are counted per table in the report. To keep every row, use HASH, FPE_ID or FPE_UUID on that column — see UNIQUE columns and collisions.

Column '…' is UNIQUE in the source schema but … (from brume plan)

Section titled “Column '…' is UNIQUE in the source schema but … (from brume plan)”

MASK, or FAKE with type: DATE, on a UNIQUE column: almost every value would collide, so plan refuses the configuration (CONFIG_SCHEMA_UNIQUE_NOT_INJECTIVE, exit code 1). Pick HASH, FPE_ID, FPE_UUID, or FAKE with a name / email type.

pg_dump: error: no matching schemas were found

Section titled “pg_dump: error: no matching schemas were found”

BRUME_SCHEMA must be the schema name exactly as stored — case, accents and spaces included, without quotes.