Operations & troubleshooting
This page collects everything you need to run Brume in production-grade conditions: performance, ops, and the most common errors.
Performance
Section titled “Performance”Parallelism
Section titled “Parallelism”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.
BRUME_PARALLELISM=8 brume executeLower 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
Section titled “fk_depth”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).
Memory
Section titled “Memory”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_columnsgroups 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.
Secret management
Section titled “Secret management”Where to store secrets
Section titled “Where to store secrets”In order of preference:
- A secret manager (Vault, AWS Secrets Manager, GCP Secret Manager, SOPS-encrypted file).
- CI provider secrets (GitHub Actions encrypted secrets, GitLab masked variables).
.env.brumefile 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.
Rotating secrets
Section titled “Rotating secrets”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:
- Pick a date.
- Stop refreshing downstream environments on the day of the rotation.
- Rotate the secrets.
- Re-run
brume executeon all downstream environments. - Notify users that previous fake IDs are no longer valid.
Upgrading to 2.x
Section titled “Upgrading to 2.x”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.
Read-only source account
Section titled “Read-only source account”See .env.brume reference for the SQL grants.
PostgreSQL compatibility
Section titled “PostgreSQL compatibility”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.
Logging
Section titled “Logging”Levels
Section titled “Levels”| Flag | Level |
|---|---|
--quiet | ERROR |
| (default) | INFO |
--verbose | DEBUG |
The final per-table report is always printed to stdout, regardless of level.
JSON logs
Section titled “JSON logs”Use --json for machine ingestion (SIEM, log aggregator, CI artifact):
brume execute --json 2> logs.jsonlEach line is a JSON object with timestamp, level, event, table, and contextual fields.
Common errors
Section titled “Common errors”Connection refused
Section titled “Connection refused”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.
PREFLIGHT_PG_DUMP_VERSION_MISMATCH
Section titled “PREFLIGHT_PG_DUMP_VERSION_MISMATCH”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.
Strategy requires a type
Section titled “Strategy requires a type”configuration error: column `users.email` declares strategy FAKE but no `type`Add type: EMAIL (or whichever type fits). See strategies and types.
NULLIFY on NOT NULL column
Section titled “NULLIFY on NOT NULL column”configuration error: column `users.created_at` is NOT NULL but strategy is NULLIFYEither pick a non-null strategy (FAKE, MASK, HASH, KEEP) or ALTER the column to be nullable on the target.
JSONB path does not resolve
Section titled “JSONB path does not resolve”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.
Template secret still in use
Section titled “Template secret still in use”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.
Uncovered PII columns (from brume plan)
Section titled “Uncovered PII columns (from brume plan)”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: KEEPrule 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_idCopied 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]
Section titled “[UNIQUE_COLLISION]”[UNIQUE_COLLISION] public.holiday : 14 ligne(s) non écrite(s) — leur valeur pseudonymisée entre en collision avec une autre ligne sur une contrainte UNIQUETwo 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.
- Recipes — copy-paste production workflows.
- GDPR & compliance — sign-off checklist.
- CLI commands — flags and exit codes.