XferDB v1.0.0 — First stable release, and bulk-copy for every target adapter
DevOpsSeptember 23, 2026•8 min read

XferDB v1.0.0 — First stable release, and bulk-copy for every target adapter

XferDB v1.0.0 — First stable release, and bulk-copy for every target adapter

I just shipped v1.0.0 of XferDB. This is the first release I'm calling stable — every adapter, the full engine (pause/resume/checkpointing), schema analysis, the REST API, the CLI, and the Web UI are complete, and the whole thing has been through a real compatibility matrix run against actual databases, not just go test.

The concrete new thing since v0.5.0: --bulk-copy, XferDB's fast bulk-load write path, now works on every target adapter, not just Postgres.

XferDB's project list — a real Postgres-to-Postgres migration, a SQLite round-trip test, and one intentionally-failed MongoDB run kept around for testing error handlingXferDB's project list — a real Postgres-to-Postgres migration, a SQLite round-trip test, and one intentionally-failed MongoDB run kept around for testing error handling

Download

xferdb (CLI + API server)

PlatformBinary
Linux x86_64xferdb-linux-amd64
macOS Apple Siliconxferdb-darwin-arm64
macOS Intelxferdb-darwin-amd64

xferdb-web (Web UI, optional)

PlatformBinary
Linux x86_64xferdb-web-linux-amd64
macOS Apple Siliconxferdb-web-darwin-arm64
macOS Intelxferdb-web-darwin-amd64

xferdb-web is a separate process — it holds no migration state or database credentials, it just serves the UI and reverse-proxies /api/* to xferdb server. Run it if you want the browser UI; the CLI works standalone without it.

If you'd rather build from source:

git clone https://github.com/bchan77/XferDB.git
cd XferDB && git checkout v1.0.0 && go build -o xferdb ./cmd/xferdb

Release notes and CHANGELOG: XferDB v1.0.0 on GitHub.

What's in v1.0.0

Bulk copy for MySQL and SQLite targets

--bulk-copy previously only did anything on a Postgres target (the COPY protocol). It's now implemented for MySQL (LOAD DATA LOCAL INFILE via a registered reader, using REPLACE INTO TABLE so a non-empty target degrades gracefully instead of erroring) and SQLite (batched INSERT OR REPLACE plus PRAGMA synchronous = OFF for the duration of the write). All three still require --truncate or --recreate-schema — bulk copy trades the upsert guarantee for speed, and that tradeoff only makes sense once you already know the target is empty.

It's a real speedup, not a marginal one. At 1M rows per table in our own compatibility runs: mysql267-pg17-bulk finished in 131s versus 742s for the same pair on the default write path — 5.7x. pg16-pg17-bulk was 199s versus 358s — 1.8x.

The migration options screen: schema mode, table/segment workers, and the bulk-copy checkbox — disabled here because "Keep existing target schema/data" is selected instead of truncate or drop & recreateThe migration options screen: schema mode, table/segment workers, and the bulk-copy checkbox — disabled here because "Keep existing target schema/data" is selected instead of truncate or drop & recreate

Adapter test coverage

adapters/postgres, adapters/mysql, and adapters/sqlite had zero unit tests before this release. Building bulk-copy for all three surfaced two real bugs that nothing local would have caught otherwise: Postgres's COPY encoder was hex-mangling non-bytea []byte values (a MySQL-sourced DECIMAL scanned as []byte came out as bytea garbage instead of the number), and SQLite rejects changing PRAGMA synchronous inside an active transaction. Both are fixed, and both now have regression tests.

CI fixes

Two bugs in our own release-gating pipeline, found while shipping this: the Gitea Action that auto-approves a PR once the compatibility matrix passes was sending an invalid API value and silently failing every time — nothing ever checked the HTTP response — and the Jenkins pipeline's flat 240-minute timeout wasn't enough for a full run at L size (1M rows/table). Both fixed.

Showing our work: 45/45 passing

XferDB v0.5.0 shipped with 27/27 passing across three source engines into Postgres. That number is now 45/45: MongoDB 6.0 through 8.3 (21 cases), MySQL 8.0/8.4/9.7/26.7 (17 cases), and Postgres 14 through 17 in every direction (7 cases) — each pairing now includes at least one dedicated bulk-copy case, not just the default write path. Same setup as before: real Postgres/MySQL/MongoDB in Kubernetes, a fresh namespace and databases per case, verified value-for-value on a control table.

Two things I'm not claiming, on purpose:

  • SQLite → PostgreSQL is implemented and unit-tested, but SQLite isn't deployable in the matrix the way a client/server database is, so this path has no automated end-to-end coverage yet.
  • PostgreSQL → YugabyteDB works — I tested 2025.2.5.2 by hand — but the automated matrix case (pinned to an older 2024.2.11.0-b36) is currently disabled because it hangs during execution. That's a real bug I haven't root-caused yet, not a "should be fine" shrug.

"Tested" on the table in the README means exactly what's in that matrix, automated, on every push. Where it isn't, I'm saying so.

That's the compatibility matrix; here's what a real migration looks like end to end. This is one of my own runs — 1,003,462 rows across 17 tables, including a synthetic 1M-row orders_1m table:

A completed migration: 1,003,462 / 1,003,462 rows (100%), 17 tables, all greenA completed migration: 1,003,462 / 1,003,462 rows (100%), 17 tables, all green

The web UI records a stats snapshot every 5 seconds while a migration runs — rows transferred, rate, phase, table count, CPU and memory — so you're not just watching a spinner:

Stats history: rate climbing from 0 to over 2,700 rows/sec as the migration ramps up, sampled every 5 secondsStats history: rate climbing from 0 to over 2,700 rows/sec as the migration ramps up, sampled every 5 seconds

Thanks

If you're running MySQL or SQLite as a target and try --bulk-copy for the first time, I'd like to hear how it goes — especially anything outside the versions in the matrix.

— Billy

← Back to Home