What Is gh-ost? Inside GitHub's Triggerless Online Schema Migration Tool for MySQL

What Is gh-ost? Inside GitHub's Triggerless Online Schema Migration Tool for MySQL

The Operational Danger of Schema Migrations at Scale

In high-concurrency production environments, updating database schemas is one of the highest-risk operational tasks. As tables grow into hundreds of millions or billions of rows (spanning hundreds of gigabytes to multiple terabytes), traditional Data Definition Language (DDL) operations like ALTER TABLE introduce critical failure points:

  • Table Lockouts and Starvation: While modern MySQL InnoDB engines provide ONLINE DDL for select operations, many common schema mutations (such as column retyping, modifying primary keys, or rebuilding tables with secondary indexes) require exclusive table locks (ALGORITHM=COPY). This queues all inbound transactional reads and writes, quickly exhausting the connection pool and taking down application APIs.
  • The Trigger Penalty in Trigger-Based Tools: Legacy online migration tools like pt-online-schema-change create synchronous database triggers (AFTER INSERT, AFTER UPDATE, AFTER DELETE) on the original table to mirror live mutations to a temporary shadow table. Under heavy write workloads, these triggers amplify row-level locking, double disk I/O, and cause deadlocks and transaction timeouts.
  • Replication Lag Spikes: Trigger-generated mutations must be recorded in the MySQL binary log (binlog) and executed synchronously on read replicas. This introduces severe replication lag across replica clusters, breaking read-after-write consistency for web clients.
  • Unstoppable Background Operations: Once a native ALTER query or trigger-based migration begins, aborting or pausing the migration mid-stream can cause long transaction rollbacks, holding locks and stalling database traffic for hours.

To eliminate these structural risks across its production databases, GitHub engineered gh-ost (GitHub's Online Schema Transmogrifier)—a triggerless, binlog-streamed online schema migration tool for MySQL.

What Is gh-ost?

gh-ost is an open-source command-line tool written in Go that executes online, non-blocking table schema migrations on MySQL (versions 5.7, 8.0, and 8.4) and MariaDB.

Unlike predecessor utilities that inject triggers into active database engines, gh-ost operates completely triggerless. It acts as an external MySQL replication client, reading change events directly from the binary log (binlog) and replaying them asynchronously onto a dynamic ghost table.

Core Architectural Capabilities

  1. Triggerless Design: gh-ost creates zero triggers on the source table. The production database does not experience trigger-induced lock contention, write amplification, or concurrent lock competition.
  2. Binlog-Driven Asynchronous Streaming: gh-ost connects to MySQL using the replication protocol, acting as an external slave. It listens for row-based binlog events on the original table, translates them to match the new schema, and applies them to the ghost table.
  3. Multi-Target Execution Modes:Migrate on Replica: gh-ost connects directly to a read replica, extracts binlog events, copies chunks, and tests the migration without touching the production master.Migrate on Replica, Cutover on Master: gh-ost reads binlogs from a replica to reduce master CPU consumption, then switches over to the master only for the final atomic rename.Migrate Directly on Master: Operates directly on the production primary instance when replicas are unavailable.
  4. Migrate on Replica: gh-ost connects directly to a read replica, extracts binlog events, copies chunks, and tests the migration without touching the production master.
  5. Migrate on Replica, Cutover on Master: gh-ost reads binlogs from a replica to reduce master CPU consumption, then switches over to the master only for the final atomic rename.
  6. Migrate Directly on Master: Operates directly on the production primary instance when replicas are unavailable.
  7. Interactive Runtime Control & Throttling: Operators can communicate with a running gh-ost process via a Unix socket or TCP port to adjust chunk sizes, pause row copying, inspect status, or throttle execution in real time without restarting the process.
  8. Atomic, Crash-Safe Cutover: The final table swap uses an atomic, blocking multi-table rename query (RENAME TABLE original TO old, ghost TO original) that guarantees zero dropped queries or lost mutations.

The 4-Phase Migration Lifecycle

Phase 1: Ghost Table Initialization

gh-ost inspects the source table structure, validates character sets, unique keys, and primary keys, and creates an empty shadow table called _<table>_gho. It applies the requested ALTER TABLE statement directly to the ghost table while it contains zero rows, allowing the DDL operation to finish instantly without blocking traffic.

Phase 2: Dual-Stream Synchronization (Copy + Stream)

gh-ost synchronizes data through two parallel streams:

  1. Row-Based Binlog Streamer: Captures all real-time INSERT, UPDATE, and DELETE events affecting the original table and maps them onto the ghost table using idempotent statements (INSERT ON DUPLICATE KEY UPDATE and DELETE).
  2. Chunked Table Backfill: Reads historical rows from the original table in micro-batches (e.g., 1,000 to 5,000 rows at a time) using primary key range scans (WHERE id BETWEEN X AND Y) and writes them into the ghost table.

Because the binlog stream continually resolves concurrent changes idempotently, the backfill process can proceed safely at its own pace.

Phase 3: Adaptive Throttling

gh-ost continuously inspects database health before copying each batch. If replication lag exceeds a threshold (e.g., 1.0s), MySQL threads exceed capacity, or a human operator creates a throttle file, gh-ost pauses backfilling and processes only the incoming binlog stream.

gh-ost Runtime Throttling Flags:
- Check Replication Lag: Seconds_Behind_Master &lt; 1.0s
- Check Server Load:      Threads_running &lt; 80
- Custom Health Query:   SELECT count(*) FROM active_locks == 0
- Manual Flag File:      touch /tmp/ghost.throttle

Phase 4: Atomic Two-Step Cutover

To replace the original table with the newly altered ghost table without dropping queries, gh-ost uses a multi-table atomic rename sequence. It opens a dedicated database connection, places a read lock on the original table (LOCK TABLES users WRITE, _users_gho WRITE), queues incoming application writes for a fraction of a second, executes the rename, and releases the lock:

-- Atomic cutover query executed inside a single locked session
RENAME TABLE 
  `users` TO `_users_del`, 
  `_users_gho` TO `users`;

Application queries experience only a momentary sub-second pause, after which all traffic targets the new schema.

Implementation and Operational Commands

1. Dry-Run Verification

Before executing schema changes on multi-terabyte production datasets, always run gh-ost with --dry-run to validate table structures, identify foreign key blockers, verify primary keys, and check binlog formats:

gh-ost \
  --user="dba_user" \
  --password="SecureDbaPassword123" \
  --host="mysql-master.internal.net" \
  --port=3306 \
  --database="billing_prod" \
  --table="invoices" \
  --alter="ADD COLUMN payment_gateway_ref VARCHAR(64) DEFAULT NULL, ADD INDEX idx_gateway_ref (payment_gateway_ref)" \
  --verbose \
  --dry-run

2. Production Migration with Dynamic Throttling

The following command executes a live migration on the primary instance, reading binlog events, enforcing replication lag checks across two replicas, and outputting an interactive Unix domain socket for real-time control:

gh-ost \
  --user="dba_user" \
  --ask-pass \
  --host="mysql-master.internal.net" \
  --database="billing_prod" \
  --table="invoices" \
  --alter="ADD COLUMN payment_gateway_ref VARCHAR(64) DEFAULT NULL, ADD INDEX idx_gateway_ref (payment_gateway_ref)" \
  --chunk-size=2500 \
  --max-load="Threads_running=50" \
  --critical-load="Threads_running=100" \
  --throttle-control-replicas="mysql-replica-01.internal:3306,mysql-replica-02.internal:3306" \
  --max-lag-millis=1000 \
  --serve-socket-file="/tmp/gh-ost.invoices.sock" \
  --cut-over="atomic" \
  --initially-drop-ghost-table \
  --initially-drop-old-table \
  --execute

3. Interactive Runtime Control via Unix Socket

gh-ost exposes a live control interface through its Unix socket or TCP port. You can inspect progress or adjust operational parameters mid-flight without stopping the migration:

# Query active migration progress and remaining ETA
echo status | nc -U /tmp/gh-ost.invoices.sock

# Pause historical row copying immediately (e.g., during sudden peak traffic)
echo pause | nc -U /tmp/gh-ost.invoices.sock

# Resume row copying
echo resume | nc -U /tmp/gh-ost.invoices.sock

# Dynamically alter chunk size to reduce disk I/O pressure
echo chunk-size=1000 | nc -U /tmp/gh-ost.invoices.sock

# Adjust maximum tolerable replication lag to 500ms
echo max-lag-millis=500 | nc -U /tmp/gh-ost.invoices.sock

Architectural Comparison: gh-ost vs. pt-online-schema-change vs. Native Online DDL

Architectural DimensionNative MySQL Online DDLpt-online-schema-changeGitHub gh-ost
Mutation Sync MechanismInternal InnoDB log bufferSynchronous DB triggersAsynchronous binlog stream reader
Write Impact on MasterHigh on table-rebuilding DDLDoubles write IOPS via triggersRead from replica / Bulk batch write
Replication Lag ImpactBlocks replica applier threadsHeavy replica lagPauses copying on replica lag thresholds
Pausable ExecutionNo (Kill requires rollback)NoYes (Dynamic pause/resume via socket)
Risk of DeadlocksModerateHigh (Trigger-induced lock contention)Negligible (No triggers created)
Failover SafetyLost on instance terminationFails; triggers remain activeSafe; cleanup handles ghost table
Foreign Key SupportNativeSupported with cascade caveatsLimited (Requires manual FK drops)
Testing on ReplicasNoNoYes (Simulate migration on replica)

SRE and Database Administrator Best Practices

  • Enforce Row-Based Logging (RBL): gh-ost relies on row-level before/after images in binary logs. Ensure your MySQL server runs with binlog_format=ROW and binlog_row_image=FULL.
  • Verify Primary Key Uniformity: gh-ost requires a primary key or a unique non-null key to partition row chunks deterministically. Avoid running gh-ost on tables lacking an integer or well-indexed alphanumeric primary key.
  • Set Up a Throttle Flag File in CI/CD: Create an automated health monitor that writes to a throttle file (touch /tmp/ghost.throttle) if upstream payment queues or APM error rates spike, allowing your systems to pause the migration automatically.
  • Account for Table Storage Footprint: Because the ghost table (_table_gho) runs parallel to the original table, your disk volume must have at least 1.5X to 2X the table's current size in free storage to hold both datasets during the migration.
  • Audit Large Text and BLOB Columns: When migrating tables containing heavy off-page data (LONGTEXT, BLOB), decrease --chunk-size (e.g., 500–1,000 rows) to prevent network buffer saturation between the database and the gh-ost process.

Getting Started

Install gh-ost on your administrative bastion or database management server:

# Option 1: Download official pre-compiled binary release
curl -LO https://github.com/github/gh-ost/releases/download/v1.1.6/gh-ost-binary-linux-amd64-20231207144747.tar.gz
tar -xzf gh-ost-binary-linux-amd64-*.tar.gz
sudo mv gh-ost /usr/local/bin/

# Option 2: Verify binary execution
gh-ost --version

# Option 3: Run interactive inspection against a test environment
gh-ost \
  --user="root" \
  --password="rootpassword" \
  --host="127.0.0.1" \
  --database="test" \
  --table="users" \
  --alter="ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" \
  --verbose \
  --dry-run

By decoupling online schema changes from synchronous database triggers and relying on binary log stream replay, gh-ost allows SREs and database administrators to alter multi-terabyte production tables safely, predictably, and with zero application downtime.

Share: