MySQL Online Schema Change on Production with gh-ost: Testing on AWS Aurora Database

09/11/2026

10

Key Takeaways

    • A native ALTER TABLE that changed a column from INT to BIGINT on 10 million rows took ~3 minutes 33 seconds and blocked application transactions, with database CPU at ~90%.
    • A MySQL online schema change with gh-ost kept sysbench transactions running normally, with only a brief block during the cut-over rename.
    • With nice-ratio = 0.1, gh-ost finished in ~10 minutes at ~70% database CPU. With nice-ratio = 20, CPU stayed below 30% but the run took 1 hour 46 minutes.
    • gh-ost needs a PRIMARY KEY or UNIQUE KEY and binlog enabled on the Aurora cluster, which requires a DB instance reboot.
    • On Aurora, gh-ost 1.1.10 needed the -skip-metadata-lock-check option to run.
    • Run gh-ost from an EC2 instance in the same zone as the writer, inside screen, and do a dry run without -execute first.

In the previous article, we tested gh-ost on an on-premises MySQL server. In this article, let’s look at the results of running gh-ost on AWS Aurora MySQL, and see whether a MySQL online schema change with gh-ost is just as safe on a managed cloud database.

Aurora MySQL gh-ost

Setting Up the Test Environment

No.Running ServiceOSInstance TypeVPC Zone
1MySQL 8.0.42 (binlog and performance schema enabled)MySQL Aurora 3.10.3db.t4g.mediumZone A
2Sysbench 1.1.0Amazon Linux 2023t3.mediumZone A
3gh-ost 1.1.10Amazon Linux 2023t3.mediumZone A

Test Scenario

As in the scenario we ran on the on-premises server, we will change the data type of column k in table sbtest1 from INT to BIGINT while the application is still running queries (simulated with sysbench using 20 parallel threads), across 2 + 1 cases:

#1: Connect to the database and execute the query:

ALTER TABLE sbtest1 MODIFY COLUMN k BIGINT NOT NULL;

#2: Use gh-ost to ALTER TABLE, with chunk size = 5000 and nice-ratio = 0.1

#3: Optimize gh-ost to avoid increasing database CPU, with chunk size = 5000 and nice-ratio = 20

Case 1: Connect to the Database and Execute the Query

Step 1: Run sysbench to simulate application queries

sysbench --db-driver=mysql --mysql-user=admin --mysql-password='YOUR_PASSWORD' --mysql-db=sysbench --tables=1 --table-size=10000000 --auto-inc=off --threads=20 --time=0 --rate=2 --rand-type=pareto --report-interval=1 --mysql-host="YOUR_AURORA_CLUSTER_ENDPOINT"   oltp_read_write prepare 2>&1 | tee sysbench.log

sysbench --db-driver=mysql --mysql-user=admin --mysql-password='YOUR_PASSWORD' --mysql-db=sysbench --tables=1 --table-size=10000000 --auto-inc=off --threads=20 --time=0 --rate=2 --rand-type=pareto --report-interval=1 --mysql-host="YOUR_AURORA_CLUSTER_ENDPOINT"   oltp_read_write run 2>&1 | tee sysbench.log
WARNING: Both event and time limits are disabled, running an endless test
sysbench 1.1.0-3ceba0b (using bundled LuaJIT 2.1.0-beta3)

Running the test with following options:
Number of threads: 20
Target transaction rate: 2/sec
Report intermediate results every 1 second(s)
Initializing random number generator from current time

Initializing worker threads...

Threads started!

[ 1s ] thds: 20 tps: 0.00 qps: 5.98 (r/w/o: 4.99/0.00/1.00) lat (ms,95%): 0.00 err/s: 0.00 reconn/s: 0.00
[ 1s ] queue length: 0, concurrency: 1
[ 2s ] thds: 20 tps: 1.00 qps: 14.01 (r/w/o: 9.01/4.00/1.00) lat (ms,95%): 45.79 err/s: 0.00 reconn/s: 0.00
[ 2s ] queue length: 0, concurrency: 0
[ 3s ] thds: 20 tps: 4.00 qps: 80.01 (r/w/o: 56.00/16.00/8.00) lat (ms,95%): 37.56 err/s: 0.00 reconn/s: 0.00
[ 3s ] queue length: 0, concurrency: 0
...

Step 2: Run ALTER TABLE with native SQL

Database changed
MySQL [sysbench]> ALTER TABLE sbtest1 MODIFY COLUMN k BIGINT NOT NULL;

Query OK, 10000000 rows affected (3 min 33.372 sec)
Records: 10000000  Duplicates: 0  Warnings: 0

Result: It took ~3 minutes 33 seconds for the query above to finish.

Observations

The number of queries executed by sysbench still dropped to 0, just like last time, after the ALTER TABLE query was executed.

[ 66s ] thds: 20 tps: 0.00 qps: 16.00 (r/w/o: 15.00/0.00/1.00) lat (ms,95%): 0.00 err/s: 0.00 reconn/s: 0.00
[ 66s ] queue length: 0, concurrency: 14
[ 67s ] thds: 20 tps: 0.00 qps: 0.00 (r/w/o: 0.00/0.00/0.00) lat (ms,95%): 0.00 err/s: 0.00 reconn/s: 0.00
[ 67s ] queue length: 0, concurrency: 14
[ 68s ] thds: 20 tps: 0.00 qps: 0.00 (r/w/o: 0.00/0.00/0.00) lat (ms,95%): 0.00 err/s: 0.00 reconn/s: 0.00
[ 68s ] queue length: 0, concurrency: 14
[ 69s ] thds: 20 tps: 0.00 qps: 89.00 (r/w/o: 83.00/0.00/6.00) lat (ms,95%): 0.00 err/s: 0.00 reconn/s: 0.00
[ 69s ] queue length: 1, concurrency: 20
[ 70s ] thds: 20 tps: 0.00 qps: 1.00 (r/w/o: 1.00/0.00/0.00) lat (ms,95%): 0.00 err/s: 0.00 reconn/s: 0.00
[ 70s ] queue length: 2, concurrency: 20
...
[ 143s ] thds: 20 tps: 24.99 qps: 748.84 (r/w/o: 556.88/121.97/69.99) lat (ms,95%): 39206.03 err/s: 20.00 reconn/s: 0.00
[ 143s ] queue length: 39, concurrency: 20
[ 144s ] thds: 20 tps: 61.01 qps: 971.19 (r/w/o: 647.13/222.04/102.02) lat (ms,95%): 26861.48 err/s: 0.00 reconn/s: 0.00
[ 144s ] queue length: 0, concurrency: 0
[ 145s ] thds: 20 tps: 2.00 qps: 40.00 (r/w/o: 28.00/8.00/4.00) lat (ms,95%): 12.98 err/s: 0.00 reconn/s: 0.00
[ 145s ] queue length: 0, concurrency: 0
[ 146s ] thds: 20 tps: 2.00 qps: 40.00 (r/w/o: 28.00/8.00/4.00) lat (ms,95%): 12.75 err/s: 0.00 reconn/s: 0.00
[ 146s ] queue length: 0, concurrency: 0

As in the on-premises test, from the moment ALTER TABLE was executed, the transactions created by sysbench could not be executed because they had to wait for the lock to be released.

  • Database CPU Utilization increased to ~90%
  • The number of blocked transactions increased

Case 2: Use gh-ost to ALTER TABLE, with Chunk Size = 5000 and Nice-Ratio = 0.1

Step 1: Run sysbench to simulate application queries

sysbench --db-driver=mysql --mysql-user=admin --mysql-password='YOUR_PASSWORD' --mysql-db=sysbench --tables=1 --table-size=10000000 --auto-inc=off --threads=20 --time=0 --rate=2 --rand-type=pareto --report-interval=1 --mysql-host="YOUR_AURORA_CLUSTER_ENDPOINT"   oltp_read_write prepare 2>&1 | tee sysbench.log

sysbench --db-driver=mysql --mysql-user=admin --mysql-password='YOUR_PASSWORD' --mysql-db=sysbench --tables=1 --table-size=10000000 --auto-inc=off --threads=20 --time=0 --rate=2 --rand-type=pareto --report-interval=1 --mysql-host="YOUR_AURORA_CLUSTER_ENDPOINT"   oltp_read_write run 2>&1 | tee sysbench.log
WARNING: Both event and time limits are disabled, running an endless test
sysbench 1.1.0-3ceba0b (using bundled LuaJIT 2.1.0-beta3)

Running the test with following options:
Number of threads: 20
Target transaction rate: 2/sec
Report intermediate results every 1 second(s)
Initializing random number generator from current time

Initializing worker threads...

Threads started!

[ 1s ] thds: 20 tps: 0.00 qps: 5.98 (r/w/o: 4.99/0.00/1.00) lat (ms,95%): 0.00 err/s: 0.00 reconn/s: 0.00
[ 1s ] queue length: 0, concurrency: 1
[ 2s ] thds: 20 tps: 1.00 qps: 14.01 (r/w/o: 9.01/4.00/1.00) lat (ms,95%): 45.79 err/s: 0.00 reconn/s: 0.00
[ 2s ] queue length: 0, concurrency: 0
[ 3s ] thds: 20 tps: 4.00 qps: 80.01 (r/w/o: 56.00/16.00/8.00) lat (ms,95%): 37.56 err/s: 0.00 reconn/s: 0.00
[ 3s ] queue length: 0, concurrency: 0
[ 4s ] thds: 20 tps: 0.00 qps: 0.00 (r/w/o: 0.00/0.00/0.00) lat (ms,95%): 0.00 err/s: 0.00 reconn/s: 0.00
[ 4s ] queue length: 0, concurrency: 0
[ 5s ] thds: 20 tps: 1.00 qps: 21.00 (r/w/o: 14.00/4.00/3.00) lat (ms,95%): 33.72 err/s: 0.00 reconn/s: 0.00
[ 5s ] queue length: 0, concurrency: 1
[ 6s ] thds: 20 tps: 3.00 qps: 59.00 (r/w/o: 42.00/12.00/5.00) lat (ms,95%): 40.37 err/s: 0.00 reconn/s: 0.00
[ 6s ] queue length: 0, concurrency: 0
...

Step 2: Run ALTER TABLE with gh-ost, chunk size = 5000 and nice-ratio = 0.1

./gh-ost -allow-on-master -assume-rbr -exact-rowcount \
  -critical-load Threads_running=400 -critical-load-hibernate-seconds 60 \
  -database sysbench -max-load Threads_running=100 -nice-ratio 0.1 \
  -chunk-size 5000 -ask-pass -table sbtest1 -user admin \
  -host YOUR_AURORA_CLUSTER_ENDPOINT \
  -postpone-cut-over-flag-file /home/ec2-user/gh-ost-sentinel \
  -throttle-additional-flag-file /home/ec2-user/gh-ost-throttle_ghost.sbtest1 \
  -alter 'MODIFY COLUMN k BIGINT NOT NULL' \
  -verbose -execute -skip-metadata-lock-check 2>&1 | tee gh-ost.log
...
Copy: 0/9873575 0.0%; Applied: 0; Backlog: 1/1000; Time: 0s(total), 0s(copy); streamer: mysql-bin-changelog.000002:2609576; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: N/A
2026-08-19 08:11:53 INFO Copy: 0/9873575 0.0%; Applied: 0; Backlog: 1/1000; Time: 0s(total), 0s(copy); streamer: mysql-bin-changelog.000002:2609576; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: N/A []
Copy: 0/9873575 0.0%; Applied: 0; Backlog: 25/1000; Time: 1s(total), 1s(copy); streamer: mysql-bin-changelog.000002:2622867; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: N/A
2026-08-19 08:11:54 INFO Copy: 0/9873575 0.0%; Applied: 0; Backlog: 25/1000; Time: 1s(total), 1s(copy); streamer: mysql-bin-changelog.000002:2622867; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: N/A []
Copy: 19998/9873580 0.2%; Applied: 20; Backlog: 1/1000; Time: 2s(total), 2s(copy); streamer: mysql-bin-changelog.000002:6527307; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: 16m27s
2026-08-19 08:11:55 INFO Copy: 19998/9873580 0.2%; Applied: 20; Backlog: 1/1000; Time: 2s(total), 2s(copy); streamer: mysql-bin-changelog.000002:6527307; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: 16m27s []
Copy: 44998/9873582 0.5%; Applied: 28; Backlog: 1/1000; Time: 3s(total), 3s(copy); streamer: mysql-bin-changelog.000002:11409647; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: 6m33s
2026-08-19 08:11:56 INFO Copy: 44998/9873582 0.5%; Applied: 28; Backlog: 1/1000; Time: 3s(total), 3s(copy); streamer: mysql-bin-changelog.000002:11409647; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: 6m33s []
Copy: 64998/9873586 0.7%; Applied: 52; Backlog: 2/1000; Time: 4s(total), 4s(copy); streamer: mysql-bin-changelog.000002:15327928; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: 8m10s
2026-08-19 08:11:57 INFO Copy: 64998/9873586 0.7%; Applied: 52; Backlog: 2/1000; Time: 4s(total), 4s(copy); streamer: mysql-bin-changelog.000002:15327928; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: 8m10s []
Copy: 89997/9873587 0.9%; Applied: 60; Backlog: 1/1000; Time: 5s(total), 5s(copy); streamer: mysql-bin-changelog.000002:20210878; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: 6m31s
2026-08-19 08:11:58 INFO Copy: 89997/9873587 0.9%; Applied: 60; Backlog: 1/1000; Time: 5s(total), 5s(copy); streamer: mysql-bin-changelog.000002:20210878; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: 6m31s []
Copy: 109995/9873589 1.1%; Applied: 72; Backlog: 2/1000; Time: 6s(total), 6s(copy); streamer: mysql-bin-changelog.000002:24121921; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: 8m8s
2026-08-19 08:11:59 INFO Copy: 109995/9873589 1.1%; Applied: 72; Backlog: 2/1000; Time: 6s(total), 6s(copy); streamer: mysql-bin-changelog.000002:24121921; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: 8m8s []
Copy: 129995/9873589 1.3%; Applied: 72; Backlog: 0/1000; Time: 7s(total), 7s(copy); streamer: mysql-bin-changelog.000002:28014402; Lag: 0.20s, HeartbeatLag: 0.34s, State: migrating; ETA: 8m7s
2026-08-19 08:12:00 INFO Copy: 129995/9873589 1.3%; Applied: 72; Backlog: 0/1000; Time: 7s(total), 7s(copy); streamer: mysql-bin-changelog.000002:28014402; Lag: 0.20s, HeartbeatLag: 0.34s, State: migrating; ETA: 8m7s []
Copy: 134995/9873590 1.4%; Applied: 76; Backlog: 2/1000; Time: 8s(total), 8s(copy); streamer: mysql-bin-changelog.000002:29006018; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: 32m27s

Observations

  • gh-ost still creates the temporary table _sbtest1_gho with the modified schema, while replicating data from the binlog into the temporary table.
  • The migration status is continuously updated on the console, along with the estimated completion time.
Copy: 9119862/10000155 91.2%; Applied: 4500; Backlog: 3/1000; Time: 9m51s(total), 9m51s(copy); streamer: mysql-bin-changelog.000015:32280777; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: 44s
2026-08-19 08:21:44 INFO Copy: 9119862/10000155 91.2%; Applied: 4500; Backlog: 3/1000; Time: 9m51s(total), 9m51s(copy); streamer: mysql-bin-changelog.000015:32280777; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: 44s []
Copy: 9144862/10000155 91.4%; Applied: 4512; Backlog: 5/1000; Time: 9m53s(total), 9m53s(copy); streamer: mysql-bin-changelog.000015:37174190; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: 57s
2026-08-19 08:21:46 INFO Copy: 9144862/10000155 91.4%; Applied: 4512; Backlog: 5/1000; Time: 9m53s(total), 9m53s(copy); streamer: mysql-bin-changelog.000015:37174190; Lag: 0.03s, HeartbeatLag: 0.04s, State: migrating; ETA: 57s []
  • Sysbench still creates transactions and executes them normally.
  • Database CPU utilization is ~70%.
  • Transactions are blocked during the cut-over because the table metadata is locked during the rename.

Case 3: Use gh-ost to ALTER TABLE, with Chunk Size = 5000 and Nice-Ratio = 20

With nice-ratio = 0.1 in test case #2, database CPU utilization was quite high at ~70%. The migration took ~10 minutes for a table with 10,000,000 records. Let’s adjust nice-ratio to increase the delay between each copy of a 5,000-record chunk and reduce the load on the database.

Step 1: Run sysbench to simulate application queries

sysbench --db-driver=mysql --mysql-user=admin --mysql-password='YOUR_PASSWORD' --mysql-db=sysbench --tables=1 --table-size=10000000 --auto-inc=off --threads=20 --time=0 --rate=2 --rand-type=pareto --report-interval=1 --mysql-host="YOUR_AURORA_CLUSTER_ENDPOINT"   oltp_read_write prepare 2>&1 | tee sysbench.log

sysbench --db-driver=mysql --mysql-user=admin --mysql-password='YOUR_PASSWORD' --mysql-db=sysbench --tables=1 --table-size=10000000 --auto-inc=off --threads=20 --time=0 --rate=2 --rand-type=pareto --report-interval=1 --mysql-host="YOUR_AURORA_CLUSTER_ENDPOINT"   oltp_read_write run 2>&1 | tee sysbench.log
WARNING: Both event and time limits are disabled, running an endless test
sysbench 1.1.0-3ceba0b (using bundled LuaJIT 2.1.0-beta3)

Running the test with following options:
Number of threads: 20
Target transaction rate: 2/sec
Report intermediate results every 1 second(s)
Initializing random number generator from current time

Initializing worker threads...

Threads started!

[ 1s ] thds: 20 tps: 0.00 qps: 5.98 (r/w/o: 4.99/0.00/1.00) lat (ms,95%): 0.00 err/s: 0.00 reconn/s: 0.00
[ 1s ] queue length: 0, concurrency: 1
[ 2s ] thds: 20 tps: 1.00 qps: 14.01 (r/w/o: 9.01/4.00/1.00) lat (ms,95%): 45.79 err/s: 0.00 reconn/s: 0.00
[ 2s ] queue length: 0, concurrency: 0
[ 3s ] thds: 20 tps: 4.00 qps: 80.01 (r/w/o: 56.00/16.00/8.00) lat (ms,95%): 37.56 err/s: 0.00 reconn/s: 0.00
[ 3s ] queue length: 0, concurrency: 0
[ 4s ] thds: 20 tps: 0.00 qps: 0.00 (r/w/o: 0.00/0.00/0.00) lat (ms,95%): 0.00 err/s: 0.00 reconn/s: 0.00
[ 4s ] queue length: 0, concurrency: 0
[ 5s ] thds: 20 tps: 1.00 qps: 21.00 (r/w/o: 14.00/4.00/3.00) lat (ms,95%): 33.72 err/s: 0.00 reconn/s: 0.00
[ 5s ] queue length: 0, concurrency: 1
[ 6s ] thds: 20 tps: 3.00 qps: 59.00 (r/w/o: 42.00/12.00/5.00) lat (ms,95%): 40.37 err/s: 0.00 reconn/s: 0.00
[ 6s ] queue length: 0, concurrency: 0
...

Step 2: Run ALTER TABLE with gh-ost, chunk size = 5000 and nice-ratio = 20

./gh-ost -allow-on-master -assume-rbr -exact-rowcount \
  -critical-load Threads_running=400 -critical-load-hibernate-seconds 60 \
  -database sysbench -max-load Threads_running=100 -nice-ratio 20 \
  -chunk-size 5000 -ask-pass -table sbtest1 -user admin \
  -host YOUR_AURORA_CLUSTER_ENDPOINT \
  -postpone-cut-over-flag-file /home/ec2-user/gh-ost-sentinel \
  -throttle-additional-flag-file /home/ec2-user/gh-ost-throttle_ghost.sbtest1 \
  -alter 'MODIFY COLUMN k BIGINT NOT NULL' \
  -verbose -execute -skip-metadata-lock-check 2>&1 | tee gh-ost.log
...
Copy: 0/9868941 0.0%; Applied: 0; Backlog: 1/1000; Time: 0s(total), 0s(copy); streamer: mysql-bin-changelog.000047:114696801; Lag: 0.05s, HeartbeatLag: 0.05s, State: migrating; ETA: N/A
2026-08-19 09:32:48 INFO Copy: 0/9868941 0.0%; Applied: 0; Backlog: 1/1000; Time: 0s(total), 0s(copy); streamer: mysql-bin-changelog.000047:114696801; Lag: 0.05s, HeartbeatLag: 0.05s, State: migrating; ETA: N/A []
Copy: 0/9868941 0.0%; Applied: 0; Backlog: 6/1000; Time: 1s(total), 1s(copy); streamer: mysql-bin-changelog.000047:114703065; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: N/A
2026-08-19 09:32:49 INFO Copy: 0/9868941 0.0%; Applied: 0; Backlog: 6/1000; Time: 1s(total), 1s(copy); streamer: mysql-bin-changelog.000047:114703065; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: N/A []
Copy: 5000/9868942 0.1%; Applied: 4; Backlog: 26/1000; Time: 2s(total), 2s(copy); streamer: mysql-bin-changelog.000047:115689557; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: N/A
2026-08-19 09:32:50 INFO Copy: 5000/9868942 0.1%; Applied: 4; Backlog: 26/1000; Time: 2s(total), 2s(copy); streamer: mysql-bin-changelog.000047:115689557; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: N/A []
Copy: 5000/9868942 0.1%; Applied: 4; Backlog: 36/1000; Time: 3s(total), 3s(copy); streamer: mysql-bin-changelog.000047:115694066; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: N/A
2026-08-19 09:32:51 INFO Copy: 5000/9868942 0.1%; Applied: 4; Backlog: 36/1000; Time: 3s(total), 3s(copy); streamer: mysql-bin-changelog.000047:115694066; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: N/A []
Copy: 5000/9868944 0.1%; Applied: 24; Backlog: 1/1000; Time: 4s(total), 4s(copy); streamer: mysql-bin-changelog.000047:115705521; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: N/A
2026-08-19 09:32:52 INFO Copy: 5000/9868944 0.1%; Applied: 24; Backlog: 1/1000; Time: 4s(total), 4s(copy); streamer: mysql-bin-changelog.000047:115705521; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: N/A []
Copy: 10000/9868944 0.1%; Applied: 24; Backlog: 19/1000; Time: 5s(total), 5s(copy); streamer: mysql-bin-changelog.000047:116688009; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: 32m51s
2026-08-19 09:32:53 INFO Copy: 10000/9868944 0.1%; Applied: 24; Backlog: 19/1000; Time: 5s(total), 5s(copy); streamer: mysql-bin-changelog.000047:116688009; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: 32m51s []
Copy: 10000/9868944 0.1%; Applied: 24; Backlog: 33/1000; Time: 6s(total), 6s(copy); streamer: mysql-bin-changelog.000047:116694274; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: 1h38m38s
2026-08-19 09:32:54 INFO Copy: 10000/9868944 0.1%; Applied: 24; Backlog: 33/1000; Time: 6s(total), 6s(copy); streamer: mysql-bin-changelog.000047:116694274; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: 1h38m38s []
Copy: 15000/9868946 0.2%; Applied: 40; Backlog: 12/1000; Time: 7s(total), 7s(copy); streamer: mysql-bin-changelog.000047:117683108; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: 32m50s
2026-08-19 09:32:55 INFO Copy: 15000/9868946 0.2%; Applied: 40; Backlog: 12/1000; Time: 7s(total), 7s(copy); streamer: mysql-bin-changelog.000047:117683108; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: 32m50s []
Copy: 15000/9868946 0.2%; Applied: 40; Backlog: 34/1000; Time: 8s(total), 8s(copy); streamer: mysql-bin-changelog.000047:117692884; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: 1h27m37s
2026-08-19 09:32:56 INFO Copy: 15000/9868946 0.2%; Applied: 40; Backlog: 34/1000; Time: 8s(total), 8s(copy); streamer: mysql-bin-changelog.000047:117692884; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: 1h27m37s []
Copy: 20000/9868950 0.2%; Applied: 68; Backlog: 7/1000; Time: 9s(total), 9s(copy); streamer: mysql-bin-changelog.000047:118683818; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: 32m49s
2026-08-19 09:32:57 INFO Copy: 20000/9868950 0.2%; Applied: 68; Backlog: 7/1000; Time: 9s(total), 9s(copy); streamer: mysql-bin-changelog.000047:118683818; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: 32m49s []
2026-08-19 09:32:57 INFO Exact number of rows via COUNT: 10000000

Observations

  • Database CPU Utilization remains stable at <30%.
  • The total run time was 1 hour 46 minutes before the cut-over could be performed.
Copy: 9988484/10001517 99.9%; Applied: 51732; Backlog: 1/1000; Time: 1h46m42s(total), 1h46m42s(copy); streamer: mysql-bin-changelog.000062:113275187; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: 8s
2026-08-19 11:19:30 INFO Copy: 9988484/10001517 99.9%; Applied: 51732; Backlog: 1/1000; Time: 1h46m42s(total), 1h46m42s(copy); streamer: mysql-bin-changelog.000062:113275187; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: 8s []
Copy: 9993483/10001517 99.9%; Applied: 51732; Backlog: 15/1000; Time: 1h46m43s(total), 1h46m43s(copy); streamer: mysql-bin-changelog.000062:114255726; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: 1s
2026-08-19 11:19:31 INFO Copy: 9993483/10001517 99.9%; Applied: 51732; Backlog: 15/1000; Time: 1h46m43s(total), 1h46m43s(copy); streamer: mysql-bin-changelog.000062:114255726; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: 1s []
Copy: 9993483/10001517 99.9%; Applied: 51732; Backlog: 29/1000; Time: 1h46m44s(total), 1h46m44s(copy); streamer: mysql-bin-changelog.000062:114261997; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: 5s
2026-08-19 11:19:32 INFO Copy: 9993483/10001517 99.9%; Applied: 51732; Backlog: 29/1000; Time: 1h46m44s(total), 1h46m44s(copy); streamer: mysql-bin-changelog.000062:114261997; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: 5s []
Copy: 9993483/10001517 99.9%; Applied: 51732; Backlog: 47/1000; Time: 1h46m45s(total), 1h46m45s(copy); streamer: mysql-bin-changelog.000062:114270020; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: 5s
2026-08-19 11:19:33 INFO Copy: 9993483/10001517 99.9%; Applied: 51732; Backlog: 47/1000; Time: 1h46m45s(total), 1h46m45s(copy); streamer: mysql-bin-changelog.000062:114270020; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: 5s []
Copy: 9998483/10001517 100.0%; Applied: 51748; Backlog: 9/1000; Time: 1h46m46s(total), 1h46m46s(copy); streamer: mysql-bin-changelog.000062:115257757; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: due
2026-08-19 11:19:34 INFO Copy: 9998483/10001517 100.0%; Applied: 51748; Backlog: 9/1000; Time: 1h46m46s(total), 1h46m46s(copy); streamer: mysql-bin-changelog.000062:115257757; Lag: 0.04s, HeartbeatLag: 0.05s, State: migrating; ETA: due []
Copy: 9998483/10001517 100.0%; Applied: 51748; Backlog: 19/1000; Time: 1h46m47s(total), 1h46m47s(copy); streamer: mysql-bin-changelog.000062:115262266; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: 1s
2026-08-19 11:19:35 INFO Copy: 9998483/10001517 100.0%; Applied: 51748; Backlog: 19/1000; Time: 1h46m47s(total), 1h46m47s(copy); streamer: mysql-bin-changelog.000062:115262266; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: 1s []
2026-08-19 11:19:36 INFO Row copy complete
Copy: 9998483/9998483 100.0%; Applied: 51760; Backlog: 0/1000; Time: 1h46m48s(total), 1h46m47s(copy); streamer: mysql-bin-changelog.000062:115275273; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: due
2026-08-19 11:19:36 INFO Copy: 9998483/9998483 100.0%; Applied: 51760; Backlog: 0/1000; Time: 1h46m48s(total), 1h46m47s(copy); streamer: mysql-bin-changelog.000062:115275273; Lag: 0.04s, HeartbeatLag: 0.04s, State: migrating; ETA: due []
Copy: 9998483/9998483 100.0%; Applied: 51760; Backlog: 1/1000; Time: 1h46m48s(total), 1h46m47s(copy); streamer: mysql-bin-changelog.000062:115276044; Lag: 0.04s, HeartbeatLag: 0.05s, State: postponing cut-over; ETA: due
  • Blocked transactions appeared, caused by the application side, not by gh-ost.
Online MySQL Schema Migration

Read more:

Conclusion: Is gh-ost Safe for MySQL Online Schema Change on Aurora?

  • gh-ost works well with Aurora MySQL for MySQL online schema change.
  • gh-ost can only be used to migrate the schema of tables that have a PRIMARY KEY or UNIQUE KEY.
  • Binlog must be enabled for the cluster, which requires rebooting the DB instance.
  • The -skip-metadata-lock-check option must be added for gh-ost to be able to run.
  • Reduce chunk-size, combined with increasing nice-ratio, to reduce database CPU utilization when running gh-ost, at the cost of a longer run time.
  • Run gh-ost directly on an EC2 instance in the same zone as the database writer instance, combined with the screen command, since gh-ost can take a long time to run.
  • Remove the -execute option to do a dry run before the real execution.

If your team runs critical workloads on Aurora and needs support planning database changes, migrations or upgrades, explore SupremeTech’s Cloud Infrastructure & DevOps services or contact our team.

Frequently Asked Questions About MySQL Online Schema Change

What is a MySQL online schema change?

A MySQL online schema change is a way to alter a table’s structure while the application keeps reading and writing to it. Tools such as gh-ost create a new table with the target schema, copy data in small chunks, keep it in sync with ongoing changes, and swap the tables in a short final step called cut-over.

Why does ALTER TABLE block transactions when changing a column type?

According to the MySQL 8.0 online DDL documentation, changing a column’s data type rebuilds the table and does not permit concurrent DML. On a large table, application writes have to wait until the rebuild finishes, which is why sysbench transactions dropped to zero in our first test case.

Does gh-ost work with Amazon Aurora MySQL?

Yes. In our test, gh-ost 1.1.10 completed the change on Aurora MySQL 3.10.3 while application queries continued. It ran against the writer with -allow-on-master and -assume-rbr, and the gh-ost documentation for Amazon RDS explains that Aurora readers share storage with the writer, so gh-ost treats them as the primary.

What does nice-ratio do in gh-ost?

nice-ratio adds sleep time after each copied chunk, relative to how long the copy took. The gh-ost interactive commands documentation explains that a ratio of 1 makes gh-ost sleep as long as it copied, roughly doubling run time. Higher values reduce database load but make the migration take longer.

What are the requirements for using gh-ost?

The table must have a PRIMARY KEY or UNIQUE KEY that gh-ost can use to iterate rows, and row-based binary logging must be available. The gh-ost requirements and limitations also note that foreign keys and triggers are not supported. On Aurora, changing the binlog setting in the cluster parameter group requires a reboot of the writer instance.

How can I test gh-ost before running it on production?

Run the same command without the -execute option. gh-ost then performs a dry run that checks the configuration and the ALTER statement without changing data. Use -postpone-cut-over-flag-file in the real run so you can choose when the final table swap happens.

Meet the author

Phuoc Pham

Phuoc Pham

Infrastructure Team Manager

As a results-driven Infrastructure Team Manager, I lead my team in building resilient, high-availability systems that support rapid organizational growth. By combining strategic technical vision with collaborative leadership to empower high-performing engineering teams, we deliver secure, future-proof infrastructure.

Solid circle

Sign me up
for the latest news!

Customize software background

Want to customize a software for your business?

Meet with us! Schedule a meeting with us!