gh-ost: Triggerless Schema Migrations via Binlog Streaming
AI generated
InnoDB
SQL
MySQL / Binlog-Based Migration
gh-ost: Triggerless Schema Migrations
Schema changes via binlog streaming instead of triggers

Trigger-based tools like pt-online-schema-change solve many problems but add synchronous overhead to every single write. gh-ost, built at GitHub, takes a fundamentally different approach: it reads changes asynchronously from the MySQL binary log, exactly like a regular replica, and applies them to a ghost table. On tables with very high, constant write load, that difference is what separates a migration nobody notices from one that visibly slows down production.

13 min read Binlog Streaming Throttling Atomic Cutover

1. From triggers to binlogs: the core idea behind gh-ost

gh-ost, short for GitHub's Online Schema Change, was built at GitHub after pt-online-schema-change repeatedly hit limits on very large, write-heavy tables. Instead of installing synchronous triggers on the original table like pt-osc does, gh-ost behaves toward the MySQL server exactly like a regular replica: it establishes a normal replication connection and reads the full stream of change events from the binary log through it.

gh-ost then applies these change events asynchronously to a separate ghost table, in parallel with the actual copy of existing data. The key advantage: the original application barely notices any of this, because no extra triggers run on the original table and every regular write stays exactly as fast as before the migration.

2. How binlog streaming works as change data capture

With row-based replication, that is binlog_format=ROW, MySQL writes a complete before and after image for every changed row into the binary log. gh-ost connects through the normal replication protocol, registers as a replica with its own server ID, and receives the same event stream a regular replica server would. From that stream, gh-ost filters out only the events relevant to the table being migrated.

Each filtered event is internally translated into a matching write on the ghost table: an insert event becomes an insert, an update event becomes an update, a delete event becomes a delete. Because this process runs fully asynchronously relative to the actual application write, it adds no extra latency to the application itself, at most a small delay before a change also lands in the ghost table.


# gh-ost requires binlog_format=ROW as a prerequisite
mysql -e "SHOW VARIABLES LIKE 'binlog_format';"

# Dry run against the target table, no change to production data
gh-ost --host=db-primary.internal --database=magento --table=catalog_product_entity --alter="ADD INDEX idx_sku_type (sku, type_id)" --dry-run

3. Architecture comparison to pt-online-schema-change

The core difference between gh-ost and pt-online-schema-change is where the extra work happens. With pt-osc it happens synchronously in the same transaction as every application write, because a trigger has to run before the actual commit. With gh-ost it happens fully decoupled, in a separate process that reads and processes the binary log at some point after the commit.

In practice, that decoupling means a table with several thousand writes per second, such as a session or event log table in a high-traffic Magento shop, feels no extra write overhead per request with gh-ost. The tradeoff is more complexity in the tool itself and a usually small delay before changes from the binary log actually land in the ghost table.

4. Prerequisites: binlog format, GTID and privileges

The hard requirement is binlog_format=ROW, statement or mixed based replication is not sufficient since gh-ost relies on the full before and after row images. The database user gh-ost connects with needs at least the REPLICATION SLAVE and REPLICATION CLIENT privileges, plus the usual DML and DDL privileges on the target database.

Before any production run, a test run via --test-on-replica is strongly recommended: gh-ost runs the entire migration on a dedicated replica, stops replication there and automatically compares the results between the original and ghost table for consistency, with zero impact on the production primary.

5. The practical workflow: test run, dry run, execute

A sensible sequence starts with a --test-on-replica run against a dedicated replica, followed by a --dry-run against the primary that performs every check without any real migration. Only then follows the actual run with --execute, ideally outside peak hours, though gh-ost's throttling generally makes it safe to run during normal load as well.

During execution, gh-ost continuously reports status information: rows copied, estimated time remaining, current throughput and current replication lag. This output can also be queried interactively through a Unix socket interface, which is valuable especially for migrations without direct terminal access, for example when started from a CI job.


# Test run on a dedicated replica, no impact on the primary server
gh-ost --host=db-replica.internal --database=magento --table=sales_order_grid --alter="ADD COLUMN priority TINYINT DEFAULT 0" --test-on-replica

# Production run with an interactive control channel over a Unix socket
gh-ost --host=db-primary.internal --database=magento --table=sales_order_grid --alter="ADD COLUMN priority TINYINT DEFAULT 0" --serve-socket-file=/tmp/gh-ost.sales_order_grid.sock --execute

6. Throttling and the interactive control channel

gh-ost continuously monitors replication lag on every configured replica throughout the migration. If lag exceeds the threshold defined with --max-lag-millis, gh-ost automatically throttles the copy process until the replicas have caught up again. A deliberate slowdown factor can also be set with --nice-ratio, which permanently takes regular application load into account even without an actual lag breach.

Through the same Unix socket interface, a running migration can be throttled manually at any time with the throttle command, or sped back up with no-throttle, without restarting the process. That is especially valuable during unexpected load spikes during business hours, for example a sudden campaign driving an unusually high order volume.

7. The cutover moment in detail

At the end of the migration, gh-ost offers two cutover strategies. The default, --cut-over=atomic, uses a special locking mechanism that ensures no writes to the old table are lost during the switch by briefly blocking all writes while the last pending binlog events are still being applied. That block typically lasts a fraction of a second up to a few seconds.

The alternative --cut-over=two-step works more conservatively with two separate steps and tolerates certain connection interruptions during the switch, at the cost of taking somewhat longer overall. For most Magento migrations, the atomic cutover is the preferred choice as long as the network connection between the gh-ost process and the database server stays stable.

8. Limits and pitfalls

The most important limitation of gh-ost concerns foreign keys: the tool does not support tables with foreign key relationships at all by default and aborts once it detects them, unless --discard-foreign-keys is explicitly set, which removes the foreign keys entirely on the new table. On heavily interlinked Magento tables with many referencing foreign keys, that is a significant practical difference from pt-online-schema-change, which at least handles foreign keys in a configurable way.

Other limitations: a table needs a primary key or at least one unique, non-nullable index as the basis for the copy process, and on rows that change extremely frequently during the migration, remaining runtime can stretch out because the same rows have to be reapplied from the binary log multiple times. Triggers on the original table that themselves have DDL-relevant side effects are also incompatible with the ghost approach.


-- Check for existing foreign key references before migrating
SELECT CONSTRAINT_NAME, TABLE_NAME, REFERENCED_TABLE_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_NAME = 'catalog_product_entity';

9. When gh-ost is the right choice over pt-osc or native DDL

gh-ost shows its strength mainly on tables with very high, constant write load, such as quote_item, sales_order_grid or custom log tables in high-traffic Magento shops, where the trigger overhead of pt-online-schema-change would actually be noticeable. When fine-grained throttling control and a particularly safe, tested cutover mechanism matter most, gh-ost is often the preferred choice.

For tables with foreign keys where fully removing the references is not an option, pt-online-schema-change often remains the more practical solution. And for any operation with native ALGORITHM=INSTANT or INPLACE support that is unlikely to hit row log capacity limits, MySQL's own built-in online DDL stays the simplest and fastest option of all.

Criterion gh-ost pt-online-schema-change
Change capture mechanism Asynchronous binlog streaming Synchronous triggers
Write overhead on the original table Practically none Extra trigger cost per write
Foreign key support Not supported, only bypassed by removal Configurable strategies available
Throttling Fine-grained via lag and interactive channel Via lag threshold and pause file
Testability before production Dedicated --test-on-replica mode Dry run only, no real replica test
Binlog format requirement binlog_format=ROW mandatory No special requirement

Mironsoft

Database performance, index tuning, and Magento DB optimization

A Magento shop suffering from slow database queries?

We analyze MySQL databases for performance bottlenecks, optimize indexes and queries with purpose, and set up backup and replication strategies that actually work when it counts.

Performance Audit

Systematically investigate the slow query log and explain plans for bottlenecks.

Index Optimization

Build indexes with purpose for the shop's actual query load.

Backup Strategy

Set up reliable backup and restore processes for production Magento databases.

10. Summary

gh-ost: Triggerless Migrations

Binlog instead of triggers

gh-ost behaves like a replica and reads changes asynchronously from the binary log instead of via synchronous triggers.

No write overhead

The original table stays unaffected while writing, ideal for very write-heavy Magento tables.

Foreign key gap

Foreign keys are not supported, only fully bypassed via --discard-foreign-keys.

Safe workflow

Test run on a replica, dry run, fine-grained throttling and an atomic cutover minimize risk.

11. FAQ: gh-ost: Triggerless Migrations

1Why does gh-ost not support foreign keys?
The binlog-based approach tracks changes independently of referential integrity, correctly carrying foreign key relationships over to the ghost table would be considerably more complex than with the trigger-based approach. The maintainers deliberately chose not to take on that complexity.
2What happens if the network connection drops during a migration?
gh-ost aborts the running migration, the ghost table remains and needs manual cleanup. The original table stays completely untouched, restarting the migration afterward is possible at any time.
3Can gh-ost run without an available replica?
Yes, with the --allow-on-master flag gh-ost reads the binary log directly from the primary instead of a replica, which adds extra read load on the production server and therefore only makes sense when no replica is available.
4How does gh-ost ensure the ghost table is really consistent with the original?
The --test-on-replica mode automatically compares checksums and row counts between the original and ghost table on the replica after the test migration completes, before any production run is even considered.
5Does gh-ost support MariaDB too?
Generally yes, as long as the target instance provides MySQL-compatible row-based replication, though the project's primary development and testing focus is MySQL and Percona Server, so details can vary by MariaDB version.
6How long does the cutover moment typically take?
With atomic cutover, usually well under a second up to a few seconds, depending on the number of pending binlog events and the current load on the target table at the moment of the switch.
7Can a running gh-ost migration be safely aborted at any time?
Yes, via the Unix socket interface with the panic command, or simply by terminating the process. Either way the original table stays unchanged, only the ghost table and its helper structures need manual cleanup afterward.
8Does gh-ost add extra load on the database server during the copy phase?
Yes, in the form of reads for the copy process and writes for applying binlog events to the ghost table, but without the synchronous trigger overhead that would occur on every single application write.
9What does an escalating replication lag warning during migration mean?
It indicates a monitored replica has exceeded the configured lag threshold and gh-ost has automatically throttled the copy process to avoid putting further strain on replication until the replica catches back up.
10Does gh-ost need extra server resources beyond MySQL itself?
gh-ost runs as a standalone external process, ideally on a separate host with network access to the database server, so the migration's CPU and memory load does not add to the database server itself.