0) Problem Restatement
TikTok asked: a team proposes migrating an existing MySQL-backed service to MongoDB. How would you:
- decide whether the migration is justified,
- compare the data models and consistency needs,
- execute the migration safely, and
- prove the claimed benefits?
1) Step 1: Understand the Motivation
Ask "what problem are we solving?". Common reasons:
- "Schema changes are painful" (the data is naturally nested or varies per record).
- "We need to scale writes beyond one MySQL primary."
- "Our queries always load a whole object with 5 joins."
2) Step 2: Compare Models Against Real Access Patterns
| Aspect | MySQL (relational) | MongoDB (document) |
|---|---|---|
| Data shape | Normalized tables, joins | Nested documents (an object and its children together) |
| Transactions | Mature multi-row ACID | Multi-document transactions exist, but best to design single-document updates |
| Schema | Enforced, migrations needed | Flexible (validation optional) |
| Scaling writes | Vertical, or manual/Vitess sharding | Built-in sharding by shard key |
| Queries | Rich joins, ad-hoc SQL, reporting | Great for "get this whole object", weaker for cross-entity joins/analytics |
List the top 10 queries by volume and latency, and model them as documents. If most become single-document reads, that's a good sign. If many need $lookup (joins), it's a warning.
3) Step 3: Execute Safely
Architecture Diagram
flowchart LR
APP["Service"] -->|"writes"| MY[("MySQL - source of truth")]
MY -->|"CDC (binlog)"| SYNC["Sync pipeline - transform to documents"]
SYNC --> MG[("MongoDB")]
BF["Backfill job"] --> MG
APP -->|"shadow reads - compare"| MG
CMP["Diff checker"] --> MY
CMP --> MG- Design documents and shard key from the access patterns (a high-cardinality key that spreads writes, e.g., user_id).
- Backfill historical data (a snapshot export → transform → load), then keep Mongo in sync with CDC from the MySQL binlog (e.g., Debezium → Kafka → a transformer). This avoids fragile dual writes in the app.
- Validate: row and document counts, checksums per entity, and a continuous diff checker on samples.
- Shadow reads: the service reads from both, returns MySQL results, and compares them with Mongo, logging mismatches and latency.
- Cutover gradually: move reads to Mongo for 1% → 10% → 100% of traffic. Then switch writes (a short write freeze or dual writes during the switch), and keep reverse sync (Mongo → MySQL) for a while as a rollback path.
- Decommission MySQL only after a stable period.
4) Step 4: Prove the Benefits
- Before starting: record baselines (p50/p99 latency of key queries, throughput limits, cost, developer time spent on schema changes, incidents).
- During shadow reads: compare latency and resource use under real traffic.
- After: the same metrics, plus cost. If the benefits don't show up, stop at the shadow phase. That's the value of a reversible plan.
5) Wrap-Up
Start from the concrete problem and rule out cheaper fixes, then compare the relational and document models against the service's real top queries and consistency needs (MongoDB fits aggregate-centric, horizontally scaling workloads, while relational fits joins, reporting and multi-entity transactions). If it's justified, migrate with a backfill plus CDC sync, validation, shadow reads, a gradual read-then-write cutover with a reverse-sync rollback path, and prove the gains against recorded baselines.