Yoğun bir oyun sunucusunda ya da trafikli bir web uygulamasında bir MySQL connection pool (bağlantı havuzu), performansı en ucuza artıran çözümlerden biridir. Çünkü her veritabanı işlemi için sıfırdan bir bağlantı açıp kapatmak göründüğünden çok daha pahalıdır: TCP el sıkışması, kimlik doğrulama, karakter seti müzakeresi ve sunucu tarafında bir thread tahsisi… Bunların hepsi her istekte tekrar tekrar yapılırsa, asıl sorguların kendisinden daha fazla zaman ve CPU harcarsın. Bu yazıda bağlantı havuzunun ne olduğunu, sunucu yükünü neden ve nasıl azalttığını, ve farklı dillerde nasıl doğru kurulacağını anlatıyorum.
Bağlantı açmak neden pahalıdır?
Bir mysql_real_connect ya da yeni bir PDO nesnesi oluşturduğunda perde arkasında şunlar olur:
- İstemci ile sunucu arasında bir TCP bağlantısı kurulur (üç adımlı el sıkışma; uzak sunucuda ek gidiş-dönüş gecikmesi).
- MySQL bir handshake paketi gönderir, istemci kullanıcı adı/parola ile yanıt verir ve sunucu bunu doğrular.
- SSL/TLS açıksa ayrıca bir şifreleme el sıkışması yapılır.
- Sunucu bu bağlantıya hizmet etmek için bir thread ayırır ve oturum değişkenlerini başlatır.
Tek bir bağlantı için bu birkaç milisaniye sürebilir; ama saniyede yüzlerce istek alan bir sunucuda her istek için bu maliyeti ödemek, hem gecikmeyi büyütür hem de MySQL'in max_connections limitine hızla dayanmana yol açar. İşte bağlantı havuzu tam burada devreye girer: bağlantıları bir kez açar, kullandıktan sonra kapatmak yerine havuza geri verir ve bir sonraki istek hazır bağlantıyı yeniden kullanır.
Connection pool tam olarak ne yapar?
Bir bağlantı havuzu, önceden açılmış ve kullanıma hazır bir grup veritabanı bağlantısını bellekte tutan bir bileşendir. Uygulama bir bağlantı istediğinde havuz boştaki bir bağlantıyı ödünç verir; iş bitince bağlantı kapatılmaz, havuza iade edilir. Tipik ayarlar şunlardır:
- Minimum havuz boyutu: sürekli açık tutulacan en az bağlantı sayısı (soğuk başlangıç gecikmesini önler).
- Maksimum havuz boyutu: aynı anda izin verilen en fazla bağlantı; MySQL'in
max_connectionsdeğerinden küçük tutulmalıdır. - Idle timeout: uzun süre kullanılmayan bağlantıların kapatılma süresi.
- Bağlantı doğrulama: havuzdan verilmeden önce bağlantının hâlâ canlı olduğunu kontrol etme (genelde basit bir ping ya da
SELECT 1).
Sonuç: el sıkışma maliyeti uygulamanın ömrü boyunca yalnızca havuz boyutu kadar ödenir; binlerce istek için değil. Bu, hem istek başına gecikmeyi düşürür hem de MySQL sunucusundaki thread oluşturma/yok etme yükünü neredeyse sıfırlar.
PHP / Laravel: persistent bağlantılar
PHP'nin klasik "her istek sıfırdan başlar" modeli, geleneksel bir havuzu zorlaştırır çünkü süreç istek bitince ölür. Çözüm, PHP-FPM süreçlerinin yaşadığı sürece bağlantıyı canlı tutan persistent (kalıcı) bağlantılardır. PDO ile:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
Laravel'de aynısını config/database.php içinde yaparsın:
'mysql' => [
'driver' => 'mysql',
// ...
'options' => [
PDO::ATTR_PERSISTENT => true,
],
],
Burada bir uyarı: persistent bağlantılar her PHP-FPM worker'ı için ayrı bir bağlantı tutar. Worker sayın yüksekse toplam bağlantı sayısı MySQL'in limitini aşabilir. Worker sayısını ve max_connections değerini birlikte planla. Gerçek bir havuz (boyut kontrolü, sağlık kontrolü) istiyorsan PHP süreçlerinin önüne ProxySQL koymak daha güçlü bir çözümdür.
Node.js: gerçek bir havuz örneği
Node tek süreçli ve uzun ömürlü olduğu için bağlantı havuzu burada doğal oturur. mysql2 kütüphanesiyle:
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: '127.0.0.1',
user: 'game',
password: process.env.DB_PASS,
database: 'server',
connectionLimit: 20,
waitForConnections: true,
queueLimit: 0,
});
const [rows] = await pool.query(
'SELECT level FROM players WHERE id = ?', [playerId]
);
pool.query() her çağrıda havuzdan bir bağlantı alır, sorguyu çalıştırır ve bağlantıyı otomatik iade eder. Transaction gibi aynı bağlantıyı gerektiren işlemlerde pool.getConnection() ile bağlantıyı manuel alıp işin sonunda connection.release() ile geri verirsin — iade etmeyi unutmak havuzu tüketen en yaygın hatadır.
Oyun sunucusu (C++) tarafı
Metin2 tabanlı ya da kendi yazdığın bir C++ oyun sunucusunda genelde hazır bir ORM yoktur; havuzu kendin yönetirsin. Mantık aynıdır: başlangıçta N adet bağlantı açıp bir kuyruğa (thread-safe queue) koyarsın, bir thread sorgu çalıştıracağında kuyruktan bir bağlantı çeker, bitince geri koyar. Önemli noktalar:
- Thread güvenliği: havuza erişimi bir mutex ya da kilitsiz kuyrukla koru. Aynı MySQL bağlantısını iki thread aynı anda kullanamaz.
- Yeniden bağlanma:
wait_timeoutdolduğunda MySQL boştaki bağlantıyı kapatır ve "MySQL server has gone away" hatası alırsın. Havuzdan vermeden öncemysql_ping()ile kontrol et, ölmüşse yeniden bağlan. - Boyutlandırma: oyun çekirdeğindeki DB thread sayısına göre havuzu ayarla; gereğinden fazla bağlantı MySQL belleğini boşa harcar.
Havuzu doğru boyutlandırmak
En sık yapılan hata, "ne kadar çok bağlantı o kadar iyi" sanmaktır. Aslında tam tersidir: çok fazla eşzamanlı bağlantı, MySQL'de bağlam değiştirme (context switch) ve kilit çekişmesini artırarak işleri yavaşlatır. Pratik bir başlangıç noktası, CPU çekirdek sayısı ile disk eşzamanlılığını temel alan görece küçük bir havuzdur (çoğu uygulama için çekirdek başına birkaç bağlantı yeterlidir). Ölçerek doğrula:
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Threads_running';
SHOW STATUS LIKE 'Max_used_connections';
Threads_running sürekli yüksekse darboğaz havuz değil, sorgularındır; önce yavaş sorguları ve eksik indeksleri düzelt. Max_used_connections, max_connections'a dayanıyorsa ya havuz limitlerini düşür ya da gerçekten daha fazla kapasiteye ihtiyacın var demektir.
Sık Sorulan Sorular
MySQL'in kendi içinde bir bağlantı havuzu var mı?
Hayır, klasik bağlantı havuzu istemci tarafı bir kavramdır. Sunucu tarafında MySQL Enterprise ve MariaDB'de bir "thread pool" eklentisi vardır; bu, bağlantıları az sayıda thread'le yönetir ama bağlantı kurma maliyetini ortadan kaldırmaz. İkisi farklı sorunları çözer: thread pool sunucudaki thread patlamasını, connection pool ise istemcideki bağlantı açma maliyetini.
Persistent bağlantı ile connection pool aynı şey mi?
Tam olarak değil. Persistent bağlantı, bir bağlantıyı istek sonrası kapatmayıp yeniden kullanır ama boyut ve sağlık yönetimi sınırlıdır. Gerçek bir havuz; minimum/maksimum boyut, idle timeout, sağlık kontrolü ve kuyruklama gibi politikalar sunar. PHP'de daha güçlü bir havuz için ProxySQL gibi bir ara katman kullanılır.
ProxySQL ne zaman gerekir?
Birden çok uygulama sunucusu varsa ve toplam bağlantı sayısını merkezi olarak sınırlamak, sorgu yönlendirme ya da okuma/yazma ayrımı yapmak istiyorsan ProxySQL mantıklıdır. Tek sunuculu küçük projelerde uygulama içi havuz çoğunlukla yeterlidir.
Veritabanı bağlantıların sunucunu boğuyor mu? Oyun sunucusu ya da web projen için connection pool kurulumu, boyutlandırma ve MySQL tarafı ayarlarında yardıma ihtiyacın varsa benimle iletişime geç; mevcut yapını inceleyip somut bir iyileştirme planı çıkarayım.