Replication (Primary-Replica)
AdvancedBinary log replication streams changes from primary to replicas asynchronously or semi-synchronously; replicas serve read traffic and provide failover capability.
Overview
MySQL replication streams changes from a primary (source) to one or more replicas using the binary log (binlog). The primary writes changes to the binlog; each replica runs an I/O thread that fetches binlog events and writes them to a local relay log, then an SQL thread applies them. This is asynchronous by default: the primary commits without waiting for replica acknowledgement, meaning replicas can lag and lose committed transactions on primary failure. Semi-synchronous replication (using the rpl_semi_sync plugins) waits for at least one replica to acknowledge receipt before the primary responds to the client, dramatically reducing the data-loss window. GTID (Global Transaction Identifiers) mode, introduced in MySQL 5.6, replaces file+offset-based positioning with universally unique transaction IDs, making failover and replica promotion deterministic and safe.
Setting up GTID-based replication
GTID replication is the modern standard. Each transaction gets a unique ID (server_uuid:transaction_number) that replicas use to track exactly which transactions they have applied, eliminating offset confusion.
# Primary my.cnf
[mysqld]
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
binlog_format = ROW # recommended: captures row-level changes
gtid_mode = ON
enforce_gtid_consistency = ON
sync_binlog = 1 # flush binlog to disk on each commit (durability)
innodb_flush_log_at_trx_commit = 1
# Replica my.cnf
[mysqld]
server-id = 2
gtid_mode = ON
enforce_gtid_consistency = ON
relay_log = /var/log/mysql/relay-bin.log
read_only = ON # prevent accidental writes to replica
super_read_only = ON # block even SUPER users
-- Configure replica (MySQL 8.0+ syntax)
CHANGE REPLICATION SOURCE TO
SOURCE_HOST = '10.0.0.1',
SOURCE_USER = 'replication_user',
SOURCE_PASSWORD = 'secret',
SOURCE_AUTO_POSITION = 1; -- use GTID auto-positioning
START REPLICA;
SHOW REPLICA STATUS\G -- check Seconds_Behind_SourceSemi-synchronous replication and replication lag
Asynchronous replication risks data loss on primary failure. Semi-sync waits for at least one replica to acknowledge before committing to the client. Monitor replication lag with Seconds_Behind_Source.
-- Enable semi-synchronous replication (primary side)
INSTALL PLUGIN rpl_semi_sync_source SONAME 'semisync_source.so';
SET GLOBAL rpl_semi_sync_source_enabled = 1;
SET GLOBAL rpl_semi_sync_source_timeout = 1000; -- fallback to async after 1s
-- Replica side
INSTALL PLUGIN rpl_semi_sync_replica SONAME 'semisync_replica.so';
SET GLOBAL rpl_semi_sync_replica_enabled = 1;
-- Monitor replication lag
SHOW REPLICA STATUS\G
-- Seconds_Behind_Source: NULL = not running, 0 = caught up, N = lagging
-- Prometheus metrics for replication lag (mysqld_exporter)
-- mysql_slave_status_seconds_behind_master
-- Alert when lag > 30s:
# - alert: MySQLReplicationLag
# expr: mysql_slave_status_seconds_behind_master > 30
# for: 5m
-- Check GTID executed sets to verify sync
SHOW VARIABLES LIKE 'gtid_executed'; -- primary
SHOW VARIABLES LIKE 'gtid_executed'; -- replica (should match when caught up)Read/write splitting in Spring Boot
Route writes to the primary and reads to replicas using Spring's AbstractRoutingDataSource. A TransactionSynchronizationManager check on readOnly determines which DataSource to use.
@Configuration
public class DataSourceConfig {
@Bean
@Primary
public DataSource routingDataSource(
@Qualifier("primaryDs") DataSource primary,
@Qualifier("replicaDs") DataSource replica) {
return new AbstractRoutingDataSource() {
@Override
protected Object determineCurrentLookupKey() {
// readOnly transactions route to replica
return TransactionSynchronizationManager.isCurrentTransactionReadOnly()
? "replica" : "primary";
}
{ // init block
setTargetDataSources(Map.of("primary", primary, "replica", replica));
setDefaultTargetDataSource(primary);
}
};
}
}
// Usage: @Transactional(readOnly=true) → replica
// @Transactional → primary
@Service
public class ReportService {
@Transactional(readOnly = true) // → replica DataSource
public List<SalesReport> generateMonthlyReport() { ... }
@Transactional // → primary DataSource
public void createOrder(Order o) { ... }
}Key Points to Remember
- 1Asynchronous replication (default) can lose committed transactions on primary failure; use semi-sync for critical data.
- 2GTID mode makes failover and replica promotion safe and deterministic — prefer it over file+offset positioning.
- 3Seconds_Behind_Source (Seconds_Behind_Master in older versions) is the primary replication health signal; alert on > 30s.
- 4Set read_only=ON and super_read_only=ON on replicas to prevent accidental writes that would break replication.
- 5Read/write splitting requires application routing logic — AbstractRoutingDataSource or ProxySQL at the proxy layer.
- 6binlog_format=ROW is recommended over STATEMENT for deterministic replication of non-deterministic functions (NOW(), UUID()).
Interview Questions
Sign in to ask AriaWhat is the difference between asynchronous and semi-synchronous MySQL replication?
How do GTIDs make replica failover safer than file+offset-based replication?
How would you implement read/write splitting in a Spring Boot application?
What does Seconds_Behind_Source = NULL mean and how would you diagnose it?
A replica is lagging by 5 minutes. What are the possible causes and how do you investigate?
Ask Aria about Replication (Primary-Replica)
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.