From 4 databases to 1 — consolidating the polyglot stack
Most data-driven teams end up running four databases without ever deciding to. Postgres for the relational system-of-record. MongoDB for the flexible document store. Redis for cache and sessions. Neo4j for the relationship graph. Each was the right tool for one job — and each added a full operational tax: replication, backups, security review, schema migration, monitoring, on-call rotation. They share data by ETL that loses identity, history, and provenance at every hop, and governance becomes a fifth product bolted on top.
RelataDB collapses all four into one binary that speaks every one of those wire protocols. Your Postgres app keeps using psql/psycopg2. Your Mongo app keeps using the official driver. Your Redis calls keep working. Your graph queries still run over Bolt. But they all read and write one governed store — with identity fusion, bi-temporal history, PROV-O provenance, and cell-level ACL built in.
This is the story of how that consolidation actually happens — the before, the after, and the phased path between them. Every capability is shipped today; the maturity table at the end is honest about which migration connectors are live vs. stubbed.
The "before" — a typical 4-database stack
Meet a fictional but realistic team: Sentinel Watch, a mid-market fraud-and-comms analytics SaaS. Their platform ingests call records, transactions, sanctions feeds, and customer profiles, and serves investigators a linked view of "who knows whom, who paid whom." Their stack grew organically:
┌───────────────┐ ETL ┌───────────────┐
app ─► │ Postgres │ ──────► │ Neo4j │ ◄─ graph queries
│ customers, │ │ relationships │
│ transactions │ │ rebuilt nightly│
└───────────────┘ └───────────────┘
┌───────────────┐ sync ┌───────────────┐
app ─► │ MongoDB │ ◄─────► │ Redis │ ◄─ cache + sessions
│ event logs, │ │ │
│ flexible docs │ └───────────────┘
└───────────────┘
│
▼ nightly identity-resolution batch
┌───────────────┐
│ identity tool │ (lossy, LLM-guessed, expensive)
└───────────────┘
The pain they live with:
- Same entity, four copies. Alice is
customer_id=42in Postgres,{_id: "a91f…"}in Mongo,user:alicein Redis, and a disconnected node in Neo4j. Reconciling them is a nightly batch job that's lossy and never quite current. - History is gone. Postgres keeps "latest value wins." Mongo overwrites. When an auditor asks "what did we know on March 15?", the answer is "restore a backup and guess."
- No provenance. Who put a fact there? When? From which feed? Nobody knows —
created_atcolumns are half-populated. - Governance is a separate product. Cell-level access (redact SSN for team X, mask PII for country Y) is enforced in application code, inconsistently, across four read paths.
- Operational tax ×4. Four replication setups, four backup schedules, four security reviews, four schema-migration pipelines, four on-call pages.
The "after" — one binary, four protocols, one governed store
┌── psql / psycopg2 / pgvector (pgwire :5433)
├── Mongo driver (Mongo wire :27017)
┌────────────────────────────────────┐ ├── redis-cli / resp (Redis :6379)
app ─► │ RelataDB (1 binary) │ ◄─ all four├── Neo4j driver / Bolt (Bolt :7687)
│ │ protocols└── HTTP / gRPC / Flight / MCP / SPARQL
│ one bi-temporal governed store │
│ + identity fusion (deterministic) │
│ + provenance on every row │
│ + Cedar cell-level ACL │
└────────────────────────────────────┘
Same app. Same drivers. Same query shapes. Four databases become one fronted by thirteen wire doors — and behind those doors is one store where identity is auto-merged, history is bi-temporal, every fact is provable, and access is enforced in the query path.
What Sentinel Watch stops doing on day one:
- Stop running the nightly identity-resolution batch — SmartIngest canonicalizes 76 identifier kinds on write and auto-links as records land.
- Stop rebuilding the Neo4j graph nightly — edges derive from rows (and
GraphTriggermakes the graph self-building from typed columns). - Stop hand-cleaning
+44 7700…vs07700…— deterministic phone/IBAN/email/crypto canonicalization, byte-identical every run. - Stop bolting governance on each read path — Cedar ABAC fires once, in the planner, on every door.
- Stop operating four databases — one binary, one backup, one audit chain, one on-call.
What you keep unchanged
- Drivers & ORMs —
psycopg2, the official Mongo driver,redis-py, the Neo4j driver,boto3,psql,mongosh,redis-cli,cypher-shell, DBeaver, TablePlus, LangChain PGVector… all work unchanged. Repoint host/port and use the bearer token as the password. - Query shape — your SQL keeps being SQL. Your Cypher keeps being Cypher (
MATCHauto-routes). Your Mongo queries keep working (within the supported subset). - App code — zero rewrite. The doors are wire-compatible servers.
A worked migration — Sentinel Watch, phased
Phase 1 — stand up Relata in parallel (week 1)
Run Relata alongside the existing four DBs. No app changes. Enable the doors you need:
RELATA_BEARER_TOKEN=<token> \
RELATA_PG_ENABLE=true RELATA_PG_BIND=0.0.0.0 \
RELATA_MONGO_ENABLE=true RELATA_MONGO_BIND=0.0.0.0 \
RELATA_REDIS_ENABLE=true RELATA_REDIS_BIND=0.0.0.0 \
RELATA_BOLT_ENABLE=true RELATA_BOLT_BIND=0.0.0.0 \
RELATA_PROFILE=server relata servePoint one read-only copy of each app at Relata and confirm queries return correct shape. No data has moved yet — this is a smoke test that your clients are wire-compatible. See Compatibility & Doors for the per-protocol quickstarts.
Phase 2 — migrate the historical data (weeks 2–3)
Migrate data out of the old DBs into Relata's governed store. Honest maturity:
| Source | Migration path | Status |
|---|---|---|
| Postgres | relata import --from postgres --dsn "postgresql://..." --table <T> --type <Type> — server-side cursor, streaming pages, type-faithful | ✅ Live |
| CSV / NDJSON | relata import --from csv --file <path> (or --from ndjson) | ✅ Live |
| MongoDB | Export to NDJSON (mongoexport) → relata import --from ndjson; the live door handles ongoing traffic meanwhile | 🟡 Import stub documented; use the export workaround |
| Neo4j | Export nodes/edges to CSV/NDJSON → relata import; the live Bolt door handles ongoing traffic | 🟡 Import stub documented; use the export workaround |
| Redis | Re-hydrate from your snapshot by writing through the Redis door (keys land as governed KvEntry rows) | ✅ Door path |
Important nuance: the doors (Mongo/Bolt/ClickHouse) are production-grade — your app's ongoing reads and writes through them work today. The one-time
relata import --from <source>connector for Mongo/Neo4j/ClickHouse is the part that's still an honest stub; until it lands, export-then-import gets the historical rows in. See Connectors & Extensions.
Every migrated row lands through the governed write path (governed_upsert_many) — so SmartIngest identity detection, ACL, tenant-ownership, and audit logging all apply automatically.
Phase 3 — cut the app over, door by door (weeks 3–4)
One door at a time, repoint the app's connection string from the old DB to Relata's door. Roll back is trivial — point back at the old DB. Each cutover is independent:
- Postgres app →
psql -h relata-vip -p 5433 -U relata(password = token) - Mongo app →
mongodb://relata-vip:27017(authrelata/ token) - Redis app →
redis-cli -h relata-vip -p 6379 -a <token> - Neo4j app →
bolt://relata-vip:7687(authneo4j/ token)
Writes now land in the governed store. Reads over any door see them — the Mongo app's write is visible to the Postgres app's SELECT, the Neo4j app's graph traversal, the S3 door's objects, and the SQL search.
Phase 4 — decommission the old DBs (week 5+)
Once traffic is stable on Relata, retire the old databases one at a time. The operational tax collapses from ×4 to ×1: one backup (object-store-native), one audit chain, one schema model, one on-call rotation.
The payoff — what becomes possible that wasn't before
Consolidation alone is a win. But because all four data shapes now live in one governed store, capabilities that were impossible in the polyglot stack become a query:
- Cross-source identity fusion — the Mongo event log's
user_idresolves to the Postgres customer'semailresolves to the Neo4j node.LOOKUP_IDENTITY/RESOLVE_IDENTITY/PATHS_BETWEEN. - Bi-temporal "what did we know when" —
AS OF '<ts>'across types that came from different original DBs. - Cross-shape joins —
SELECT … FROM MongoDocument JOIN Person ON …— impossible when they lived in separate engines. - One audit chain — a forensic query spans what used to be four separate logs.
- Governance once — Cedar ABAC enforces cell-level access uniformly across every protocol a request came in on.
Honest maturity table
| Capability | Status |
|---|---|
| Postgres / pgvector door | ✅ Production |
| MongoDB wire door | ✅ Production (subset: SCRAM-SHA-256, no transactions/change-streams/$push/$pull/$unset) |
| Redis RESP door | ✅ Production (no MULTI/EXEC, scripting, cluster commands) |
| Neo4j HTTP Cypher + Bolt doors | ✅ Production (Cypher subset; read + governed write) |
| ClickHouse HTTP / native doors | ✅ Production (read-only) |
| S3-compatible door | ✅ Production (with bi-temporal ?versions; cluster fan-out pending) |
relata import --from postgres | ✅ Live |
relata import --from {csv,ndjson} | ✅ Live |
relata import --from {mongo,neo4j,clickhouse} | 🟡 Honest stubs — use export-then-import today |
See Limits & Caveats for the full per-protocol status.
When NOT to do this
- Your data is clean, public, and low-stakes → a single Postgres is simpler than consolidation.
- You only need one database shape (pure document, pure graph, pure vector) → a specialty DB is lighter.
- Your polyglot stack is already paid-down and stable with no governance pain → don't migrate for migration's sake.
Relata earns its keep when the four-DB tax is real and the data is messy, sensitive, connected, and needs proving.
How to start
- Try one door against a running Relata in 60 seconds — Compatibility & Doors.
- Migrate one Postgres table —
relata import --from postgres --dry-runto preview the mapping. - Cut over one app as a pilot, measure, expand.
See also
- Compatibility & Doors — the wire-protocol story
- Deploying Protocol Doors — production door wiring
- Connectors & Extensions —
relata import+ the ETL framework - Relata vs others — when to pick Relata
- Who Relata is for — by-team map