MySQL Group Replication Kurulumu ve Yönetimi

Yüksek erişilebilirlik gerektiren üretim ortamlarında MySQL’in yerleşik replikasyon çözümlerinden Group Replication, özellikle multi-master yazma senaryolarında ciddi avantajlar sunuyor. Klasik master-slave yapısının aksine, Group Replication tüm düğümlerin okuma-yazma işlemi yapabildiği, otomatik üye yönetimi ve yerleşik conflict detection mekanizması olan bir yapı kuruyor. Bu yazıda sıfırdan üç düğümlü bir Group Replication kümesi kurarak, günlük yönetim senaryolarını ve sorun giderme adımlarını ele alacağız.

Group Replication Nedir ve Ne Zaman Kullanmalısınız?

MySQL Group Replication, Paxos tabanlı konsensüs protokolü üzerine inşa edilmiş bir replikasyon mekanizması. Her yazma işlemi grup içindeki tüm düğümlerin onayını aldıktan sonra commit ediliyor. Bu sayede veri tutarlılığı garanti altına alınıyor.

Şöyle bir senaryo düşünelim: E-ticaret platformunuz var ve sipariş veritabanınız tek bir MySQL sunucusunda çalışıyor. O sunucu çöktüğünde siparişler de gidiyor. Group Replication bu sorunu çözmek için ideal bir yapı sunuyor çünkü herhangi bir düğüm düştüğünde kalan düğümler otomatik olarak devam ediyor, manüel müdahaleye gerek kalmıyor.

Single-Primary Mode ile Multi-Primary Mode arasında seçim yaparken dikkatli olun. Single-primary modda yalnızca bir düğüm yazma işlemi yaparken diğerleri okuma için kullanılıyor. Multi-primary modda tüm düğümler yazma yapabiliyor ancak çakışma yönetimi daha karmaşık bir hal alıyor. Çoğu prodüksiyon senaryosunda single-primary moddan başlamak daha sağlıklı.

Ortam Hazırlığı

Bu kurulum için üç adet Ubuntu 22.04 sunucu kullanacağız. Sunucularımızın bilgileri şöyle:

  • node1: 192.168.1.10 (başlangıçta primary olacak)
  • node2: 192.168.1.11
  • node3: 192.168.1.12

Her üç sunucuda da MySQL 8.0 kurulu ve temel network ayarları yapılandırılmış olmalı. Eğer MySQL kurulu değilse önce şunu çalıştırın:

# Her üç düğümde çalıştırın
sudo apt update && sudo apt upgrade -y
sudo apt install mysql-server -y
sudo systemctl enable mysql
sudo systemctl start mysql

# MySQL versiyonunu doğrulayın
mysql --version
# mysql  Ver 8.0.35-0ubuntu0.22.04.1 for Linux on x86_64 ((Ubuntu))

Hosts dosyasını her üç sunucuda da güncelleyin:

sudo tee -a /etc/hosts << 'EOF'
192.168.1.10 node1 node1.grp.local
192.168.1.11 node2 node2.grp.local
192.168.1.12 node3 node3.grp.local
EOF

Firewall ayarlarını da unutmayın. Group Replication için 33061 portunu açmanız gerekiyor:

# Her üç düğümde çalıştırın
sudo ufw allow 3306/tcp
sudo ufw allow 33061/tcp
sudo ufw reload

# ya da iptables kullanıyorsanız
iptables -I INPUT -p tcp --dport 33061 -j ACCEPT
iptables -I INPUT -p tcp --dport 3306 -j ACCEPT

MySQL Yapılandırması

Her düğüm için ayrı bir MySQL yapılandırması gerekiyor. En kritik nokta şu: her düğümün kendine özgü server-id ve loose-group_replication_local_address değeri olmalı.

node1 için /etc/mysql/mysql.conf.d/mysqld.cnf dosyasını düzenleyin:

sudo tee /etc/mysql/mysql.conf.d/group_replication.cnf << 'EOF'
[mysqld]
# Temel ayarlar
server_id = 1
bind-address = 0.0.0.0
report_host = node1

# Binary log ayarları
log_bin = binlog
log_slave_updates = ON
binlog_format = ROW
binlog_checksum = NONE

# GTID ayarları
gtid_mode = ON
enforce_gtid_consistency = ON

# Replikasyon ayarları
master_info_repository = TABLE
relay_log_info_repository = TABLE

# Group Replication plugin
plugin_load_add = 'group_replication.so'
group_replication_group_name = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
group_replication_start_on_boot = OFF
group_replication_local_address = "node1:33061"
group_replication_group_seeds = "node1:33061,node2:33061,node3:33061"
group_replication_bootstrap_group = OFF
group_replication_single_primary_mode = ON
group_replication_enforce_update_everywhere_checks = OFF
EOF

node2 için farklılaşan değerlere dikkat edin:

sudo tee /etc/mysql/mysql.conf.d/group_replication.cnf << 'EOF'
[mysqld]
server_id = 2
bind-address = 0.0.0.0
report_host = node2

log_bin = binlog
log_slave_updates = ON
binlog_format = ROW
binlog_checksum = NONE

gtid_mode = ON
enforce_gtid_consistency = ON

master_info_repository = TABLE
relay_log_info_repository = TABLE

plugin_load_add = 'group_replication.so'
group_replication_group_name = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
group_replication_start_on_boot = OFF
group_replication_local_address = "node2:33061"
group_replication_group_seeds = "node1:33061,node2:33061,node3:33061"
group_replication_bootstrap_group = OFF
group_replication_single_primary_mode = ON
group_replication_enforce_update_everywhere_checks = OFF
EOF

node3 için aynı yapıyı server_id = 3, report_host = node3 ve group_replication_local_address = "node3:33061" değerleriyle oluşturun. group_replication_group_name için üretimde UUID üretecini kullanın:

python3 -c "import uuid; print(uuid.uuid4())"
# Çıktı örneği: 7f3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c

Replikasyon Kullanıcısı ve İlk Yapılandırma

Her üç düğümde de replikasyon kullanıcısını oluşturmanız gerekiyor. MySQL’e bağlanın:

# node1, node2 ve node3'te çalıştırın
sudo mysql -u root

# MySQL içinde
SET SQL_LOG_BIN=0;
CREATE USER 'repl_user'@'%' IDENTIFIED BY 'GucluSifre123!@#' REQUIRE SSL;
GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'%';
GRANT BACKUP_ADMIN ON *.* TO 'repl_user'@'%';
FLUSH PRIVILEGES;
SET SQL_LOG_BIN=1;

# Replikasyon kanalını yapılandır
CHANGE REPLICATION SOURCE TO
  SOURCE_USER='repl_user',
  SOURCE_PASSWORD='GucluSifre123!@#'
  FOR CHANNEL 'group_replication_recovery';

MySQL’i yeniden başlatın:

sudo systemctl restart mysql

Grubu Başlatmak: Bootstrap İşlemi

Bu adım çok kritik. Group Replication’ı yalnızca bir kez ve yalnızca ilk düğümde bootstrap etmelisiniz. Yanlış yapılan bootstrap işlemi split-brain senaryosuna yol açar.

# SADECE node1'de çalıştırın
sudo mysql -u root

# Bootstrap modunu geçici olarak aktif edin
SET GLOBAL group_replication_bootstrap_group=ON;

# Group Replication'ı başlat
START GROUP_REPLICATION;

# Bootstrap modunu hemen kapat!
SET GLOBAL group_replication_bootstrap_group=OFF;

# Durumu kontrol edin
SELECT * FROM performance_schema.replication_group_membersG

Çıktıda şunu görmelisiniz:

CHANNEL_NAME: group_replication_applier
   MEMBER_ID: 7f3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c
 MEMBER_HOST: node1
 MEMBER_PORT: 3306
MEMBER_STATE: ONLINE
 MEMBER_ROLE: PRIMARY

Şimdi diğer düğümleri gruba ekleyelim. node2 ve node3’te ayrı ayrı çalıştırın:

# node2 ve node3'te çalıştırın
sudo mysql -u root

START GROUP_REPLICATION;

# Birkaç saniye bekleyin, sonra kontrol edin
SELECT MEMBER_HOST, MEMBER_STATE, MEMBER_ROLE
FROM performance_schema.replication_group_members;

Üç düğümün de ONLINE durumunda göründüğünü doğrulayın. RECOVERING durumunda kalan düğüm varsa bu normaldir, büyük bir veri seti varsa senkronizasyon biraz zaman alabilir.

Küme Durumunu İzlemek

Günlük operasyonlarda bu sorgular hayat kurtarır:

# Küme genel durumu
mysql -u root -e "
SELECT 
  MEMBER_HOST,
  MEMBER_PORT,
  MEMBER_STATE,
  MEMBER_ROLE,
  MEMBER_VERSION
FROM performance_schema.replication_group_members
ORDER BY MEMBER_ROLE DESC;
"

# Primary düğümü bulmak
mysql -u root -e "
SELECT MEMBER_HOST, MEMBER_PORT
FROM performance_schema.replication_group_members
WHERE MEMBER_ROLE = 'PRIMARY'
AND MEMBER_STATE = 'ONLINE';
"

# Replikasyon gecikmesini kontrol et
mysql -u root -e "
SELECT 
  CHANNEL_NAME,
  COUNT_TRANSACTIONS_IN_QUEUE,
  COUNT_TRANSACTIONS_CHECKED,
  COUNT_CONFLICTS_DETECTED,
  COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE
FROM performance_schema.replication_group_member_stats;
"

Bu sorguları bir bash script’e dönüştürerek cron ile her 5 dakikada çalıştırabilir ve Slack ya da e-posta ile alarm gönderebilirsiniz.

Monitoring Script’i

Prodüksiyonda kullandığım basit ama işlevsel bir izleme script’i:

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

MYSQL_USER="monitor_user"
MYSQL_PASS="MonitorSifre456"
SLACK_WEBHOOK="https://hooks.slack.com/services/XXX/YYY/ZZZ"
LOG_FILE="/var/log/mysql/group_replication_monitor.log"
HOSTNAME=$(hostname)

log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> $LOG_FILE
}

send_alert() {
    local message=$1
    curl -s -X POST "$SLACK_WEBHOOK" 
        -H 'Content-type: application/json' 
        --data "{"text":"[$HOSTNAME] MySQL GRP ALARM: $message"}" > /dev/null
    log_message "ALARM gonderildi: $message"
}

# Küme durumunu kontrol et
MEMBER_COUNT=$(mysql -u$MYSQL_USER -p$MYSQL_PASS -sN -e 
    "SELECT COUNT(*) FROM performance_schema.replication_group_members 
     WHERE MEMBER_STATE='ONLINE';" 2>/dev/null)

if [ -z "$MEMBER_COUNT" ]; then
    send_alert "MySQL'e baglanilamiyor! Servis durumu kontrol edilmeli."
    exit 1
fi

if [ "$MEMBER_COUNT" -lt 2 ]; then
    send_alert "Aktif uye sayisi $MEMBER_COUNT! Kurum cokmek uzere olabilir."
fi

# Transaction kuyruğu kontrolü
QUEUE_SIZE=$(mysql -u$MYSQL_USER -p$MYSQL_PASS -sN -e 
    "SELECT COUNT_TRANSACTIONS_IN_QUEUE 
     FROM performance_schema.replication_group_member_stats 
     LIMIT 1;" 2>/dev/null)

if [ "$QUEUE_SIZE" -gt 1000 ]; then
    send_alert "Transaction kuyrugu $QUEUE_SIZE adet! Replikasyon gecikmesi var."
fi

log_message "Kontrol OK - Aktif uye: $MEMBER_COUNT, Kuyruk: $QUEUE_SIZE"
chmod +x /usr/local/bin/grp_monitor.sh
echo "*/5 * * * * /usr/local/bin/grp_monitor.sh" | sudo crontab -

Bir Düğümü Gruba Geri Eklemek

Gerçek hayatta en sık karşılaşılan senaryo: bir düğüm bakım için kapatıldı ya da kendi kendine düştü. Nasıl geri ekleyeceğinizi bilin.

# Düştüğü varsayılan node2'de çalıştırın

# Önce durumu kontrol edin
sudo systemctl status mysql

# MySQL'i başlatın
sudo systemctl start mysql

# MySQL'e bağlanın
sudo mysql -u root

# Mevcut durumu görün
SHOW SLAVE STATUSG

# Group Replication'ı başlatın (bootstrap olmadan!)
START GROUP_REPLICATION;

# Recovery sürecini takip edin
SELECT MEMBER_HOST, MEMBER_STATE
FROM performance_schema.replication_group_members;
-- RECOVERING -> ONLINE geçişini bekleyin

Düğüm uzun süre offline kaldıysa ve binlog’lar silinmişse group_replication_recovery kanalı hata verebilir. Bu durumda MySQL Shell ile clone plugin devreye giriyor:

# node2'de MySQL Shell ile
mysqlsh root@node1:3306

# Shell içinde
js
dba.rebootClusterFromCompleteOutage()

Primary Failover Testi

Üretim ortamına geçmeden önce failover testini mutlaka yapın. Bu senaryo gerçek bir tatbikat olarak düşünün:

# node1 (primary) üzerinde kasıtlı olarak MySQL'i durduralım
# Önce uygulamanızı bir süre izleyin

# node1'de
sudo systemctl stop mysql

# node2'den küme durumunu kontrol edin
mysql -u root -e "
SELECT MEMBER_HOST, MEMBER_STATE, MEMBER_ROLE
FROM performance_schema.replication_group_members;
"
# node2 ya da node3 otomatik olarak PRIMARY olmuş olmalı

# Failover süresini ölçmek için
watch -n 1 "mysql -u root -e 'SELECT MEMBER_HOST, MEMBER_ROLE FROM performance_schema.replication_group_members WHERE MEMBER_STATE="ONLINE";'"

Group Replication’ın otomatik primary seçimi genellikle 5-15 saniye içinde tamamlanır. Bu süreyi uygulamanızdaki connection retry mekanizması ile örtüştürmeniz kritik.

Uygulama Tarafında Connection Yönetimi

Group Replication’ı kurdunuz ama uygulamanız hala tek bir IP’ye bağlanıyorsa failover işe yaramaz. İki seçeneğiniz var:

MySQL Router kullanın. MySQL Router, Group Replication ile native entegrasyon sunuyor ve primary düğümü otomatik olarak uygulamaya yönlendiriyor:

# MySQL Router kurulumu
sudo apt install mysql-router -y

# Router yapılandırması
mysqlrouter --bootstrap root@node1:3306 --directory /etc/mysqlrouter --conf-base-port 6446 --user=mysqlrouter

# Servisi başlatın
sudo systemctl start mysqlrouter
sudo systemctl enable mysqlrouter

Router sonrası bağlantı bilgileri:

  • 6446: Read-Write (Primary’yi işaret eder)
  • 6447: Read-Only (Secondary’leri round-robin ile yük dağıtır)

ProxySQL alternatifi daha gelişmiş yük dengeleme ve query routing özellikleri sunuyor ancak konfigürasyon daha karmaşık. Kritik prodüksiyon ortamları için ProxySQL’i tercih ediyorum.

Sık Karşılaşılan Sorunlar ve Çözümleri

Sorun: Düğüm RECOVERING durumundan çıkmıyor

Bu genellikle recovery kullanıcısının erişim sorunundan kaynaklanıyor:

# node1 (primary) üzerinde
mysql -u root -e "
SELECT User, Host, plugin FROM mysql.user WHERE User='repl_user';
"

# Kullanıcı yoksa ya da şifre yanlışsa yeniden oluşturun
SET SQL_LOG_BIN=0;
DROP USER IF EXISTS 'repl_user'@'%';
CREATE USER 'repl_user'@'%' IDENTIFIED BY 'GucluSifre123!@#' REQUIRE SSL;
GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'%';
GRANT BACKUP_ADMIN ON *.* TO 'repl_user'@'%';
FLUSH PRIVILEGES;
SET SQL_LOG_BIN=1;

Sorun: Transaction conflict çok fazla

Multi-primary modda aynı satıra eş zamanlı yazma conflict’e yol açar. Bunu tespit etmek için:

mysql -u root -e "
SELECT 
  MEMBER_HOST,
  COUNT_CONFLICTS_DETECTED,
  COUNT_TRANSACTIONS_ROWS_VALIDATING
FROM performance_schema.replication_group_member_stats;
"

Conflict sayısı sürekli artıyorsa multi-primary’den single-primary moda geçmeyi düşünün.

Sorun: Group Replication başlamıyor, UUID çakışması

Eğer mevcut bir MySQL instance’ını kopyaladıysanız auto.cnf dosyasındaki server-uuid değeri çakışıyor olabilir:

# Her düğümde unique UUID olduğunu doğrulayın
cat /var/lib/mysql/auto.cnf

# Çakışma varsa dosyayı silin ve MySQL'i yeniden başlatın (yeni UUID üretir)
sudo systemctl stop mysql
sudo rm /var/lib/mysql/auto.cnf
sudo systemctl start mysql

Yedekleme Stratejisi

Group Replication kullanıyorsanız yedeklemeyi secondary bir düğümden almak primary’ye yük bindirmez:

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

BACKUP_DIR="/backup/mysql/$(date +%Y%m%d_%H%M)"
SECONDARY_HOST=$(mysql -u root -sN -e "
    SELECT MEMBER_HOST 
    FROM performance_schema.replication_group_members 
    WHERE MEMBER_ROLE='SECONDARY' 
    AND MEMBER_STATE='ONLINE' 
    LIMIT 1;")

if [ -z "$SECONDARY_HOST" ]; then
    echo "Secondary bulunamadi, primary'den yedek alinacak"
    SECONDARY_HOST="localhost"
fi

mkdir -p "$BACKUP_DIR"

xtrabackup --backup 
    --host=$SECONDARY_HOST 
    --user=backup_user 
    --password='YedekSifre789' 
    --target-dir="$BACKUP_DIR" 
    --compress 
    --compress-threads=4

if [ $? -eq 0 ]; then
    echo "$(date): Yedek basariyla alindi -> $BACKUP_DIR"
    # 7 gunden eski yedekleri sil
    find /backup/mysql -maxdepth 1 -type d -mtime +7 -exec rm -rf {} ;
else
    echo "$(date): YEDEK HATASI! Manuel kontrol gerekli."
fi

Performans Tuning İpuçları

Group Replication’da performansı etkileyen birkaç kritik parametre var:

  • group_replication_flow_control_mode: Varsayılan QUOTA, yoğun yazma trafiğinde hız sınırlaması yapıyor. Geçici olarak DISABLED yapabilirsiniz ama dikkatli olun.
  • group_replication_pipeline_stats_min_fact: Applier thread sayısını belirliyor, büyük tablolarda artırmak gecikmeyi azaltıyor.
  • group_replication_compression_threshold: Bu değer üzerindeki transaction’lar sıkıştırılıyor. Varsayılan 1000000 byte genellikle uygun.
  • slave_parallel_workers: Secondary’lerde parallel applier thread sayısı, yoğun ortamlarda 4-8 arasında tutun.
  • slave_parallel_type: LOGICAL_CLOCK olarak ayarlayın, bağımsız transaction’ları paralel uygulama imkanı tanıyor.

Sonuç

MySQL Group Replication, doğru kurulduğunda ve yönetildiğinde gerçekten sağlam bir yüksek erişilebilirlik çözümü sunuyor. Ancak sihirli bir kutup değil; uygulamanızın bağlantı katmanını da buna göre tasarlamanız şart. MySQL Router ya da ProxySQL entegrasyonu olmadan kurulan bir Group Replication kümesi, failover anında uygulamanızı yine de etkiliyor.

Prodüksiyona geçmeden önce şu kontrol listesini tamamlayın: tüm düğümler ONLINE durumunda mı, replikasyon kullanıcısı doğru yetkilerle oluşturulmuş mu, failover testi yapıldı mı, MySQL Router ya da benzer bir proxy katmanı devrede mi, yedekleme script’i çalışıyor mu ve monitoring alarm veriyor mu.

Group Replication ile ilgili en çok sorulan şey şu oluyor: “MariaDB Galera Cluster ile karşılaştırınca hangisi daha iyi?” Her ikisinin de güçlü yanları var. Galera daha uzun süredir olgun bir proje, Group Replication ise MySQL ekosistemiyle tam entegre. Eğer MySQL 8.x kullanıyorsanız ve Oracle desteğine ihtiyaç duyuyorsanız Group Replication, MariaDB 10.x kullanıyorsanız Galera çok daha mantıklı bir seçim.

Son olarak şunu söylemeliyim: bu yapıyı kurmak bir günlük iş ama sonradan gece uyandıran şeyleri önlüyor. Bir veritabanı sunucusu çöktüğünde uygulamanın sessizce diğer düğüme geçtiğini görmek, tüm bu kurulum çabasını anında değerli kılıyor.

Yorum yapın