MySQL Replikasyon Hataları: Slave Gecikme ve Hata Çözümleri

Üretim ortamında MySQL replikasyonu kurarken her şey yolunda gidebilir, ta ki sabah 3’te telefon çalana kadar. “Slave geri kaldı, veri tutarsızlığı var” diye başlayan bu tür geceler, bir sistem yöneticisinin en sinir bozucu deneyimleri arasında yer alır. Bu yazıda MySQL replikasyon hatalarını, slave gecikmelerini ve bunları nasıl çözeceğinizi gerçek dünya senaryolarıyla ele alacağım.

MySQL Replikasyonu Nasıl Çalışır?

Sorunları çözmeden önce temel mekanizmayı anlamak şart. Master, tüm değişiklikleri binary log’a yazar. Slave bu log’ları okuyarak kendi relay log’una kopyalar (IO thread), ardından bu değişiklikleri uygular (SQL thread). İki thread’in de sağlıklı çalışması gerekir. Birinde sorun çıktığında replikasyon durur ya da gecikir.

# Slave durumunu kontrol etmenin ilk adımı
mysql -u root -p -e "SHOW SLAVE STATUSG"

Bu komutun çıktısında dikkat etmeniz gereken birkaç kritik alan var:

  • Slave_IO_Running: IO thread’in çalışıp çalışmadığı (Yes/No/Connecting)
  • Slave_SQL_Running: SQL thread’in durumu
  • Seconds_Behind_Master: Slave’in master’dan kaç saniye geride olduğu
  • Last_IO_Error: IO thread’de son hata mesajı
  • Last_SQL_Error: SQL thread’de son hata mesajı
  • Exec_Master_Log_Pos: Son çalıştırılan binary log pozisyonu

Slave Gecikmesinin Nedenleri ve Teşhisi

Gecikmeyi Ölçmek

Önce mevcut durumu net görmek lazım:

# Detaylı slave durumu
mysql -u root -p << 'EOF'
SHOW SLAVE STATUSG
SELECT 
  CHANNEL_NAME,
  SERVICE_STATE,
  LAST_ERROR_MESSAGE,
  LAST_ERROR_TIMESTAMP
FROM performance_schema.replication_applier_status_by_worker;
EOF

Seconds_Behind_Master değeri sürekli artıyorsa ciddi bir sorun var demektir. Sıfıra yakınsa, slave arada sırada geri kalıp tekrar yetişiyor olabilir.

Yoğun Yazma Trafiği

En yaygın gecikme nedenlerinden biri, master’daki yazma yükünün slave’in SQL thread’inin kaldırabileceğinin üzerinde olmasıdır. Master paralel yazan onlarca client’a sahipken, klasik replikasyonda slave bu değişiklikleri tek thread ile sırayla uygular.

# Master'daki yazma operasyonlarını izle
mysql -u root -p -e "SHOW GLOBAL STATUS LIKE 'Com_insert';"
mysql -u root -p -e "SHOW GLOBAL STATUS LIKE 'Com_update';"
mysql -u root -p -e "SHOW GLOBAL STATUS LIKE 'Com_delete';"

# Binlog'daki olayları analiz et
mysqlbinlog --start-datetime="2024-01-15 10:00:00" 
  --stop-datetime="2024-01-15 10:05:00" 
  /var/lib/mysql/mysql-bin.000123 | head -100

MySQL 5.7 ve üzeri için paralel replikasyonu etkinleştirmek bu sorunu büyük ölçüde çözer:

-- Slave'de paralel replikasyonu etkinleştir
STOP SLAVE;

SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK';
SET GLOBAL slave_parallel_workers = 8;
SET GLOBAL slave_preserve_commit_order = 1;

-- my.cnf'e de ekleyin kalıcı olsun diye
-- slave_parallel_type = LOGICAL_CLOCK
-- slave_parallel_workers = 8
-- slave_preserve_commit_order = 1

START SLAVE;

Büyük Transaction’lar

Tek sorguda milyonlarca satır güncelleyen bir sorgu, master’da birkaç saniyede biterken slave’de onlarca saniye sürebilir. Bunu tespit etmek için:

# Uzun süren relay log işlemlerini izle
mysql -u root -p << 'EOF'
SELECT 
  worker_id,
  last_applied_transaction,
  applying_transaction,
  last_applied_transaction_end_apply_timestamp,
  last_applied_transaction_start_apply_timestamp,
  TIMESTAMPDIFF(SECOND, 
    last_applied_transaction_start_apply_timestamp,
    last_applied_transaction_end_apply_timestamp
  ) AS apply_duration_seconds
FROM performance_schema.replication_applier_status_by_worker;
EOF

Eğer büyük batch işlemleri yapıyorsanız, bunları daha küçük parçalara bölmek hem master hem de slave üzerindeki yükü azaltır.

Yaygın Replikasyon Hataları

Hata 1062: Duplicate Entry

Bu hata, slave’in zaten var olan bir kaydı eklemeye çalıştığında ortaya çıkar. Genellikle slave üzerinde yanlışlıkla yapılan yazma işlemleri veya replikasyonun tutarsız bir noktadan başlatılması sonucu oluşur.

# Hatayı görmek için
mysql -u root -p -e "SHOW SLAVE STATUSG" | grep -A2 "Last_SQL_Error"

# Örnek hata mesajı:
# Last_SQL_Error: Could not execute Write_rows event on table mydb.users; 
# Duplicate entry '12345' for key 'PRIMARY'

Geçici çözüm (dikkatli kullanın!):

-- TEHLİKELİ: Sadece geçici debug için, production'da dikkatli ol
STOP SLAVE;
SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;
START SLAVE;

-- Daha güvenli yaklaşım: slave_skip_errors (my.cnf'e eklenebilir)
-- slave_skip_errors = 1062
-- Ama bu kalıcı veri tutarsızlığına yol açabilir, dikkatli ol!

Kalıcı çözüm için önce neden duplicate oluştuğunu anlamak gerekir. Çoğu zaman slave üzerinde doğrudan yazma yapılmıştır:

-- Slave'in read_only olduğundan emin ol
SHOW VARIABLES LIKE 'read_only';
SHOW VARIABLES LIKE 'super_read_only';

-- Read-only'yi etkinleştir
SET GLOBAL read_only = ON;
SET GLOBAL super_read_only = ON;

Hata 1032: Can’t Find Record

Slave, güncellenmesi veya silinmesi gereken bir kaydı bulamadığında bu hata oluşur. Genellikle slave üzerinde silme işlemi yapılmış ya da replikasyon atlamış olabilir.

# Hangi tabloda hangi kaydın eksik olduğunu bul
mysqlbinlog --base64-output=decode-rows -v 
  /var/lib/mysql/relay-bin.000045 | grep -A10 "table_id"
-- Master ve slave'deki kayıt sayısını karşılaştır
-- Master'da:
SELECT COUNT(*) FROM mydb.orders WHERE updated_at > '2024-01-15';

-- Slave'de:
SELECT COUNT(*) FROM mydb.orders WHERE updated_at > '2024-01-15';

Hata 1236: Binary Log Dosyası Bulunamadı

Master’daki binary log temizlendiğinde slave hala eski bir log’u aramaya devam edebilir. Bu kritik bir hata çünkü replikasyonu sıfırlamak gerekir.

# Master'daki mevcut binary log'ları kontrol et
mysql -u root -p -e "SHOW BINARY LOGS;"

# Slave'in nereye baktığını gör
mysql -u root -p -e "SHOW SLAVE STATUSG" | grep "Master_Log_File"

Bu durumda slave’i sıfırdan kurmak en güvenli yoldur:

# Master'da dump al
mysqldump --all-databases 
  --master-data=2 
  --single-transaction 
  --flush-logs 
  -u root -p > /backup/master_dump.sql

# Dump'ın başında binary log pozisyonu var
head -50 /backup/master_dump.sql | grep "MASTER_LOG"

Replikasyonu Sıfırdan Kurmak

Bazen en temiz çözüm replikasyonu baştan kurmaktır. Üretim ortamında bunu minimal downtime ile yapmak mümkün:

# 1. Master'da dump al (read lock kısa süreliğine)
mysqldump -u root -p 
  --all-databases 
  --master-data=2 
  --single-transaction 
  --routines 
  --triggers 
  --events 
  --flush-logs 
  --compress 
  -h master_ip | gzip > /tmp/master_full_$(date +%Y%m%d_%H%M%S).sql.gz

# 2. Slave'e aktar
rsync -avz --progress /tmp/master_full_*.sql.gz slave_ip:/tmp/

# 3. Slave'de import et
mysql -u root -p slave_host << 'EOF'
STOP SLAVE;
RESET SLAVE ALL;
EOF

zcat /tmp/master_full_*.sql.gz | mysql -u root -p

# 4. Binary log pozisyonunu al ve replikasyonu başlat
# (Dump dosyasının başındaki CHANGE MASTER satırını kullan)
head -100 <(zcat /tmp/master_full_*.sql.gz) | grep "CHANGE MASTER"
-- Slave'de replikasyonu yapılandır
CHANGE MASTER TO
  MASTER_HOST='192.168.1.100',
  MASTER_USER='replication_user',
  MASTER_PASSWORD='guclu_sifre_buraya',
  MASTER_LOG_FILE='mysql-bin.000456',
  MASTER_LOG_POS=154;

START SLAVE;
SHOW SLAVE STATUSG

GTID Replikasyonunda Hata Yönetimi

MySQL 5.6+ ile gelen GTID (Global Transaction ID) replikasyonu, birçok sorunu kolaylaştırır ama kendine özgü sorunları da beraberinde getirir.

-- GTID'nin etkin olup olmadığını kontrol et
SHOW VARIABLES LIKE 'gtid_mode';
SHOW VARIABLES LIKE 'enforce_gtid_consistency';

-- Çalıştırılan ve alınan GTID'leri gör
SHOW MASTER STATUS;
-- Executed_Gtid_Set sütununa bak

-- Slave'de eksik GTID'leri bul
SELECT GTID_SUBTRACT(
  (SELECT Executed_Gtid_Set FROM information_schema.GLOBAL_STATUS 
   WHERE VARIABLE_NAME='GTID_EXECUTED'),  
  (SELECT @@global.gtid_purged)
);

GTID ile replikasyonda transaction atlamak için farklı bir yöntem kullanılır:

-- GTID replikasyonda hatalı transaction'ı atla
STOP SLAVE;

-- Hatalı GTID'yi al (SHOW SLAVE STATUS'tan)
-- Örnek: master_uuid:12345

SET GTID_NEXT='3E11FA47-71CA-11E1-9E33-C80AA9429562:12345';
BEGIN;
COMMIT;
SET GTID_NEXT='AUTOMATIC';

START SLAVE;
SHOW SLAVE STATUSG

Replikasyonu İzlemek İçin Script

Manuel kontrol yapmak yerine otomatik izleme kurmak çok daha sağlıklıdır. İşte basit ama etkili bir monitoring scripti:

#!/bin/bash
# /usr/local/bin/check_mysql_replication.sh

MYSQL_USER="monitor_user"
MYSQL_PASS="monitor_pass"
ALERT_EMAIL="[email protected]"
MAX_LAG=300  # 5 dakika max gecikme

# Slave durumunu al
SLAVE_STATUS=$(mysql -u$MYSQL_USER -p$MYSQL_PASS -e "SHOW SLAVE STATUSG" 2>/dev/null)

# Kritik değerleri parse et
IO_RUNNING=$(echo "$SLAVE_STATUS" | grep "Slave_IO_Running:" | awk '{print $2}')
SQL_RUNNING=$(echo "$SLAVE_STATUS" | grep "Slave_SQL_Running:" | awk '{print $2}')
SECONDS_BEHIND=$(echo "$SLAVE_STATUS" | grep "Seconds_Behind_Master:" | awk '{print $2}')
LAST_SQL_ERROR=$(echo "$SLAVE_STATUS" | grep "Last_SQL_Error:" | cut -d: -f2-)
LAST_IO_ERROR=$(echo "$SLAVE_STATUS" | grep "Last_IO_Error:" | cut -d: -f2-)

# Kontroller
if [ "$IO_RUNNING" != "Yes" ]; then
    echo "KRITIK: Slave IO Thread calısmiyor!" | 
    mail -s "[ALARM] MySQL Replikasyon IO Hatası - $(hostname)" $ALERT_EMAIL
    echo "Hata: $LAST_IO_ERROR" >> /var/log/mysql_replication_errors.log
fi

if [ "$SQL_RUNNING" != "Yes" ]; then
    echo "KRITIK: Slave SQL Thread calısmiyor!" | 
    mail -s "[ALARM] MySQL Replikasyon SQL Hatası - $(hostname)" $ALERT_EMAIL
    echo "Hata: $LAST_SQL_ERROR" >> /var/log/mysql_replication_errors.log
fi

if [ "$SECONDS_BEHIND" != "NULL" ] && [ "$SECONDS_BEHIND" -gt "$MAX_LAG" ]; then
    echo "UYARI: Slave ${SECONDS_BEHIND} saniye geride!" | 
    mail -s "[UYARI] MySQL Slave Gecikmesi - $(hostname)" $ALERT_EMAIL
fi

# Log'a yaz
echo "$(date '+%Y-%m-%d %H:%M:%S') | IO: $IO_RUNNING | SQL: $SQL_RUNNING | Lag: ${SECONDS_BEHIND}s" 
  >> /var/log/mysql_replication_monitor.log
# Script'i her dakika çalıştır
chmod +x /usr/local/bin/check_mysql_replication.sh
echo "* * * * * root /usr/local/bin/check_mysql_replication.sh" > /etc/cron.d/mysql_replication_check

Disk I/O ve Network Kaynaklı Gecikmeler

Bazen gecikme ne sorgu karmaşıklığından ne de thread sayısından kaynaklanır; altta yatan altyapıdan kaynaklanır.

# Slave'deki disk I/O durumunu kontrol et
iostat -x 1 10

# Network gecikmesini ölç
ping -c 100 master_ip | tail -5

# MySQL'in relay log yazma hızını izle
watch -n 1 'ls -la /var/lib/mysql/relay-bin.* | tail -5'

# Network bant genişliği kullanımı
iftop -i eth0 -f "host master_ip"

Relay log’ların bulunduğu diskin dolu olması da sık karşılaşılan bir sorundur:

# Disk kullanımını kontrol et
df -h /var/lib/mysql

# Eski relay log'ları temizle (dikkatli ol!)
# Önce slave'in nerede olduğunu gör
mysql -u root -p -e "SHOW SLAVE STATUSG" | grep "Relay_Log_File"

# Relay log temizleme (MySQL bunu otomatik yapmalı, relay_log_purge = ON)
mysql -u root -p -e "SHOW VARIABLES LIKE 'relay_log_purge';"

Gerçek Dünya Senaryosu: E-ticaret Platformunda Replikasyon Krizi

Bir e-ticaret müşterisinde yaşanan gerçek bir senaryoyu anlatayım. Kampanya günü yoğun yazma trafiği sonucunda slave 45 dakika geri kalmıştı. Raporlama sistemi slave’i okuyordu ve yöneticiler anlık olmayan satış verisi görüyordu. Panik had safhaya ulaşmıştı.

İlk adım durumu değerlendirmekti:

-- Slave durumunu anlık izle
SHOW SLAVE STATUSG
-- Seconds_Behind_Master: 2743

-- Slave üzerindeki aktif süreçleri gör
SHOW PROCESSLIST;

-- Hangi tablo üzerinde işlem yapılıyor?
SELECT * FROM information_schema.INNODB_TRXG

Sorun, kampanya sırasında yapılan büyük bir toplu güncelleme sorgusuydu. Master tek bir transaction içinde 2 milyon satırı güncelliyordu. Slave bu dev transaction’ı uygulamak zorundaydı.

Çözüm için slave’deki paralel worker sayısını artırdık:

STOP SLAVE SQL_THREAD;
SET GLOBAL slave_parallel_workers = 16;
START SLAVE SQL_THREAD;

-- 10 dakikada bir gecikmeyi izledik
-- watch -n 60 'mysql -u root -p -e "SHOW SLAVE STATUSG" | grep Seconds_Behind'

Aynı zamanda gelecekte bu tür sorunları önlemek için büyük toplu işlemleri parçalara bölen bir prosedür yazdık:

-- Büyük batch işlemleri küçük parçalara böl
DELIMITER //
CREATE PROCEDURE bulk_update_in_chunks(
  IN chunk_size INT,
  IN max_id INT
)
BEGIN
  DECLARE current_id INT DEFAULT 0;
  
  WHILE current_id < max_id DO
    UPDATE orders 
    SET status = 'processed'
    WHERE id BETWEEN current_id AND current_id + chunk_size
    AND status = 'pending';
    
    SET current_id = current_id + chunk_size + 1;
    
    -- Her chunk arasında kısa bir bekleme
    DO SLEEP(0.1);
  END WHILE;
END //
DELIMITER ;

Performans Şeması ile Derinlemesine Analiz

Performance Schema, replikasyon sorunlarını teşhis etmek için altın madeni gibidir:

-- Replikasyon kanallarının detaylı durumu
SELECT * FROM performance_schema.replication_connection_statusG

-- Worker thread'lerin performansı
SELECT 
  WORKER_ID,
  LAST_APPLIED_TRANSACTION,
  LAST_ERROR_MESSAGE,
  LAST_ERROR_TIMESTAMP,
  APPLYING_TRANSACTION
FROM performance_schema.replication_applier_status_by_worker;

-- En çok zaman alan event'leri bul
SELECT 
  APPLYING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP,
  APPLYING_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP,
  TIMESTAMPDIFF(MICROSECOND,
    APPLYING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP,
    APPLYING_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP
  ) / 1000000 AS apply_lag_seconds
FROM performance_schema.replication_applier_status_by_coordinator;

Önleyici Tedbirler ve Best Practice’ler

Sorun çıkmadan önce almak gereken önlemler:

  • Binary log formatı: ROW formatı kullanın. STATEMENT format bazı sorgularda tutarsızlık yaratabilir
  • sync_binlog = 1: Crash durumunda veri kaybını önler, performans tradeoff’u var
  • innodb_flush_log_at_trx_commit = 1: ACID uyumluluğu için
  • expire_logs_days: Binary log’ların otomatik temizlenmesi için (7 gün öneririm)
  • max_allowed_packet: Master ve slave’de aynı olmalı, büyük blob’lar için artırın
  • slave_net_timeout: Network kesilmelerinde IO thread’in ne kadar bekleyeceği
  • read_only = ON: Slave üzerine yanlışlıkla yazma yapılmasını önler
  • Düzenli pt-table-checksum: Percona toolkit ile veri tutarlılığını periyodik kontrol edin
# Percona Toolkit ile tutarlılık kontrolü
pt-table-checksum 
  --host=master_ip 
  --user=checksum_user 
  --password=sifre 
  --databases=production_db 
  --replicate=checksum.checksums

# Tutarsızlıkları görmek için slave'de
pt-table-sync 
  --print 
  --sync-to-master 
  h=slave_ip,u=root,p=sifre,D=production_db

Sonuç

MySQL replikasyon sorunları, deneyimli DBA’ler için bile stresli anlar yaratabilir. Ancak sistematik bir yaklaşımla, doğru araçları kullanarak ve iyi bir monitoring altyapısıyla bu sorunların büyük çoğunluğunu hem hızlıca çözebilir hem de tekrarlanmasını önleyebilirsiniz.

En önemli takeaway’ler şunlar:

  • Slave’i her zaman read_only = ON ile çalıştırın
  • Paralel replikasyonu etkinleştirin, MySQL 5.7+ için LOGICAL_CLOCK kullanın
  • Büyük batch işlemleri küçük parçalara bölün
  • Performance Schema’yı aktif olarak kullanın
  • pt-table-checksum ile düzenli tutarlılık kontrolü yapın
  • Gecikme alertleri için otomasyon kurun, elle kontrol etmeyi bırakın

Replikasyon, modern web altyapısının vazgeçilmez bir parçası. Bu mekanizmayı iyi anladığınızda, gecenin 3’ünde gelen o telefona çok daha sakin cevap verebilirsiniz.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir