MySQL Bağlantı Havuzu Sorunları ve Çözüm Yöntemleri

Prodüksiyonda bir sabah uyandığında uygulamanın “Too many connections” hatası fırlattığını görmen kadar sinir bozucu çok az şey vardır. MySQL bağlantı havuzu sorunları, deneyimli sysadmin’lerin bile saatlerini hatta günlerini harcadığı karmaşık bir alan. Bu yazıda gerçek dünya senaryoları üzerinden MySQL bağlantı havuzu sorunlarını nasıl tespit edeceğini, neden kaynaklandığını ve nasıl çözeceğini ele alacağız.

MySQL Bağlantı Havuzu Nedir ve Neden Önemlidir

MySQL, her gelen bağlantı için yeni bir thread oluşturur. Bu thread’ler bellek ve CPU tüketir. Bağlantı havuzu (connection pool) kavramı, bu bağlantıları önceden açık tutarak her istek için yeniden bağlantı kurma maliyetini ortadan kaldırmak amacıyla geliştirilmiştir.

Uygulama tarafında ProxySQL, PgBouncer benzeri araçlar veya Java’daki HikariCP, Python’daki SQLAlchemy connection pool gibi kütüphaneler bu işi yapar. MySQL tarafında ise thread_cache_size ve max_connections gibi parametreler devreye girer.

Sorun şu ki, bağlantı havuzu doğru yapılandırılmadığında hem çok az bağlantıyla kaynak açlığı yaşarsın hem de çok fazla bağlantıyla MySQL’i boğarsın.

Mevcut Durumu Anlamak: İlk Adım Teşhis

Herhangi bir müdahale yapmadan önce ne olduğunu anlamak gerekiyor. MySQL’e bağlanıp mevcut durumu kontrol etmekle başlayalım.

# MySQL'e bağlan ve genel bağlantı durumunu gör
mysql -u root -p -e "SHOW STATUS LIKE '%connect%';"

Bu komutun çıktısında dikkat etmen gereken değerler:

  • Connections: Sunucu başladığından beri toplam bağlantı sayısı
  • Max_used_connections: Şimdiye kadar aynı anda kullanılan maksimum bağlantı sayısı
  • Threads_connected: Şu an aktif açık bağlantı sayısı
  • Threads_running: Şu an gerçekten sorgu çalıştıran thread sayısı
  • Connection_errors_max_connections: Maksimum bağlantı limitine çarpılma sayısı
  • Aborted_connects: Başarısız bağlantı denemeleri
# Daha detaylı thread bilgisi
mysql -u root -p -e "SHOW STATUS LIKE 'Thread%';"

# Mevcut açık bağlantıları listele
mysql -u root -p -e "SHOW PROCESSLIST;"

# Daha uzun sorguları görmek için
mysql -u root -p -e "SHOW FULL PROCESSLIST;"

SHOW PROCESSLIST çıktısında Sleep durumundaki bağlantılar çok fazlaysa, bu bağlantıların düzgün kapatılmadığının işareti. Bu “connection leak” yani bağlantı sızıntısı sorununa işaret eder.

Yaygın Senaryo 1: Too Many Connections Hatası

En sık karşılaşılan senaryo. Uygulama loglarında ERROR 1040 (HY000): Too many connections görüyorsun. İlk yapacağın iş mevcut limiti kontrol etmek:

# Mevcut max_connections değerini gör
mysql -u root -p -e "SHOW VARIABLES LIKE 'max_connections';"

# Şu an kaç bağlantı var
mysql -u root -p -e "SHOW STATUS LIKE 'Threads_connected';"

# Geçmişte en yüksek kaç bağlantı kullanıldı
mysql -u root -p -e "SHOW STATUS LIKE 'Max_used_connections';"

Diyelim ki max_connections 151 (varsayılan değer) ve Max_used_connections 149 çıktı. Bu kritik bir durumu gösteriyor. Acil geçici çözüm olarak bu değeri dinamik olarak artırabilirsin:

# Çalışan MySQL'de dinamik olarak değiştir (kalıcı değil, restart sonrası sıfırlanır)
mysql -u root -p -e "SET GLOBAL max_connections = 500;"

# Kalıcı hale getirmek için my.cnf'e ekle
sudo vim /etc/mysql/my.cnf

my.cnf içine şunu ekle:

[mysqld]
max_connections = 500
max_connect_errors = 1000000
wait_timeout = 300
interactive_timeout = 300

Burada wait_timeout ve interactive_timeout değerleri önemli. Bu değerler, boşta bekleyen bağlantıların kaç saniye sonra otomatik kapatılacağını belirler. Varsayılan değer 8 saat (28800 saniye) ki bu çoğu durumda çok fazla.

Yaygın Senaryo 2: Bağlantı Sızıntısı (Connection Leak)

Bir e-ticaret şirketi için çalışırken şöyle bir senaryo yaşadım: Uygulama hızla bağlantı tüketiyor ama trafik o kadar yoğun değildi. SHOW PROCESSLIST çıktısına baktığımda yüzlerce Sleep durumunda bağlantı gördüm.

# Sleep durumundaki bağlantıları say
mysql -u root -p -e "SELECT COUNT(*) FROM information_schema.processlist WHERE command='Sleep';"

# Hangi kullanıcılardan Sleep bağlantısı geliyor
mysql -u root -p -e "SELECT user, host, COUNT(*) as conn_count FROM information_schema.processlist WHERE command='Sleep' GROUP BY user, host ORDER BY conn_count DESC;"

# Uzun süredir bekleyen Sleep bağlantıları
mysql -u root -p -e "SELECT id, user, host, db, time FROM information_schema.processlist WHERE command='Sleep' AND time > 60 ORDER BY time DESC;"

Bu sorgu, 60 saniyeden uzun süredir boşta bekleyen bağlantıları listeliyor. Bunları elle öldürmek gerekebilir:

# Belirli bir bağlantıyı öldür
mysql -u root -p -e "KILL CONNECTION <process_id>;"

# Toplu olarak kill etmek için script
mysql -u root -p -e "SELECT CONCAT('KILL ', id, ';') FROM information_schema.processlist WHERE command='Sleep' AND time > 300;" | mysql -u root -p

Uzun vadeli çözüm ise uygulama kodunu düzeltmek. Python SQLAlchemy kullanan bir uygulamada şu yapılandırma bağlantı sızıntısını önler:

# Python uygulaması için environment variable olarak ayarla
# ya da doğrudan uygulama config'ine yaz
# SQLAlchemy pool ayarları:
# pool_size=10 (havuzdaki kalıcı bağlantı sayısı)
# max_overflow=20 (havuz dolunca açılabilecek ekstra bağlantı)
# pool_timeout=30 (bağlantı beklerken timeout)
# pool_recycle=1800 (bağlantıyı 30 dakikada bir yenile)
# pool_pre_ping=True (bağlantı sağlığını kontrol et)

export SQLALCHEMY_POOL_SIZE=10
export SQLALCHEMY_MAX_OVERFLOW=20
export SQLALCHEMY_POOL_RECYCLE=1800

ProxySQL ile Bağlantı Havuzu Yönetimi

Ciddi bir production ortamında doğrudan MySQL’e bağlanmak yerine ProxySQL gibi bir middleware kullanmak çok daha sağlıklı. ProxySQL, bağlantı havuzunu uygulama ile MySQL arasında yönetir.

# ProxySQL kurulumu (CentOS/RHEL)
cat > /etc/yum.repos.d/proxysql.repo << EOF
[proxysql_repo]
name= ProxySQL YUM repository
baseurl=https://repo.proxysql.com/ProxySQL/proxysql-2.5.x/centos/$releasever
gpgcheck=1
gpgkey=https://repo.proxysql.com/ProxySQL/repo_pub_key
EOF

yum install proxysql -y
systemctl start proxysql
systemctl enable proxysql

ProxySQL kurulduktan sonra yönetim arayüzüne bağlan:

# ProxySQL admin arayüzüne bağlan (varsayılan port 6032)
mysql -u admin -padmin -h 127.0.0.1 -P 6032

# MySQL backend sunucularını ekle
INSERT INTO mysql_servers (hostgroup_id, hostname, port, max_connections) 
VALUES (1, '127.0.0.1', 3306, 200);

# Kullanıcı ekle
INSERT INTO mysql_users (username, password, default_hostgroup) 
VALUES ('appuser', 'password', 1);

# Ayarları kaydet ve yükle
LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;
LOAD MYSQL USERS TO RUNTIME;
SAVE MYSQL USERS TO DISK;

ProxySQL’in connection pool istatistiklerini izlemek:

# ProxySQL bağlantı pool istatistikleri
mysql -u admin -padmin -h 127.0.0.1 -P 6032 -e "SELECT * FROM stats_mysql_connection_pool;"

Bu çıktıda dikkat etmen gereken alanlar:

  • ConnUsed: Şu an kullanılan bağlantı sayısı
  • ConnFree: Havuzda boşta bekleyen bağlantı sayısı
  • ConnOK: Başarılı bağlantı sayısı
  • ConnERR: Hatalı bağlantı sayısı
  • Latency_us: Microsecond cinsinden gecikme

MySQL Thread Cache Optimizasyonu

Bağlantı sorunlarının bir diğer önemli boyutu thread cache. Her bağlantı için sıfırdan thread açmak maliyetli. thread_cache_size bu maliyeti azaltır.

# Thread cache durumunu kontrol et
mysql -u root -p -e "SHOW STATUS LIKE 'Threads_created';"
mysql -u root -p -e "SHOW VARIABLES LIKE 'thread_cache_size';"

# Thread cache hit rate hesapla
mysql -u root -p << 'EOF'
SELECT 
    (1 - (Threads_created / Connections)) * 100 AS thread_cache_hit_rate
FROM (
    SELECT 
        (SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME = 'Threads_created') AS Threads_created,
        (SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME = 'Connections') AS Connections
) AS t;
EOF

Thread cache hit rate’in %90’ın üzerinde olması gerekiyor. Düşükse thread_cache_size artır:

# Dinamik olarak ayarla
mysql -u root -p -e "SET GLOBAL thread_cache_size = 100;"

# my.cnf'e ekle (kalıcı)
echo "thread_cache_size = 100" >> /etc/mysql/conf.d/performance.cnf

Monitoring: Sorunları Önceden Yakalamak

Sorun olduktan sonra müdahale etmek yerine önceden tespit etmek çok daha iyisi. Basit bir bash script ile periyodik monitoring yapabilirsin:

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

MYSQL_USER="monitor"
MYSQL_PASS="monitorpass"
THRESHOLD=80  # max_connections'in %80'i
LOG_FILE="/var/log/mysql_connection_monitor.log"
ALERT_EMAIL="[email protected]"

MAX_CONN=$(mysql -u$MYSQL_USER -p$MYSQL_PASS -se "SHOW VARIABLES LIKE 'max_connections';" | awk '{print $2}')
CURR_CONN=$(mysql -u$MYSQL_USER -p$MYSQL_PASS -se "SHOW STATUS LIKE 'Threads_connected';" | awk '{print $2}')
SLEEP_CONN=$(mysql -u$MYSQL_USER -p$MYSQL_PASS -se "SELECT COUNT(*) FROM information_schema.processlist WHERE command='Sleep';")

USAGE_PCT=$(( (CURR_CONN * 100) / MAX_CONN ))

TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "$TIMESTAMP - Max: $MAX_CONN, Current: $CURR_CONN, Sleep: $SLEEP_CONN, Usage: %$USAGE_PCT" >> $LOG_FILE

if [ $USAGE_PCT -ge $THRESHOLD ]; then
    echo "UYARI: MySQL bağlantı kullanimi %$USAGE_PCT seviyesinde! ($CURR_CONN / $MAX_CONN)" | 
    mail -s "[ALERT] MySQL Connection Pool Kritik - $(hostname)" $ALERT_EMAIL
fi

# Crontab'a eklemek için:
# */5 * * * * /usr/local/bin/mysql_conn_monitor.sh

Gerçek Dünya Senaryosu: PHP Uygulamasında Bağlantı Havuzu Sorunu

PHP + Laravel kullanan bir uygulamada yaşanan bağlantı patlamasını inceleyelim. PHP’nin persistent connections özelliği bazen beklenmedik davranışlar yaratır.

# PHP-FPM worker sayısını kontrol et
grep -E "pm.max_children|pm.start_servers|pm.min_spare_servers|pm.max_spare_servers" /etc/php-fpm.d/www.conf

# Her worker bir MySQL bağlantısı tutabilir
# pm.max_children = 50 ise teorik maksimum bağlantı = 50
# Birden fazla sunucu varsa bu sayı ile çarp

# MySQL'den hangi hostlardan bağlantı geldiğini gör
mysql -u root -p -e "SELECT host, COUNT(*) as count FROM information_schema.processlist GROUP BY host ORDER BY count DESC;"

Bu durumda max_connections değerini şöyle hesaplamalısın:

  • PHP-FPM max_children: 50
  • Web sunucu sayısı: 3
  • Teorik max bağlantı: 150
  • Güvenlik marjı ile: 200-250 olarak ayarla

Ayrıca PHP uygulamalarında şu my.cnf ayarı önemli:

[mysqld]
# PHP uygulamaları için optimize edilmiş ayarlar
max_connections = 300
wait_timeout = 180
interactive_timeout = 180
net_read_timeout = 30
net_write_timeout = 30

# Bağlantı havuzu için önemli
thread_cache_size = 50
thread_stack = 256K

MySQL 8.0’da Bağlantı Havuzu İyileştirmeleri

MySQL 8.0 ile gelen connection_control plugin ve geliştirilmiş performance_schema tabloları teşhisi kolaylaştırıyor:

# Performance Schema ile detaylı bağlantı analizi (MySQL 8.0+)
mysql -u root -p << 'EOF'
SELECT 
    processlist_user,
    processlist_host,
    processlist_db,
    processlist_command,
    COUNT(*) as connection_count,
    AVG(processlist_time) as avg_time_seconds
FROM performance_schema.threads
WHERE processlist_command IS NOT NULL
GROUP BY processlist_user, processlist_host, processlist_db, processlist_command
ORDER BY connection_count DESC;
EOF

# En uzun süredir bekleyen bağlantıları bul
mysql -u root -p -e "
SELECT 
    t.processlist_id,
    t.processlist_user,
    t.processlist_host,
    t.processlist_time,
    t.processlist_state,
    LEFT(t.processlist_info, 100) as query_preview
FROM performance_schema.threads t
WHERE t.processlist_command = 'Sleep'
AND t.processlist_time > 120
ORDER BY t.processlist_time DESC
LIMIT 20;"

Bağlantı Havuzu Sorunlarını Kalıcı Çözmek

Geçici yamalar yerine kalıcı çözüm için bir checklist:

Uygulama tarafı:

  • Connection pool kütüphanesi kullan (HikariCP, c3p0, SQLAlchemy pool)
  • Her zaman try-finally ya da with bloğu ile bağlantıları kapat
  • Uygulama başlatılırken pool boyutunu doğru ayarla
  • pool_recycle ile bağlantıları düzenli yenile
  • pool_pre_ping ile dead connection’ları tespit et

MySQL tarafı:

  • max_connections değerini gerçekçi hesapla
  • wait_timeout ve interactive_timeout değerlerini düşür (300-600 saniye arası)
  • thread_cache_size değerini optimize et
  • Düzenli SHOW PROCESSLIST analizi yap

Altyapı tarafı:

  • ProxySQL veya benzeri connection pooler kullan
  • Monitoring ve alerting kur
  • Load balancer health check’lerini MySQL’e yönlendir

Kapasiteyi doğru hesapla:

  • Toplam bağlantı ihtiyacı = (Uygulama sunucu sayısı) x (Her sunucudaki max worker) x (Her worker’ın açabileceği bağlantı)
  • Bu sayının %20 üstüne max_connections ayarla
  • Yönetim erişimi için minimum 10-20 bağlantıyı rezerve tut

Acil Durum Müdahalesi

Her şey kötüye gittiğinde ve uygulama tamamen cevap vermez hale geldiğinde:

# Hızlıca tüm Sleep bağlantılarını öldür
mysql -u root -p -e "
SELECT GROUP_CONCAT(CONCAT('KILL ', id) SEPARATOR '; ')
FROM information_schema.processlist 
WHERE command = 'Sleep' 
AND time > 60
AND user != 'root';" | mysql -u root -p

# ya da daha agresif yaklaşım - belirli kullanıcının tüm bağlantıları
mysql -u root -p -e "
SELECT CONCAT('KILL ', id, ';') 
FROM information_schema.processlist 
WHERE user = 'appuser' 
AND command = 'Sleep';" | grep KILL | mysql -u root -p

# Geçici olarak max_connections artır
mysql -u root -p -e "SET GLOBAL max_connections = 1000;"

# MySQL'in durumunu anlık izle
watch -n 2 'mysql -u root -pSIFRE -e "SHOW STATUS LIKE "Threads_%"; SHOW STATUS LIKE "Connections";"'

Sonuç

MySQL bağlantı havuzu sorunları genellikle birden fazla katmanda birbirine bağlı problemlerden kaynaklanır. Uygulama bağlantıları düzgün kapatmıyor olabilir, MySQL konfigürasyonu yetersiz kalıyor olabilir ya da trafik artışı mevcut kapasiteyi aşmış olabilir.

En önemli takeaway’ler şunlar: Düzenli SHOW PROCESSLIST analizi ile sorunları erken yakala, wait_timeout değerini makul bir seviyeye çek, uygulama tarafında connection pool kütüphanelerini doğru yapılandır ve production ortamlarda mutlaka ProxySQL gibi bir connection pooler kullan.

Monitoring olmadan yönetemezsin, yönetemediğini optimize edemezsin. Basit bir bash scripti bile olsa MySQL bağlantı metriklerini izlemek seni gece 3’te “Too many connections” alarmından kurtarabilir. Bu yazıda ele aldığımız senaryolar gerçek ortamlarda sık karşılaştığım durumlar, umarım benzer sorunlarla karşılaştığında işe yarar.

Bir yanıt yazın

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