giip
SES Proposal
データベース移行移行サイジングロールバックストレージ承認

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

そのまま実行できるコマンド

Measure actual per-object size on the MySQL family参照のみ
対象
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.

Measure actual per-object size on SQL Server参照のみ
対象
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).

Take a trial dump of a representative table and measure duration and output size
対象
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.

Verify consistency after migration
対象
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).

Check the freeze scope for the migration (inventory of DDL 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_NAMETarget schema and tableUse this list to make the line between in-scope and out-of-scope explicit
estimated_rowsEstimated 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_gbSize of the data portionThe main contributor to transfer volume. Pick trial-run targets in descending order
index_gbSize of the index portionThe higher this ratio, the bigger the payoff from rebuilding indexes after loading
free_gb / the gap between reserved and usedSpace reserved but unusedOften resolved on the target. The required capacity on the target can end up smaller than the reserved amount on the source
total_gbTotal of data plus indexesSum per schema to pin down the overall "actual size"
index_columns (secondary index list)Columns making up the indexBecomes the work list for what to recreate after loading
UPDATE_TIMEWhen 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

考えられる原因(可能性の高い順)

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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. 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. 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. 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. 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. 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. 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.

バージョン・環境による違い

Cutover method 1: full stopStop the service, run the dump/load/verification, then resume. The simplest and easiest to verify, but the duration directly becomes the downtime. Most reliable when a long outage is acceptable.
Cutover method 2: initial load + CDC catch-upLoad the initial data while live, catch up on the difference via CDC, then cut over with a short stop. Downtime can be shortened, but it adds separate work — configuring, monitoring, and error-handling CDC. If new changes outpace catch-up, it never finishes.
Cutover method 3: dual writesThe application writes to both old and new systems, runs them in parallel for a while, then cuts over. Downtime can be nearly eliminated, but it requires application changes, and designing how to handle inconsistency between the two systems is the hardest part.
Engine differences in the sizing SQLMySQL's `TABLE_ROWS` and `DATA_FREE` and SQL Server's `reserved_page_count` and `used_page_count` each mean something different. Don't compare them directly across engines.

これで解決しない場合に確認すること

  • 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エージェントと人間の専門家が継続的に監視・運用しています。

関連するナレッジ

関連サービス

Request a plan review for a 3TB-class migration

同じ確認を複数の環境で継続する必要がある場合は、運用体制ごと相談できます。

Request a plan review for a 3TB-class migration

ナレッジベース一覧へ