How to Plan a 3TB-Scale Database Migration
公開日 2026-08-13 · 更新日 2026-08-13 · 最終検証日 2026-08-13
結論
A plan for a 3TB-scale migration is built in this order: (1) measure the actual size per object, (2) decide between a full stop / initial load + CDC catch-up / dual writes based on the acceptable downtime, (3) run a trial with representative data to measure duration, (4) decide on a consistency-verification method, and (5) define the rollback conditions and the point of no return. Don't start with a rule-of-thumb duration figure — the only numbers that belong in the plan are measured values from a trial run in your own environment.
この文書の適用条件
| 対象製品 | Database migration (MySQL family / SQL Server family, etc.) |
|---|---|
| 確認バージョン | The sizing SQL is verified on MySQL 5.7 / 8.0 and SQL Server 2012 and later. Check the migration tool version for each environment |
| 適用環境 | On-premises, AWS, Azure (and migrations between them) |
| 必要権限 | Sizing requires metadata read privileges (`VIEW DATABASE STATE` on SQL Server). Retrieving a dump requires `SELECT` on the target schema |
| 実行影響 | No impact from the sizing SQL. Retrieving a dump and a trial load put load on both the source and target |
| 再起動 | Not required for measurement. Whether cutover involves downtime depends on the chosen method |
| 最終検証日 | 2026-08-13 |
そのまま実行できるコマンド
- 対象
- MySQL 5.7 / 8.0, Aurora MySQL 2.x / 3.x
- 権限
- Read access to `information_schema`
- 変更作業
- None (read-only)
- Production実行
- Possible
-- 対象: MySQL 5.7 / 8.0、Aurora MySQL 2.x / 3.x
-- 権限: information_schema の参照権限
-- 変更作業: なし(参照のみ)
-- Production 実行: 可能
SELECT
TABLE_SCHEMA,
TABLE_NAME,
ENGINE,
TABLE_ROWS AS estimated_rows,
ROUND(DATA_LENGTH / 1024 / 1024 / 1024, 2) AS data_gb,
ROUND(INDEX_LENGTH / 1024 / 1024 / 1024, 2) AS index_gb,
ROUND(DATA_FREE / 1024 / 1024 / 1024, 2) AS free_gb,
ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024 / 1024, 2) AS total_gb
FROM information_schema.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
ORDER BY (DATA_LENGTH + INDEX_LENGTH) DESC;
-- スキーマ単位の合計(全体像の把握用)
SELECT
TABLE_SCHEMA,
COUNT(*) AS table_count,
ROUND(SUM(DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024 / 1024, 2) AS total_gb
FROM information_schema.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
GROUP BY TABLE_SCHEMA
ORDER BY total_gb DESC;`TABLE_ROWS` is an estimate on InnoDB. Re-measure with `COUNT(*)` only for tables where you need the exact row count. A table with a high `index_gb` ratio is a good candidate for the "rebuild the index after loading" strategy described later.
- 対象
- SQL Server 2012 and later, Amazon RDS for SQL Server, Azure SQL Managed Instance
- 権限
- `VIEW DATABASE STATE`
- 変更作業
- None (read-only)
- Production実行
- Possible
-- 対象: SQL Server 2012 以降(RDS / Azure SQL Managed Instance を含む)
-- 権限: VIEW DATABASE STATE
-- 変更作業: なし(参照のみ)
-- Production 実行: 可能
SELECT
SCHEMA_NAME(t.schema_id) AS schema_name,
t.name AS table_name,
SUM(CASE WHEN ps.index_id IN (0, 1) THEN ps.row_count ELSE 0 END) AS row_count,
SUM(ps.reserved_page_count) * 8.0 / 1024 / 1024 AS reserved_gb,
SUM(ps.used_page_count) * 8.0 / 1024 / 1024 AS used_gb
FROM sys.dm_db_partition_stats AS ps
INNER JOIN sys.tables AS t
ON t.object_id = ps.object_id
GROUP BY t.schema_id, t.name
ORDER BY SUM(ps.reserved_page_count) DESC;This converts to GB assuming an 8KB page size. `reserved_gb` is reserved space and `used_gb` is actual usage. A table with a large gap between the two includes fragmentation or deleted space, so its size after migration can end up smaller than on the source (confirm this with a trial load rather than an estimate, too).
- 対象
- MySQL 5.7 / 8.0, Aurora MySQL (mysqldump / mydumper)
- 権限
- `SELECT` on the target schema
- 変更作業
- None (the source is read-only), but it adds read load
- Production実行
- Carry out during a window that can tolerate the load
# 対象: MySQL 5.7 / 8.0、Aurora MySQL(mysqldump / mydumper)
# 権限: 対象スキーマへの SELECT
# 変更作業: なし(移行元は参照のみ)。ただし読み取り負荷がかかる
# Production 実行: 負荷を許容できる時間帯で実施すること
# 1) 単一テーブルで所要時間を実測する(--no-tablespaces は MySQL 8.0 で
# PROCESS 権限を要求されるのを避けるため)
time mysqldump \
--single-transaction \
--quick \
--no-tablespaces \
--host example-rds-endpoint \
--user sample_user \
--password \
SampleDB sample_table \
| gzip > /var/tmp/sample_table.sql.gz
# 2) 並列ダンプで実測する場合(資格情報はコマンドラインに書かず設定ファイルで渡す)
mydumper \
--defaults-file /etc/mysql/sample-migration.cnf \
--database SampleDB \
--threads 8 \
--rows 500000 \
--compress \
--outputdir /var/tmp/dump-sample
# 3) 出力サイズを確認し、テーブルサイズとの比率を記録する
du -sh /var/tmp/dump-sample"How many minutes this table took and how many GB the output was" is the only number obtained here that belongs in the plan. When extrapolating a single table's result to the whole, the ratio changes for tables with different row width, index count, or BLOB presence, so measure several representative tables with different characteristics. `--single-transaction` is an option for getting a consistent snapshot; concurrent DDL breaks that consistency.
- 対象
- MySQL 5.7 / 8.0, Aurora MySQL (both source and target)
- 権限
- `SELECT` on the target table
- 変更作業
- None (read-only), but reading every row adds load
- Production実行
- Carry out during downtime or a low-load window
-- 対象: MySQL 5.7 / 8.0、Aurora MySQL(移行元・移行先の双方で同じSQLを実行する)
-- 権限: 対象テーブルへの SELECT
-- 変更作業: なし(参照のみ)。ただし全行を読むため I/O 負荷がかかる
-- Production 実行: 停止中または低負荷時間帯に実施すること
-- 1) 行数の一致を確認する(最も軽い検証。ここが合わなければ先へ進まない)
SELECT 'sample_table' AS table_name, COUNT(*) AS row_count
FROM SampleDB.sample_table;
-- 2) 主要列の値までを含めた突き合わせ(同一エンジン間の比較に使う)
SELECT
COUNT(*) AS row_count,
SUM(CRC32(CONCAT_WS('|', id, name, DATE_FORMAT(updated_at, '%Y-%m-%d %H:%i:%s')))) AS crc_sum
FROM SampleDB.sample_table;
-- 3) テーブル単位のチェックサム(全行を読む。大きな表では時間がかかる)
CHECKSUM TABLE SampleDB.sample_table EXTENDED;Use step 2 when the source and target run the same engine family. Since it won't match unless NULL handling, floating-point representation, and date formatting line up, it explicitly normalizes values with `CONCAT_WS`. In a cross-engine migration, the representation of values itself changes, so focus mainly on row-count matching and application-level verification (comparing the results of representative screens and batch jobs).
- 対象
- MySQL 5.7 / 8.0, Aurora MySQL
- 権限
- Read access to `information_schema`
- 変更作業
- None (read-only)
- Production実行
- Possible
-- 対象: MySQL 5.7 / 8.0、Aurora MySQL
-- 権限: information_schema の参照権限
-- 変更作業: なし(参照のみ)
-- Production 実行: 可能
-- 1) 直近に定義が変わったテーブル(凍結期間中に変更が入っていないかの確認に使う)
SELECT TABLE_SCHEMA, TABLE_NAME, CREATE_TIME, UPDATE_TIME
FROM information_schema.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
ORDER BY CREATE_TIME DESC;
-- 2) 二次インデックスの一覧(ロード後に作り直す対象の洗い出し)
SELECT
TABLE_SCHEMA,
TABLE_NAME,
INDEX_NAME,
GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS index_columns,
MAX(NON_UNIQUE) AS non_unique
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
AND INDEX_NAME <> 'PRIMARY'
GROUP BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
ORDER BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME;The result of step 2 is itself the work list if you choose the strategy of dropping secondary indexes before loading and rebuilding them afterward. Generating and storing the rebuild DDL in advance means you don't have to recall the definitions on cutover day. Since a unique index (`non_unique = 0`) also serves to detect duplicate data, arrange a separate guarantee against duplicates if you drop one.
結果の読み方
| 列 | 意味 | 確認するポイント |
|---|---|---|
| TABLE_SCHEMA / TABLE_NAME | Target schema and table | Use this list to make the line between in-scope and out-of-scope explicit |
| estimated_rows | Estimated row count (an approximation based on statistics on InnoDB) | Re-pull the exact figure with `COUNT(*)` if it will serve as a verification baseline |
| data_gb | Size of the data portion | The main contributor to transfer volume. Pick trial-run targets in descending order |
| index_gb | Size of the index portion | The higher this ratio, the bigger the payoff from rebuilding indexes after loading |
| free_gb / the gap between reserved and used | Space reserved but unused | Often resolved on the target. The required capacity on the target can end up smaller than the reserved amount on the source |
| total_gb | Total of data plus indexes | Sum per schema to pin down the overall "actual size" |
| index_columns (secondary index list) | Columns making up the index | Becomes the work list for what to recreate after loading |
| UPDATE_TIME | When the table definition or data was last updated (engine-dependent) | Use this to check for unexpected changes during the freeze period |
こういう状況で使います
- Asked "how many hours will it take" but unable to give a number backed by evidence
- A schedule has been set without ever rehearsing the migration
- Discussion of the cutover method has started before the acceptable downtime has been decided
- The initial load finished, but catching up on the difference never finishes
- Making the target's capacity equal to the source's ran out of space during the load
- It's undecided who decides to roll back, and when
考えられる原因(可能性の高い順)
01
Planning around a total figure like "3TB" without measuring actual size
Even with the same total, the work involved is completely different between a single giant table and thousands of mid-sized tables. Pin down the per-object size and count first.
02
Estimating duration from a generic rule of thumb
Transfer speed varies by orders of magnitude depending on network bandwidth, storage throughput, parallelism, presence of indexes, and compression. Applying someone else's figures to your own environment makes the plan itself unworkable.
03
The acceptable downtime was not decided first
The cutover method should be decided based on acceptable downtime. If a long outage is acceptable, a full stop is the simplest and most reliable. Choosing a method without working backward from this leads to rework later.
04
Loading with indexes still attached
A bulk load with secondary indexes kept in place triggers an index update on every row insert. Since duration differs from rebuilding them all at once after loading, compare both with a trial run.
05
Reaching cutover day without deciding the verification method
"It looks like it's working" is not verification. How far to go with row counts, value comparison, and application-level checks needs to be agreed on in advance.
06
No point of no return has been defined
Once writes to the new environment begin, a simple rollback is no longer possible. Spell out in the procedure exactly which point, once passed, leaves only forward progress as an option.
確認手順
- 1
Measure actual size and count per object
参照のみRun the sizing SQL above to pin down the number of tables, the size of the largest table, and what share of the total the top 10 represent.
- 2
Finalize the acceptable downtime with stakeholders
参照のみBefore any technical discussion, decide how many hours the business can tolerate being down. Nothing else can be decided until this is set.
- 3
Run trials on representative tables with different characteristics
中Measure duration on several tables selected for different traits: large, wide rows, containing BLOBs, many indexes, and so on.
- 4
Confirm the target's actual capacity with a trial load
中It will not equal the source's usage. It can shrink from resolved fragmentation or grow from storage structure differences.
- 5
Measure the rate at which differences accumulate
参照のみIf using a CDC approach, the volume of changes generated during the initial load determines how long catch-up takes. Measure the daily row-change count.
- 6
Run the rehearsal at least once with a production-equivalent procedure
中Gaps in the runbook are found only in a rehearsal. This is also where you pin down the measured duration.
対応方法
すぐに実施できる低リスクの対応
Finish the inventory of what is in scope
参照のみSeparate tables to migrate, tables not to migrate, and tables to delete before migration. Deciding what not to migrate alone often shrinks the total significantly.
Narrow cutover method candidates from the acceptable downtime
参照のみA full stop if a long outage is tolerable, initial load + CDC catch-up if only a short one is, and dual writes if none is.
事前検討が必要な変更
Measure duration with a trial run
中Build up the total from measured values on representative tables. Put only measured values, not rules of thumb, into the plan.
Decide how to handle indexes and constraints
中Compare rebuilding secondary indexes and some constraints after loading through measurement, and decide whether to adopt it. Generate and store the rebuild DDL in advance.
Document the pass criteria for verification
参照のみDecide upfront "what needs to be true to call the cutover complete" — row-count matches, checksum matches on key tables, matching results on representative screens/batch jobs, and so on.
Create a freeze and notification plan
参照のみDecide when to stop schema changes, batch jobs, and data entry, who to notify, and how to handle exception requests.
再起動・サービス影響を伴う変更
Carry out the initial load
高This puts read load on the source and write load on the target. Build margin into the measured duration for unexpected re-runs.
Set up change data synchronization (CDC) to catch up
高If the rate of new differences exceeds the catch-up rate, it will never catch up. Continuously check whether the lag is shrinking.
専門家のレビューが必要な作業
Carry out the cutover
専門家レビュー必須Execute in the order: stop writes → confirm the difference has fully caught up → verify → switch the connection target → strengthen monitoring, with the decision-maker and pass criteria for each stage set beforehand.
Decide on a rollback
専門家レビュー必須Decide in advance how far to roll back if pass criteria are not met (just the connection target, or the data too). Once past the point of no return, only moving forward remains an option.
!注意事項
- This article does not give a generic duration figure, because it varies by orders of magnitude with network, storage performance, parallelism, and index configuration. The only numbers that belong in the plan are measured values from a trial run in your own environment.
- When extrapolating a single table's measured value to the whole, the ratio changes for tables that differ in row width, index count, or BLOB presence. Measure several representative tables with different characteristics.
- The target's required capacity will not equal the source's usage. It can shrink from resolved fragmentation or grow from a different storage structure. Confirm this with a trial load.
- When loading with secondary indexes dropped, also dropping a unique index can let duplicate data in. Arrange a separate guarantee against duplicates.
- `CHECKSUM TABLE` and computing a checksum over all rows reads the entire table. Do not run this against a large table while it is live in production.
- A migration plan with no defined point of no return leaves you unable to make a call when something goes wrong. Decide the conditions, procedure, and decision-maker for a rollback before the schedule.
- A schema change during the freeze period puts already-loaded data out of sync with the target's definition. Document what is in scope for the freeze and how exceptions are handled.
バージョン・環境による違い
これで解決しない場合に確認すること
Check the network path and its actual effective bandwidth
Measure the actual effective bandwidth of the path between source and target (dedicated line, VPN, internet) by actually transferring data. Catalog figures are not enough to plan with.
Check the target's temporary and log space
During loading, the transaction log and temp space grow much larger than normal. Sizing based only on data space will fall short.
Check the impact on the source while loading
Read load can slow down the service side. Consider options such as reducing parallelism, splitting across time windows, or reading from a replica.
Check how character set, collation, and time zone are handled
If value representation changes across the migration, verification will show mismatches. Decide the standard before migrating.
Include migrating privileges, connection info, and monitoring settings in the plan
Moving the data doesn't automatically move users, privileges, monitoring, or backup settings. Include time for this work in the schedule too.
Decide on stakeholder notification and a point of contact
Notify people in advance of the freeze period, cutover time, scope of impact, and who to contact. Most of the confusion on the day can be prevented here.
この文書の根拠と限界
一般的な技術説明
A general approach to migration planning based on the public specifications of MySQL's `information_schema.TABLES` and SQL Server's `sys.dm_db_partition_stats`, and the documented options of `mysqldump` / `mydumper`. Figures such as duration, transfer speed, and capacity ratios are intentionally omitted because they depend heavily on the environment. It does not include any specific customer's migration case.
よくある質問
How long does a 3TB migration take?
This article does not give a figure. Duration varies by orders of magnitude with the network's actual effective bandwidth, source and target storage performance, parallelism, index configuration, and compression. Run a trial on representative tables and build up the total from those measured values — applying someone else's figures to your own environment makes the plan unworkable.
Which cutover method should I choose?
Decide based on acceptable downtime. A full stop is simplest and easiest to verify if a long outage is acceptable; initial load + CDC catch-up if only a short one is; dual writes if almost none is. The latter two trade downtime for extra work — operating CDC or modifying the application.
Can this be run in production?
The sizing and definition-inventory SQL is read-only and can be run in production. A trial dump and checksum computation add read load, so pick an appropriate time window. Treat the initial load and cutover as planned operations carried out under a pre-agreed procedure.
Should indexes be built before or after loading?
This can't be decided in the abstract — compare both with a trial run. Rebuilding secondary indexes after loading is often favorable, but the rebuild itself takes time, so judge by the total time. If you drop a unique index, arrange a separate guarantee against duplicate data.
How much verification is enough?
The standard is whatever you agreed in advance counts as "complete." At minimum, check that row counts match across every target table, then compare values on key tables, and confirm that results from representative screens/batch jobs match the pre-migration state. A full value comparison takes time, so define the scope and target before running it.
Until when is a rollback possible?
Until writes to the new environment begin. After that, data that exists only in the new environment is created, so simply reverting to the old environment means losing it. Document this boundary as the point of no return in the procedure, and decide who makes the call to cross it.
この文書がカバーする質問
- How to estimate the time a large-scale database migration will take
- How to choose a cutover method for a database migration
- How to verify data consistency after a migration
- How to estimate the target's storage capacity
リスク表示の意味
- 参照のみデータと設定を変更しません。
- 低影響は限定的ですが、権限と負荷の確認が必要です。
- 中性能・ロック・コストに影響する可能性があります。
- 高障害・データ損失・復旧作業が発生する可能性があります。
- 専門家レビュー必須本番適用前に別途レビューが必須です。
GIIPの対応範囲
The migration plan itself can be built up through repeated measurement and review. What's easy to miss in practice is the weeks after cutover. GIIP saves key metrics collected before cutover (representative query execution time, error rate, storage usage trend) as a baseline, and tracks the same metrics side by side after cutover to catch any drift. Only items that show a difference are isolated for action, with day-to-day comparison and initial investigation handled by an AI agent, while the decision to roll back or change the configuration is made by a person.
執筆・技術検証
GIIP プロダクション運用チーム
大規模Webサービス、SQL Server、Oracle、AWS、Azureの設計・移行・運用に約30年従事。x12largeクラスのAWS RDS for SQL Server環境12セット、約12万テーブルのOracle環境、約3TBのTiDBからAurora MySQLへの移行を経験。現在も複数のクラウドデータベースと約30のWebサービスを、AIエージェントと人間の専門家が継続的に監視・運用しています。
Checklist for Migrating from TiDB to Aurora MySQL
TiDB is MySQL-compatible, but not identical. This organizes the differences in primary keys, ID generation, transactions, statistics, and capacity into a pre-migration checklist with verification SQL.
aurora-mysqlWhy AWS DMS Reports Error 1032 and How to Investigate It
Error 1032 in DMS CDC means the corresponding row doesn't exist on the target. This organizes how to read the control tables, how to check for primary key mismatches, and the available task-setting options.
aurora-mysqlWhat to Check When Migrating from utf8mb3 to utf8mb4 in Aurora MySQL
The first thing that breaks when converting to utf8mb4 is index key length. This organizes the SQL for finding targets, how to choose a collation, the impact of conversion, and where the settings live.
awsThe Difference Between One Large RDS Instance and Several Smaller Instances
A document organizing the technical differences between "one large instance" and "several smaller instances" for RDS sizing, and the CloudWatch metrics you should measure before deciding.
関連サービス
Request a plan review for a 3TB-class migration
同じ確認を複数の環境で継続する必要がある場合は、運用体制ごと相談できます。
Request a plan review for a 3TB-class migration