İçeriğe geç

Bağlantı Havuzu — Büyük Havuz Hızlı Havuz Değildir

Orta 8 dk Çok sık karşılaşılır

Önce şunu oku: MVCC ve Kilitler — Eski Satır Sürümleri Nerede Yaşar?

30 saniyede özet

Veritabanına bağlanmak pahalıdır, bu yüzden bağlantılar bir havuzda hazır bekletilir. Havuzu büyütmek sorunu çözmez, kuyruğu veritabanının içine taşır. Zaman aşımlarının çoğu, bağlantıyı gereğinden uzun tutan koddan gelir.

Siparişler zaman aşımına uğramaya başlayınca ekip havuzdaki bağlantı sayısını 10’dan 100’e çıkardı. Bir hafta sonra asıl sebep bulundu: ödeme çağrısı transaction’ın içindeydi.

  1. Bayt: Siparişler zaman aşımına uğruyor! Havuzdaki bağlantı sayısını onu yüze çıkardık.

  2. Sen: Düzeldi mi?

  3. Bayt: Bir hafta boyunca tam anlayamadık. Sonra asıl suçluyu bulduk.

  4. Bayt: Dört kasalı bir markette içeri daha çok müşteri almak neyi değiştirir?

Havuz neden var?

Yeni bir veritabanı bağlantısı açmak ucuz değildir: ağ bağlantısı, TLS, kimlik doğrulama ve PostgreSQL’de sunucu tarafında yeni bir süreç. Bu yüzden uygulama açık bağlantıları bir connection poolAçık veritabanı bağlantılarını tutan ve isteklere ödünç veren havuz. Bağlantı açmanın maliyetini bir kez öder; boyutu veritabanının kapasitesine göre seçilir.Sözlükte gör →’da tutar ve istekler onları ödünç alıp iade eder.

Spring Boot’ta varsayılan havuz HikariCP’dir. Varsayılan maximumPoolSize 10, bağlantı bekleme süresi (connectionTimeout) 30 saniyedir.

Kafam karıştı, daha basit anlat

Her istek için yeni bağlantı açmak, her yolculuk için yeni araba almak gibidir. Havuz birkaç arabayı hazır tutar, herkes sırayla kullanır.

Hızlı kontrolBaşlangıç

Uygulamalar veritabanı bağlantılarını neden her istekte açıp kapatmak yerine bir havuzda tutar?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kaç bağlantı?

Veritabanı sunucusunun 4 çekirdeği var. Havuzu 10'dan 100'e çıkarmak yük altında sorguları hızlandırır mı? Cevabı göster

Genellikle hayır. Veritabanı aynı anda ancak kaynaklarının izin verdiği kadar işi verimli yapar. Fazla bağlantı, sorguları CPU, kilit ve önbellek için yarıştırır; bekleme havuzdan veritabanının içine taşınır.

Kasa sayısı sabit; içeri alınan müşteri sayısı değil.
Adım adım oku
  1. Veritabanı sunucusunun dört çekirdeği, dört kasa gibidir: aynı anda gerçekten çalışabilen sorgu sayısı sınırlıdır.
  2. Havuz büyüyünce içeri daha çok bağlantı girer; ama kasa sayısı aynı kaldığı için kuyruk yalnızca kasaların önüne taşınır.
  3. Transaction içinde yapılan dış ödeme çağrısı, kasada telefonla konuşan müşteri gibi bağlantıyı hiçbir iş yapmadan tutar.
  4. Dış çağrı transaction'dan önce ya da sonra yapılınca bağlantı yalnızca gerçekten veritabanı işi varken tutulur.

PostgreSQL wiki’si ve HikariCP belgeleri başlangıç noktası olarak şu formülü verir: bağlantı = (çekirdek × 2) + etkin disk sayısı. Bu bir başlangıçtır; son boyutu yük testinde bekleme ve gecikmeye bakarak ayarla.

Birden çok pod varsa toplam önemlidir: pod sayısı × havuz boyutu, veritabanının bağlantı sınırını (PostgreSQL’de varsayılan max_connections 100) aşmamalı.

Kafam karıştı, daha basit anlat

Daha fazla araba her zaman daha hızlı demek değildir. Yol dar ise arabalar sadece trafikte bekler. Veritabanı da aynı anda ancak belli sayıda işi iyi yapar.

Hızlı kontrolOrta

Veritabanı 4 çekirdekli, SSD kullanıyor. Havuz boyutu için makul bir başlangıç noktası hangisi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Servis 12 pod'a ölçeklendi, her birinde havuz boyutu 20. PostgreSQL'in max_connections ayarı varsayılan değerde (100). Ne olur?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin gör

Büyük havuz, hızlı havuz mu?

Tohum 227362

Oynat ya da adımla.

Hız
Adım 0

Şu an ne oldu?

Çok büyük (100) havuz

Her istek veritabanına gitmeden önce havuzdan bir bağlantı alıyor. Kim bekleyecek: istek mi, veritabanı mı?

Görevler0/3

  • Bekleyen istek olmadan veritabanını boğaçık

    İpucu

    Havuzu büyüt.

  • Veritabanı boştayken istekleri timeout'a düşüraçık

    İpucu

    Bağlantıyı veritabanı dışında bir şeyi beklerken tut.

  • Yavaş dış çağrı senaryosunda dengeyi kuraçık

    İpucu

    Önce kök neden, sonra boyut.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

  1. Varsayılanla oynat. Çok büyük havuz: kimse beklemedi, veritabanı boğuldu.
  2. Havuzu “formülden” yap. Dengeli.
  3. Durumu “yavaş dış çağrı” yap. Veritabanı boşta, istekler timeout alıyor.
  4. Havuzu büyüt. Timeout gitti, açık transaction’lar birikti.
  5. Formüle dön ve “kök nedeni düzelt”i aç. Denge geri geldi.
Hızlı kontrolOrta

Simülatörde 'yavaş dış çağrı' senaryosunda havuz büyütülünce timeout'lar gitti ama veritabanında açık transaction'lar birikti. Doğru çözüm ne?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Bağlantıyı kim tutuyor?

Havuzun boyutu kadar önemli olan, her bağlantının ne kadar süre tutulduğudur. İki klasik suçlu var:

  • Transaction içinde dış çağrı: HTTP isteği beklenirken bağlantı da, transaction da açık kalır.
  • bağlantı sızıntısıHavuzdan alınan bir bağlantının iade edilmemesi. Her sızıntı havuzu bir bağlantı küçültür; sonunda bütün istekler bağlantı beklerken timeout alır.Sözlükte gör →: bir hata yolu bağlantıyı iade etmez; havuz yavaş yavaş boşalır.

HikariCP’de leakDetectionThreshold, belirli bir süreden uzun tutulan bağlantıyı alan kod satırını log’a yazar. hikaricp.connections.pending ve active metrikleri de havuzun ne kadar dolu olduğunu gösterir.

Bir bankanın para transferi servisine bakalım. Para çıkmadan önce bir fraud servisine sorulur; o cevabı beklerken bağlantının nerede olduğu, bütün farkı yaratıyor.

Derinleş · Para transferi: bağlantı yalnızca gerektiği kadar 4 dosya · ~68 satır · ilk okumada atlayabilirsin
Proje dosyaları

src/main/resources/ application.yml Havuz ayarları: küçük bir havuz ve uzun tutulan bağlantıyı log'a yazan leak detection.

src/main/resources/application.yml
spring:
datasource:
hikari:
maximum-pool-size: 10 # start from (cores x 2) + disks, then measure
connection-timeout: 30000 # ms a request may wait for a free connection (default)
leak-detection-threshold: 10000 # logs the stack trace of code holding one longer than this

src/main/java/com/bank/transfer/ TransferService.java Fraud kontrolü transaction'dan önce yapılır; bu sırada elde bağlantı yok.

src/main/java/com/bank/transfer/TransferService.java
record TransferRequest(String fromIban, String toIban, BigDecimal amount,
Currency currency, String reference) {}
@Service
class TransferService {
private final FraudClient fraud;
private final TransferPosting posting;
TransferService(FraudClient fraud, TransferPosting posting) {
this.fraud = fraud;
this.posting = posting;
}
// Not @Transactional: the slow HTTP call runs with no connection borrowed.
TransferResult send(TransferRequest req) {
FraudDecision decision = fraud.check(req);
if (decision.rejected()) {
return TransferResult.rejected(decision.reason());
}
return posting.post(req); // a separate bean, so its @Transactional proxy applies
}
}

src/main/java/com/bank/transfer/ TransferPosting.java Bağlantı yalnızca borç ve alacak kayıtlarını yazan kısa transaction boyunca tutulur.

src/main/java/com/bank/transfer/TransferPosting.java
@Component
class TransferPosting {
private final AccountRepository accounts;
private final LedgerRepository ledger;
TransferPosting(AccountRepository accounts, LedgerRepository ledger) {
this.accounts = accounts;
this.ledger = ledger;
}
// The connection is borrowed here and returned at commit: only database work inside.
@Transactional
public TransferResult post(TransferRequest req) {
Account from = accounts.lockByIban(req.fromIban()); // SELECT ... FOR UPDATE
from.debit(req.amount(), req.currency()); // throws if balance or limit is short
ledger.save(LedgerEntry.debit(from, req));
ledger.save(LedgerEntry.credit(req.toIban(), req));
return TransferResult.accepted(req.reference());
}
}

counter-example/ TransferServiceHoldingTheConnection.java Şöyle de yazılabilirdi, ama bak ne oluyor: fraud cevabı beklenirken bağlantı da satır kilidi de boşta bekliyor.

counter-example/TransferServiceHoldingTheConnection.java
// Works fine at low traffic. Under load, look at what the connection does meanwhile.
@Transactional
public TransferResult send(TransferRequest req) {
Account from = accounts.lockByIban(req.fromIban()); // connection borrowed, row locked
// While the fraud service answers, the connection sits idle and the account row
// stays locked. With ten slow answers in flight the pool is empty, and the next
// transfer waits connectionTimeout even though the database has nothing to do.
FraudDecision decision = fraud.check(req);
if (decision.rejected()) {
return TransferResult.rejected(decision.reason());
}
from.debit(req.amount(), req.currency());
ledger.save(LedgerEntry.debit(from, req));
ledger.save(LedgerEntry.credit(req.toIban(), req));
return TransferResult.accepted(req.reference());
}
Kafam karıştı, daha basit anlat

Arabayı alıp park yerinde bekletme. Transaction içinde başka bir servise gidip cevap beklersen, bağlantı da o süre boyunca boşuna tutulur.

Hızlı kontrolOrta

Servis birkaç gün çalıştıktan sonra her istekte 'Connection is not available, request timed out' veriyor. Hatalı satır hangisi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Hatalı satıra dokun, sonra kontrol et.

ReportDao.java
Java 21UTF-8LF

Her belirtiyi en olası nedenine göre ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Havuz gerçekten küçük

Veritabanı rahat, bağlantılar kısa tutuluyor ama hepsi dolu.

    Bağlantı uzun tutuluyor

    Bağlantılar iş yapmadan bekliyor ya da hiç dönmüyor.

      Havuz fazla büyük

      Veritabanı aşırı yüklü.

        Tuzaklar

        Timeout’a havuz büyüterek cevap vermek. Kök neden yavaş bir sorgu ya da dış çağrıysa, büyük havuz sorunu veritabanına taşır.

        Otomatik ölçeklenen pod’lar. Pod sayısı iki katına çıkınca bağlantı sayısı da iki katına çıkar; veritabanının sınırı değişmez.

        PgBouncerPostgreSQL önünde çalışan hafif bir bağlantı havuzu. Çok sayıda istemci bağlantısını az sayıda sunucu bağlantısına paylaştırır.Sözlükte gör → transaction modu. Çok sayıda istemciyi az sayıda bağlantıya böler; ama oturuma bağlı özellikler (SET, advisory lock, bazı sürümlerde prepared statement) beklendiği gibi çalışmayabilir.

        Bağlantıyı elle almak. DataSource.getConnection() ile alınan bağlantı try-with-resources ile kapatılmazsa, ilk exception onu sızdırır.

        Hızlı kontrolİleri

        PgBouncer transaction pooling moduna geçildikten sonra bazı özellikler garip davranıyor. Hangisi büyük olasılıkla etkilenir?

        Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

        Kendini sına

        Şimşek turu1/5

        Yeni bir veritabanı bağlantısı açmak ucuzdur.

        Soru 1/2Orta

        Siparişlerde 'Connection is not available' hataları var. Ekip havuzu 10'dan 100'e çıkarmayı öneriyor. Önce neye bakarsın?

        Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

        Aklında kalacak üç şey

        1. 1 Veritabanı aynı anda ancak işlemci ve diskinin izin verdiği kadar işi verimli yapar. Fazla bağlantı sorguları birbiriyle yarıştırır.
        2. 2 Havuz boyutu için başlangıç noktası (çekirdek × 2) + disk sayısıdır; son karar ölçerek verilir. Sunucu sayısı × havuz boyutu, veritabanının bağlantı sınırını aşmamalıdır.
        3. 3 Bağlantı zaman aşımında önce bağlantıyı kimin, neden uzun tuttuğuna bak: transaction içinde yapılan bir dış çağrı ya da geri verilmeyen bir bağlantı.
        Sonraki kapı Ödemeleri aya göre dört veritabanına böldün. Bugünün bütün ödemeleri hangisine gidiyor? Sharding ve Shard Anahtarı — Veriyi Bölmenin Tek Önemli Kararı · 9 dk

        4 kart sonraki derste seni bekliyor

        0/5 kart bu dersten toplandı