Prompt
How do I migrate my app database from MySQL to PostgreSQL without downtime?
Latest observation
Migrating from MySQL to PostgreSQL with no downtime is usually done as a phased, dual-write or change-data-capture (CDC) cutover, not as a single “big bang” dump-and-restore.
Here’s the practical approach.
High-level strategy
- Prepare PostgreSQL schema
- Backfill existing MySQL data into PostgreSQL
- Continuously replicate MySQL changes to PostgreSQL
- Validate data consistency
- Switch app reads/writes with a controlled cutover
- Keep MySQL as rollback for a short period
- Decommission MySQL after confidence is high
The key is that your app keeps running while data is being copied and kept in sync.
Option 1: Recommended approach — CDC-based migration
Use a tool that streams changes from MySQL binlog into PostgreSQL.
Common choices:
- AWS DMS
- Debezium + Kafka + sink/consumer
- pg_chameleon
- Striim / Fivetran / Airbyte depending on your stack
How it works
- Initial full load copies all MySQL data to PostgreSQL
- Then ongoing inserts/updates/deletes are captured from MySQL’s binary log
- PostgreSQL stays nearly in sync until cutover
Pros
- Minimal downtime
- Safer for large databases
- Keeps source system live during migration
Cons
- More moving parts
- Requires careful handling of schema differences and edge cases
Option 2: Dual-write in the application
Your app writes to both MySQL and PostgreSQL for a period.
Typical flow
- Reads still come from MySQL
- Writes go to both databases
- Backfill PostgreSQL with historical data
- Verify consistency
- Flip reads to PostgreSQL
- Eventually stop writing MySQL
Pros
- Full app control
- No dependency on CDC tooling
Cons
- Hard to get right
- Error handling and retries become tricky
- Risks divergence if one write fails
- More application complexity
This is usually only worth it if you already have strong distributed write patterns and observability.
Option 3: Short maintenance window
This is not “no downtime,” but sometimes it’s “near-zero downtime”:
- Stop writes briefly
- Dump delta
- Import into PostgreSQL
- Switch app
This can work for smaller systems, but it’s not true zero-downtime.
Recommended migration plan
1) Assess compatibility gaps
MySQL and PostgreSQL differ in SQL dialect, data types, constraints, and behavior.
Common issues
AUTO_INCREMENT→SERIALorIDENTITYTINYINT(1)often maps toBOOLEANDATETIME/ timezone handlingTEXT/BLOBvsBYTEAENUMdifferencesGROUP BYandNULLsemantics- Case sensitivity and collation differences
ON DUPLICATE KEY UPDATE→ON CONFLICT ... DO UPDATE- MySQL-specific functions:
IFNULL,NOW(),FIND_IN_SET, etc.
Action
- Audit schema and queries
- Identify all MySQL-specific SQL in code
- Check indexes, constraints, and transaction assumptions
2) Prepare PostgreSQL schema
Create the target schema in PostgreSQL first.
Tips
- Use the right data types, not just a mechanical conversion
- Add primary keys and foreign keys
- Create indexes needed by your app
- Re-check sequences/identity behavior
- Ensure collation/encoding match your expectations
You may want to use a conversion tool as a starting point, but manually review it.
3) Set up initial backfill
Load all existing MySQL data into PostgreSQL.
Common methods
- Export/import with
mysqldump+ transform +psql - Use migration tools that do full load automatically
- Use ETL pipelines
Important
- Do the backfill while the app stays live
- Record a point-in-time watermark so CDC can continue from there
4) Stream live changes
After the initial load, replicate new MySQL changes.
Requirements
- MySQL binlog enabled
- Row-based replication preferred
- Binlog retention long enough for the entire migration window
- Network connectivity to the replication tool
Validate
- Check that inserts, updates, deletes are appearing in PostgreSQL
- Monitor replication lag
- Test failure recovery
5) Run parallel validation
Before cutover, compare MySQL and PostgreSQL data.
Compare:
- Row counts per table
- Checksums/hashes on key tables
- Sample records
- Business-critical aggregates
- Query results for important endpoints
Watch for:
- Data type conversion issues
- Truncated strings
- Timezone offsets
- Numeric precision differences
- Missing or duplicated rows
6) Cutover with minimal or zero downtime
When PostgreSQL is caught up:
Safe cutover pattern
- Put app into “read-only” or “drain writes” mode if possible
- Wait for CDC lag to hit zero
- Switch app connection strings to PostgreSQL
- Keep MySQL intact for rollback
- Monitor carefully
If you cannot pause writes, a feature flag or routing layer can help:
- route reads to PostgreSQL
- keep writes dual-written until confidence is high
- then make PostgreSQL the source of truth
7) Post-cutover monitoring
For at least hours to days:
- Track error rates
- Compare business metrics
- Watch slow queries and lock behavior
- Verify background jobs, cron tasks, and analytics
- Check sequence values and next IDs
- Ensure connection pool settings work well with PostgreSQL
Key pitfalls
1) SQL incompatibilities
Queries that worked in MySQL may fail or behave differently in PostgreSQL.
Examples
LIMIT offset, count→LIMIT count OFFSET offsetINSERT ... ON DUPLICATE KEY UPDATE→INSERT ... ON CONFLICT- Backticks
`col`→ double quotes"col"or no quotes if possible - Implicit type coercions that MySQL allows but PostgreSQL rejects
2) Transaction behavior
PostgreSQL is stricter about locks and transaction isolation in some cases.
- Long transactions can cause bloat
- Deadlocks may appear differently
- Retry logic may be needed
3) Auto-increment/sequence sync
Make sure PostgreSQL sequences are set to the correct next value after backfill, or inserts may collide.
4) Case sensitivity
MySQL often behaves case-insensitively depending on collation; PostgreSQL is more literal unless configured otherwise.
5) Replication lag and binlog retention
If the CDC tool falls behind and MySQL purges binlogs, you can lose sync.
A simple migration architecture
A common setup looks like this:
- MySQL = source of truth during migration
- CDC tool = reads MySQL binlog
- PostgreSQL = target database
- App = optionally reads from MySQL first, then PostgreSQL
- Validation jobs = compare data continuously
If you want truly no downtime
Use this sequence:
- Build PostgreSQL schema
- Start full load into PostgreSQL
- Enable CDC from MySQL binlog
- Keep app running on MySQL
- Run automated data validation
- Switch reads to PostgreSQL
- Switch writes to PostgreSQL
- Keep MySQL as fallback until stable
- Remove MySQL after a safe period
Tooling recommendations
For managed cloud environments
- AWS DMS if on AWS
- Cloud SQL / database migration services if on GCP/Azure equivalents
For self-managed setups
- Debezium if you already use Kafka or need flexible streaming
- pg_chameleon for MySQL-to-Postgres replication
- Airbyte/Fivetran for simpler ETL-style workflows, though not always ideal for strict zero-downtime cutovers
What I’d recommend in practice
For most production apps:
- Use CDC replication
- Do an initial full load + live sync
- Run parallel validation
- Cut over with a feature flag
- Keep a rollback plan
- Leave MySQL available temporarily
If you want, I can give you:
- a step-by-step migration checklist,
- a tool recommendation based on your stack, or
- a detailed cutover plan for a specific app architecture (e.g. Rails, Django, Node, Spring, AWS, Kubernetes).