You’ve spent three hours debugging a custom migration script. Again. The MySQL schema changed because someone added an index last week, and now your PostgreSQL replica is silently out of sync. You fix it, push a hotfix, and vow to document better next time. But next time comes, and the same brittle script breaks in a new way.
This isn’t a skill issue. It’s a framing issue.
We’ve been told that database migration is a translation problem: take schema A, map it to schema B, run it once, and never look back. But in the real world of continuous development, that’s a fantasy. The moment your MySQL schema evolves — and it will — you’re back to manual catch-up.
Migration isn’t a one-shot lift. It’s a continuous diff. Treat it like a CI pipeline, not a wedding vow.
I saw this firsthand at a mid-sized SaaS company. We had a perfectly fine MySQL database powering our production app, but the analytics team wanted PostgreSQL for complex queries. The CTO signed off on a ‘quick migration’ using pgloader. Three weeks later, we had a working copy. Two weeks after that, a developer added a new column to a MySQL table, and nobody updated the PostgreSQL schema. The analytics dashboard started showing incomplete data. The bug took four days to trace back. The fix was another manual script.
The root cause wasn’t human error. It was the assumption that migration is a discrete event.
Enter a different mindset: schema synchronization as a continuous, idempotent state operation. The idea is simple — instead of translating a schema once, you constantly compare the actual MySQL schema with the desired PostgreSQL schema and apply only the differences. No black boxes. No one-off scripts. Just a repeatable, diff-driven pipeline.
This is exactly what schema_ferry does. It parses your MySQL schema using Rails Active Record, applies customizable rules (yes, you can exclude that one index), generates a PostgreSQL schema file, then uses ridgepole to compute the exact DDL needed to align the target database. Every time you run it, you’re not re-migrating — you’re syncing.
Let me be blunt: If you’re still writing custom Python scripts to keep two databases in sync, you’re wasting time and risking data integrity.
The conventional wisdom says: ‘Use pgloader for the initial load, then handle subsequent changes manually.’ That’s like building a house and then saying you’ll add a roof with duct tape. The initial migration is easy; the ongoing maintenance is where projects rot.
What makes schema_ferry different isn’t the technology — it’s the philosophy. It treats schema migration as a state-management problem, not a translation problem. The difference is profound:
- Translation assumes a one-way, final mapping. It’s fragile when either side drifts.
- State management assumes both sides are constantly evolving. The tool’s job is to continuously reconcile differences.
The real innovation isn’t moving data faster. It’s making the process boring, repeatable, and integratable into CI/CD.
When you shift from manual scripts to a continuous sync approach, something unexpected happens: you stop fearing schema changes. Developers feel free to add columns, drop indexes, or rename tables because they know the sync tool will catch it. The PostgreSQL replica stays accurate without a dedicated ‘migration manager’ role.
Here’s the twist that most engineers miss: the hardest part of schema migration isn’t the initial mapping. It’s the second, third, and hundredth change. Strong tools solve the first part. Great tools eliminate the friction of the rest.
So next time you find yourself writing a script to handle ‘just this one little schema drift,’ pause. Ask yourself: Am I solving a one-time problem or papering over a broken model? If it’s the latter, it’s time to stop translating and start syncing.
Stop treating your database architecture like a museum piece. Make it a living document that’s continuously kept in alignment.
FAQ
Q: How is schema_ferry different from pgloader?
A: pgloader is designed for one-shot migrations. schema_ferry treats migration as continuous synchronization — it can be run repeatedly, only applying the diff between MySQL and PostgreSQL schemas, so it handles ongoing development changes without manual scripts.
Q: Is schema_ferry production-ready for complex schemas?
A: It uses Rails Active Record for parsing and ridgepole for diffing, both battle-tested in production environments. However, you should always test with your specific schema first — particularly for custom types, triggers, or stored procedures that may need additional DSL rules.
Q: Doesn't this add unnecessary complexity? Why not just use one database system?
A: If you can standardize on one database, great. But heterogenous setups are common for good reasons — MySQL for operational workloads, PostgreSQL for analytics. The complexity lies in keeping them in sync, and schema_ferry reduces that complexity by making sync automated and repeatable.