
XferDB v0.4.0 — MongoDB to Postgres, the migration path we’ve been waiting for
XferDB v0.4.0 — MongoDB to Postgres, the migration path we've been waiting for
I just shipped v0.4.0 of XferDB to Gitea (and synced to the GitHub mirror). This is the release I've been waiting on for a while, because it does the one thing every team I've talked to has been asking me for: MongoDB → PostgreSQL, end-to-end, with schema inference.
You can grab the binaries below. If you want to follow along, the full source is open.
Download XferDB v0.4.0
| Platform | Binary |
|---|---|
| Linux x86_64 | xferdb-linux-amd64 |
| macOS Apple Silicon | xferdb-darwin-arm64 |
| macOS Intel | xferdb-darwin-amd64 |
If you'd rather build from source:
git clone https://github.com/bchan77/XferDB.git cd XferDB && git checkout v0.4.0 && go build -o xferdb ./cmd/xferdb
The release is on GitHub. CHANGELOG lives in the repo at CHANGELOG.md.
Why this release matters to me
When I started XferDB the goal was simple: get data out of one database and into another without losing your weekends to hand-rolled scripts. The first releases (v0.2.x and v0.3.x) covered the relational world really well — Postgres → Postgres, Postgres → YugabyteDB, SQLite → Postgres. They handled schema, indexes, foreign keys, parallel table workers, bulk-copy, all the things you'd want.
But the NoSQL side stayed rough. You could wire MongoDB up as a source by hand if you knew the engine well enough, but there was no schema inference, no real count, no test path. v0.4.0 closes that gap.
The headline is: MongoDB 8.0 → PostgreSQL 13 is now an officially-tested migration path. First end-to-end migration I trust. That's what you came for; that's what I'm most excited about.
What's in v0.4.0
MongoDB source adapter with schema inference
The big one. You point XferDB at a MongoDB collection and it samples documents, infers a Postgres schema, and writes a draft plan you can review before any data moves. For the unversioned-document crowd that means JSONB for the genuinely schemaless fields and typed columns for the predictable ones.
# Sample source documents and infer a Postgres schema xferdb project analyze # Review the inferred plan, override any field you don't like xferdb project schema
Two flags make this practical at scale:
--accurate-countsuses Mongo's exactCountDocumentsinstead of the storage engine's estimate. The estimate is fast but lies, especially on sharded clusters or after deletes. With this flag your ETA is honest.--sample-pctswitches sampling from "first N documents" to a percentage once a collection crosses--sample-threshold. So a 100M-document collection doesn't waste time reading 2000 docs that are all from the same hot shard.
Async pipeline mode (--async-pipeline)
The worker that reads from Mongo and the worker that writes to Postgres now run as separate goroutines connected by buffered channels. Read N+1 while writing N. On my home Bobby-dev box this dropped the MongoDB 8.0 → Postgres 13 run on a 1.2M-row orders collection from 1488s to 974s — about 35% faster, with the same --batch-size 2000. YMMV depending on network and source DB, but the architecture is just plain better.
If you don't want it, just don't pass the flag; XferDB falls back to the synchronous loop from v0.2/v0.3.
Stats history recording
This is the one I wanted most during testing. Before, you got a final Rows: 1,200,000 / 1,200,000 (Migration complete) line and that was it. Now the engine records a snapshot every 5 seconds — goroutines, heap, batch counters, read/write throughput, ETA — and you can pull it back after the fact:
# Look at the time series of a finished migration xferdb migrate --history # Or via the API curl http://localhost:8081/api/v1/migrations/<id>/stats/history
It's invaluable when a migration runs overnight and you want to know whether it was memory-bound, network-bound, or just slow source writes. The --history flag on the CLI gives you a tidy table; the API gives you the raw snapshots for graphing.
Table status detail
The live display used to read pending / running / done. With v0.4.0 it shows the actual sub-state: counting rows…, waiting for schema…, waiting for indexes…, writing batches…. The display never appears hung anymore, even when Mongo's count is taking 30 seconds because the source is sharded and busy.
Bug fixes that matter for long migrations
- Row totals now reflect Mongo's actual count, not the over-estimate from
db.collection.stats(). ETA recalculates with the corrected number, not the stale estimate. - Rate decays when no batches arrive for a while (e.g. during bulk-copy setup, schema-finalize, index build) instead of staying frozen at the last observed throughput. Capped at 10% so the rate never hits zero and lies about whether the migration is alive.
- Mid-migration row-count drift is now logged, not silent.
What it looks like in practice
The migration I keep running is the same one I've been tuning for months: a MongoDB shop.orders collection into a Postgres public.orders table. With v0.4.0:
# Server first, once per host xferdb server --addr :8081 # Project setup xferdb project create \ --name mongodb_to_postgres \ --source "mongodb://mongo:$MONGO_PW@192.168.21.133:27017/shop?authSource=admin" \ --target "postgres://yugabyte:$PG_PW@postgres.homelab.local:5432/yugabyte?sslmode=disable" xferdb project use mongodb_to_postgres xferdb project analyze # Mongo schema → Postgres draft xferdb project schema # review & override xferdb project preflight # connectivity, auth, perms # The migration itself xferdb migrate \ --batch-size 2000 \ --table-workers 3 \ --segment-workers 1 \ --async-pipeline \ --recreate-schema
That last command finished a 1.2M-row orders migration in ~16 minutes on a 1.7 GiB RAM box with 3 table-workers, 1 segment-worker, and the async pipeline enabled. v0.3.x with the same shape was ~25 minutes. The async pipeline alone accounts for ~35% of that.
What's not in v0.4.0
To keep expectations clear:
- MySQL → Postgres is implemented in code but I haven't run it end-to-end. If you do, please file an issue with what you saw; I'm holding off on tagging it
testeduntil I have at least three independent reports. - MongoDB as a target is
🔜— we read from Mongo, but we don't write to it. No 0.4.0 timeline on this. - Cassandra is
🔜everywhere. No code yet.
If any of these are blockers for you, open an issue on Gitea or GitHub and we'll talk.
What's next
The 0.5.0 line is already brewing in feat/record-migration-stats (which is now main post-merge). My queue, roughly:
- MySQL → Postgres: test it on a real dataset, promote to
tested. - Bulk-copy for Mongo → Postgres: the Postgres side already supports
COPY; the Mongo source needs a streaming cursor mode for that to be a win. Expect this in 0.5.x. - Schema drift detection: re-running a migration against a target whose schema has drifted from the last-known plan. Right now you just get a confusing FK error; I'd like it to be a clean report.
- Per-table concurrency controls: today it's
--table-workers Nglobally. I want per-table overrides so you can pincustomersto 1 worker andordersto 4.
Thanks
To everyone who filed issues against the v0.2/v0.3 release line — especially the ones who took the time to include xferdb migrate --history output and actual row counts. That feedback is what shaped 0.4.0.
If you try the MongoDB path and hit something weird, the GitHub repo has issues enabled. Gitea mirrors everything if you prefer to file there.
— Billy