---
title: 'Bağlantı Havuzu'
source: 'https://academia.sh/tr/kurslar/veri-erisim-katmani/baglanti-havuzu'
course: 'Veri Erişim Katmanı ve İş Mantığı'
language: tr
updated: '2026-08-17T18:06:50+00:00'
license: 'CC BY-SA 4.0'
---

# Bağlantı Havuzu

Bağlantının pahalı kaynak olarak yönetilmesi: açma maliyetinin ölçülmesi, sabit boyutlu havuz ve kuyruk, havuz boyutu ile bekleme süresi ilişkisinin paralel isteklerle ölçülmesi, doygunlukta kuyruk zaman aşımı ve oturum durumunun taşınması.

Önceki iki ders sorguyu tek bir noktadan üretilir hâle getirdi. Sorgunun çalışabilmesi için
bir şey daha gerekir: bağlantı. Şimdiye kadar her betik kendi bağlantısını açtı, işini
bitirdi ve süreç sona erdi.

Sürekli çalışan bir servis böyle davranamaz. Bağlantı açmak bedava değildir ve
veritabanının kabul edeceği eşzamanlı bağlantı sayısı sınırlıdır. **Bağlantı havuzu**
(connection pool), önceden açılmış bağlantıları saklayan ve isteklere ödünç veren yapıdır.
Bu ders havuzu kurar, havuz boyutunu değiştirerek isteklerin kuyrukta ne kadar beklediğini
ölçer ve havuzun getirdiği iki tuzağı gösterir.

## Açma Maliyeti

İlk soru maliyetin gerçekten var olup olmadığıdır.

```js
// acma-maliyeti.mjs — her istekte baglanti acmak ile tek baglantiyi yeniden kullanmak
import { DatabaseSync } from "node:sqlite";

const N = 2000;
const SORGU = "SELECT count(*) AS n FROM odunc WHERE iade_tarihi IS NULL";

let t = performance.now();
for (let i = 0; i < N; i++) {
  const db = new DatabaseSync("kutuphane.db");
  db.prepare(SORGU).get();
  db.close();
}
const acKapa = performance.now() - t;

const db = new DatabaseSync("kutuphane.db");
t = performance.now();
for (let i = 0; i < N; i++) db.prepare(SORGU).get();
const yenidenKullan = performance.now() - t;
db.close();

console.log(`her istekte ac-kapa : ${acKapa.toFixed(0)} ms  (${(acKapa / N).toFixed(3)} ms/istek)`);
console.log(`yeniden kullan      : ${yenidenKullan.toFixed(0)} ms  (${(yenidenKullan / N).toFixed(3)} ms/istek)`);
console.log(`oran                : ${(acKapa / yenidenKullan).toFixed(1)}x`);
```

Aşağıdaki komut şemayı kurar ve ölçümü çalıştırır.

```sh
rm -f kutuphane.db
sqlite3 kutuphane.db <<'SQL'
CREATE TABLE odunc (odunc_id INTEGER PRIMARY KEY, kitap_id INTEGER NOT NULL,
                    uye_id INTEGER NOT NULL, alis_tarihi TEXT NOT NULL, iade_tarihi TEXT);
INSERT INTO odunc VALUES (1,1,1,'2025-01-10','2025-01-24'),(3,1,2,'2025-02-11',NULL),
  (7,5,4,'2025-04-21',NULL),(10,3,5,'2025-06-03',NULL),(12,4,4,'2025-06-20','2025-07-04');
SQL
node acma-maliyeti.mjs
```

```
her istekte ac-kapa : 113 ms  (0.057 ms/istek)
yeniden kullan      : 10 ms  (0.005 ms/istek)
oran                : 11.3x
```

Süreler donanıma ve dosya sistemine bağlıdır; başka bir makinede sayılar değişir. Anlamlı
olan orandır: aynı işi yapmak için bağlantıyı her seferinde açmak, açık bir bağlantıyı
yeniden kullanmaktan on bir kat pahalıya geldi. Bu ölçüm yerel bir dosya üzerindedir. Ağ
üzerinden konuşulan bir veritabanında açma maliyeti ayrıca oturum kurulumu, kimlik
doğrulama ve şifreleme el sıkışması içerir; oran büyür.

## Havuz ve Kuyruk

Havuz iki durumu yönetir: boştaki bağlantıların listesi ve boş bağlantı bekleyenlerin
kuyruğu. Boş bağlantı varsa istek anında karşılanır; yoksa kuyruğa girer.

```js
// havuz.mjs — sabit boyutlu kaynak havuzu ve kuyrugu
export class Havuz {
  constructor(uret, boyut, kuyrukZamanAsimiMs) {
    this.bos = Array.from({ length: boyut }, (_, i) => uret(i));
    this.kuyruk = [];
    this.kuyrukZamanAsimiMs = kuyrukZamanAsimiMs;
    this.boyut = boyut;
  }
  al() {
    if (this.bos.length > 0) return Promise.resolve(this.bos.pop());
    return new Promise((coz, hata) => {
      const kayit = { coz, hata };
      kayit.sayac = setTimeout(() => {
        this.kuyruk.splice(this.kuyruk.indexOf(kayit), 1);
        hata(new Error("kuyruk zaman asimi"));
      }, this.kuyrukZamanAsimiMs);
      this.kuyruk.push(kayit);
    });
  }
  birak(baglanti) {
    const kayit = this.kuyruk.shift();
    if (kayit === undefined) { this.bos.push(baglanti); return; }
    clearTimeout(kayit.sayac);
    kayit.coz(baglanti);
  }
}
```

`birak` yönteminin davranışı dikkat ister: bırakılan bağlantı boş listeye geri konmadan
önce kuyruğa bakılır. Kuyrukta bekleyen varsa bağlantı doğrudan ona devredilir. Bu, boş
listeye koyup sonra yeniden almaktan hem daha hızlıdır hem de bekleyenlerin sırasını korur.

Havuzun bir bağlantıyı sonsuza kadar bekletmemesi gerekir. `kuyrukZamanAsimiMs` süresi
dolduğunda bekleyen istek kuyruktan çıkarılır ve hata alır. Bu **kuyruk zaman aşımı**dır
(queue timeout); yokluğunda yavaşlayan bir veritabanı, bütün isteklerin süresiz beklemesine
ve uygulamanın tamamen durmasına yol açar.

## Havuzlu Servis

Servis her istekte havuzdan bir bağlantı alır, sorguyu çalıştırır ve bağlantıyı `finally`
bloğunda geri bırakır. `finally` zorunludur: hata durumunda bırakılmayan bağlantı havuzdan
kalıcı olarak eksilir.

```js
// sunucu.mjs — her istek havuzdan bir baglanti alir, sorguyu calistirir, geri birakir
import { createServer } from "node:http";
import { DatabaseSync } from "node:sqlite";
import { Havuz } from "./havuz.mjs";

const BOYUT = Number(process.env.HAVUZ_BOYUTU ?? 4);
const GIDIS_DONUS_MS = Number(process.env.GIDIS_DONUS_MS ?? 40);
const ZAMAN_ASIMI_MS = Number(process.env.ZAMAN_ASIMI_MS ?? 2000);

const havuz = new Havuz(() => new DatabaseSync("kutuphane.db"), BOYUT, ZAMAN_ASIMI_MS);
const olcumler = [];
const bekle = (ms) => new Promise((c) => setTimeout(c, ms));

const sunucu = createServer(async (istek, yanit) => {
  const yol = new URL(istek.url, "http://yerel").pathname;

  if (yol === "/kapat") { yanit.end("kapaniyor\n"); sunucu.close(); return; }

  if (yol === "/rapor") {
    const b = olcumler.map((o) => o.bekleme).sort((x, y) => x - y);
    const yuzdelik = (p) => (b.length === 0 ? 0 : +b[Math.min(b.length - 1, Math.floor(b.length * p))].toFixed(1));
    yanit.writeHead(200, { "content-type": "application/json" });
    yanit.end(JSON.stringify({
      havuz_boyutu: BOYUT, istek: olcumler.length,
      reddedilen: olcumler.filter((o) => o.reddedildi).length,
      bekleme_ortanca_ms: yuzdelik(0.5), bekleme_p95_ms: yuzdelik(0.95),
      bekleme_en_yuksek_ms: b.length === 0 ? 0 : +b[b.length - 1].toFixed(1),
    }));
    return;
  }

  const kuyrugaGiris = performance.now();
  let baglanti;
  try {
    baglanti = await havuz.al();
  } catch {
    olcumler.push({ bekleme: performance.now() - kuyrugaGiris, reddedildi: true });
    yanit.writeHead(503, { "content-type": "text/plain" });
    yanit.end("havuz dolu\n");
    return;
  }
  const bekleme = performance.now() - kuyrugaGiris;
  try {
    const satir = baglanti.prepare(
      "SELECT count(*) AS n FROM odunc WHERE iade_tarihi IS NULL").get();
    await bekle(GIDIS_DONUS_MS);          // veritabani gidis-donusunun modeli
    olcumler.push({ bekleme, reddedildi: false });
    yanit.writeHead(200, { "content-type": "text/plain" });
    yanit.end(`acik kayit: ${satir.n}  bekleme_ms: ${bekleme.toFixed(1)}\n`);
  } finally {
    havuz.birak(baglanti);
  }
});
sunucu.listen(8080, () => console.log(`havuz boyutu ${BOYUT}, port 8080`));
```

`GIDIS_DONUS_MS` süresi bir modeldir ve bu ders boyunca model olarak kalır. Buradaki
veritabanı süreç içinde çalıştığı için gerçek bir ağ gidiş-dönüşü yoktur; ayrı bir sunucuda
duran veritabanının cevap süresi bu bekleme ile temsil edilir. Ölçülen şey gidiş-dönüşün
kendisi değil, **bağlantının o süre boyunca meşgul olmasının kuyruğa etkisidir**.

## Havuz Boyutu ve Bekleme Süresi

Ölçüm düzeneği basittir: aynı anda yirmi dört istek gönder, havuz boyutunu değiştir,
isteklerin kuyrukta ne kadar beklediğine bak.

```sh
# olc.sh — havuz boyutunu degistirerek 24 paralel istegin bekleme suresini olcer
for boyut in 1 2 4 8 24; do
  HAVUZ_BOYUTU=$boyut GIDIS_DONUS_MS=40 node sunucu.mjs >/dev/null &
  sunucu=$!
  sleep 0.6
  istekler=""
  baslangic=$(date +%s%N)
  for i in $(seq 24); do
    curl -s -o /dev/null http://localhost:8080/odunc &
    istekler="$istekler $!"
  done
  wait $istekler
  bitis=$(date +%s%N)
  printf 'toplam_ms=%s  ' $(( (bitis - baslangic) / 1000000 ))
  curl -s http://localhost:8080/rapor
  echo
  curl -s -o /dev/null http://localhost:8080/kapat
  wait $sunucu
done
```

```sh
sh olc.sh
```

```
toplam_ms=1035  {"havuz_boyutu":1,"istek":24,"reddedilen":0,"bekleme_ortanca_ms":493.8,"bekleme_p95_ms":904.2,"bekleme_en_yuksek_ms":945.1}
toplam_ms=533  {"havuz_boyutu":2,"istek":24,"reddedilen":0,"bekleme_ortanca_ms":241.7,"bekleme_p95_ms":444.5,"bekleme_en_yuksek_ms":445.1}
toplam_ms=284  {"havuz_boyutu":4,"istek":24,"reddedilen":0,"bekleme_ortanca_ms":115.3,"bekleme_p95_ms":196.3,"bekleme_en_yuksek_ms":196.7}
toplam_ms=161  {"havuz_boyutu":8,"istek":24,"reddedilen":0,"bekleme_ortanca_ms":37,"bekleme_p95_ms":76.5,"bekleme_en_yuksek_ms":76.5}
toplam_ms=83  {"havuz_boyutu":24,"istek":24,"reddedilen":0,"bekleme_ortanca_ms":0,"bekleme_p95_ms":0.1,"bekleme_en_yuksek_ms":0.3}
```

Mutlak süreler makineye bağlıdır; ilişki değildir. Havuz boyutu her ikiye katlandığında
ortanca bekleme yarıya iniyor: 494, 242, 115, 37 milisaniye. Bunun nedeni doğrudan
görülebilir. Yirmi dört istek, boyut $b$ olan bir havuzda $\lceil 24/b \rceil$ turda
işlenir; her tur bir hizmet süresi kadar sürer, dolayısıyla kuyruktaki ortalama bekleme
tur sayısıyla orantılıdır.

Havuz istek sayısına eşitlendiğinde bekleme sıfırlanıyor. Bu, "havuzu büyütmek her zaman
iyidir" sonucunu vermez. Beklemenin sıfırlanması, işin daha hızlı yapılması demek değildir;
kuyruğun uygulamadan veritabanına taşınması demektir. Veritabanının kabul edebileceği
eşzamanlı bağlantı sayısı ve işleyebileceği eşzamanlı sorgu sayısı sınırlıdır; havuz o
sınırın üstüne çıkarsa bekleme uygulamada değil veritabanında birikir ve orada ölçmek
zorlaşır.

İkinci nokta çarpımdır. Uygulama birden çok kopya hâlinde çalışıyorsa toplam bağlantı
sayısı kopya sayısı ile havuz boyutunun çarpımıdır. Sekiz kopya ve yirmi bağlantılık havuz,
veritabanından yüz altmış bağlantı ister.

## Doygunluk

Havuzdaki bütün bağlantılar meşgulken ve kuyruk beklemesi zaman aşımını geçtiğinde havuz
**doygunluğa** ulaşmıştır. Bu durumda istek reddedilir.

```sh
# doygunluk.sh — havuz kucuk, kuyruk zaman asimi kisa: reddedilen istekler
HAVUZ_BOYUTU=2 GIDIS_DONUS_MS=40 ZAMAN_ASIMI_MS=200 node sunucu.mjs >/dev/null &
sunucu=$!
sleep 0.6
istekler=""
for i in $(seq 24); do
  curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080/odunc >> kodlar.txt &
  istekler="$istekler $!"
done
wait $istekler
echo "yanit kodu dagilimi:"
sort kodlar.txt | uniq -c
rm -f kodlar.txt
curl -s http://localhost:8080/rapor; echo
curl -s -o /dev/null http://localhost:8080/kapat
wait $sunucu
```

```sh
sh doygunluk.sh
```

```
yanit kodu dagilimi:
  12 200
  12 503
{"havuz_boyutu":2,"istek":24,"reddedilen":12,"bekleme_ortanca_ms":200.4,"bekleme_p95_ms":201.6,"bekleme_en_yuksek_ms":201.7}
```

Yirmi dört isteğin yarısı yanıtlandı, yarısı iki yüz milisaniyeyi bekledikten sonra 503 ile
reddedildi. Kabul edilen ve reddedilen isteklerin oranı makinenin o andaki yüküne göre
çalıştırmadan çalıştırmaya değişir; değişmeyen, reddedilen isteklerin beklemesinin zaman
aşımı değerinin hemen üstünde toplanmasıdır. Bu, sınırın çalıştığının işaretidir.

Reddetmek başarısızlık gibi görünür, ama alternatifi daha kötüdür. Zaman aşımı olmasaydı bu
on iki istek beklemeye devam eder, istemci tarafındaki zaman aşımları devreye girer,
istemciler yeniden dener ve kuyruk daha da uzardı. Sınırlı bir kuyruk, hızlı ve okunabilir
bir hata üretir.

## Oturum Durumunun Taşınması

Havuzun ikinci tuzağı bağlantının kendisiyle ilgilidir. Bir bağlantı yalnız bir borudan
ibaret değildir; üzerinde geçici tablolar, ayarlar ve açık bir işlem bulunabilir. Bu
birikime **oturum durumu** (session state) denir ve bırakılan bağlantıyla birlikte sonraki
alıcıya geçer.

```js
// oturum-durumu.mjs — birakilan baglantidaki artik durum sonraki alana gecer
import { DatabaseSync } from "node:sqlite";
import { Havuz } from "./havuz.mjs";

const havuz = new Havuz(() => new DatabaseSync("kutuphane.db"), 1, 1000);

const birinci = await havuz.al();
birinci.exec("CREATE TEMP TABLE gecici_rapor (n INTEGER)");
birinci.exec("INSERT INTO gecici_rapor VALUES (42)");
havuz.birak(birinci);                       // temizlik yapilmadi

const ikinci = await havuz.al();
const kalinti = ikinci.prepare(
  "SELECT count(*) AS n FROM temp.sqlite_master WHERE name = 'gecici_rapor'").get().n;
console.log("ikinci alicinin gordugu artik tablo sayisi:", kalinti);
if (kalinti > 0) console.log("icindeki deger:", ikinci.prepare("SELECT n FROM gecici_rapor").get().n);

ikinci.exec("DROP TABLE IF EXISTS gecici_rapor");   // birakmadan once temizlik
havuz.birak(ikinci);
const ucuncu = await havuz.al();
console.log("temizlikten sonra artik tablo sayisi     :", ucuncu.prepare(
  "SELECT count(*) AS n FROM temp.sqlite_master WHERE name = 'gecici_rapor'").get().n);
```

```sh
node oturum-durumu.mjs
```

```
ikinci alicinin gordugu artik tablo sayisi: 1
icindeki deger: 42
temizlikten sonra artik tablo sayisi     : 0
```

İkinci alıcı, hiç oluşturmadığı bir tabloyu ve içindeki değeri gördü. Bu, iki isteğin
birbirinin durumunu okuması demektir. Havuzun bırakma yolunda bir temizlik adımı olmak
zorundadır: açık işlem geri alınır, geçici nesneler düşürülür, oturum ayarları
varsayılana döner.

İlişkisel Veritabanı Yönetimi kursunda tanıtılan havuzlama düzeyleri bu soruna verilen
farklı yanıtlardır. Oturum düzeyinde havuzlamada bağlantı istemciye oturum boyunca aittir
ve oturum durumu korunur. İşlem düzeyinde havuzlamada bağlantı her işlem sonunda geri
alınır; bu, hazır deyimler ve geçici tablolar gibi oturuma bağlı özelliklerin
kullanılamaması pahasına havuz kullanımını yükseltir.

## Özet

- Bağlantıyı her istekte açmak, açık bir bağlantıyı yeniden kullanmaktan bu ölçümde on bir
  kat pahalıya geldi; havuzun varlık nedeni bu farktır.
- Havuz iki yapı tutar: boştaki bağlantılar ve bekleyenlerin kuyruğu. Bırakılan bağlantı
  boş listeye dönmeden önce kuyruğa devredilir.
- Yirmi dört paralel istekte havuz boyutu ikiye katlandıkça ortanca bekleme yarıya indi
  (494 → 242 → 115 → 37 ms); havuz istek sayısına eşitlendiğinde bekleme sıfırlandı.
- Havuzu büyütmek beklemeyi yok etmez, kuyruğu veritabanına taşır; toplam bağlantı sayısı
  uygulama kopyası sayısıyla çarpılır.
- Doygunlukta kuyruk zaman aşımı isteği hızla reddeder; sınırsız kuyruk yerine hızlı hata
  tercih edilir.
- Bırakılan bağlantıdaki oturum durumu sonraki alıcıya geçer; bırakma yolunda temizlik
  zorunludur.

## Sonraki Adım

Havuz kurulduğunda bütün bağlantılar aynı şemayı görür. Bu varsayım, şema değişene kadar
doğrudur. Yeni bir sütun eklendiğinde, bir sütun kaldırıldığında ya da bir alan adı
değiştiğinde uygulamanın çalışan kopyaları ile veritabanının durumu birbirinden ayrılır;
eski kopya yeni şemayı, yeni kopya eski şemayı görebilir. Sonraki ders şema değişikliğini
sürümlenmiş göç adımlarına böler, ileri ve geri yönde çalıştırır ve bir sütunun hizmet
kesilmeden nasıl yeniden adlandırıldığını üç adımlı bir düzenle uygulayarak gösterir.
