R 1
Roksam
farkmt2official 1
farkmt2official
TuZaKK 1
TuZaKK
sermin 1
sermin
berkmenoo 1
berkmenoo
mannaxxx 1
mannaxxx
Agora Metin2 1
Agora Metin2
[DEV]AB 1
[DEV]AB
kaptanmikro1 1
kaptanmikro1
Hikaye Ekle

[NODE JS] Database Connection Retry Stratejisi

  • Konuyu başlatan Konuyu başlatan Silverhand
  • Başlangıç tarihi Başlangıç tarihi
  • Cevaplar Cevaplar 1
  • Görüntüleme Görüntüleme 9

Türkiye'de ilk Mobil & PC Aynı Anda Metin2 Oyna. Triarchonline kalıcı ve uzun ömürlü yapısı ile 24 Temmuz'da açılıyor. | 1-99 Mobil Metin2 Triarch HEMEN TIKLA!

nodejs.png


[NODE JS] Database Connection Retry Stratejisi​


Veritabanı bağlantıları her zaman sorunsuz kurulamaz. Ağ kesintileri, veritabanı sunucusunun yeniden başlaması, bağlantı havuzunun dolması gibi pek çok senaryo bağlantı hatalarına yol açabilir. Üretim ortamında bu tür geçici hataları yönetmek için sağlam bir retry (yeniden deneme) stratejisi şarttır. Bu strateji olmadan uygulamanız, basit bir ağ titremesinde bile tamamen çökebilir.

İlk microservice projemde retry mekanizması kullanmıyorduk. Bir gece veritabanı sunucusu bakım için yeniden başlatıldığında, tüm servisler çöktü ve kendilerini toparlayamadı. Manuel müdahale gerekti. O deneyimden sonra her projeme mutlaka retry stratejisi eklemeye başladım. Bu küçük yatırım, saatlerce sürebilecek kesintileri saniyeler seviyesine indiriyor.

Retry stratejilerinde en yaygın kullanılan yaklaşım "Exponential Backoff" yani üstel geri çekilmedir. Bu yaklaşımda her başarısız denemeden sonra bekleme süresi katlanarak artar. İlk denemede 1 saniye, ikinci denemede 2 saniye, üçüncüde 4 saniye şeklinde devam eder. Bu sayede veritabanı sunucusuna aşırı yük bindirmeden, yeterli toparlanma süresi tanımış olursunuz.

Bir diğer önemli kavram "Jitter" yani rastgele sapma eklenmesidir. Birden fazla servis aynı anda yeniden bağlanmaya çalıştığında, hepsi aynı aralıklarla denerse veritabanına ani yük oluşur. Bekleme süresine rastgele bir değer ekleyerek bu yığılmayı önleyebilirsiniz. Bu basit teknik, özellikle çok sayıda replica çalıştırılan ortamlarda büyük fark yaratır.

Circuit Breaker pattern'i de retry stratejisiyle birlikte kullanılması gereken önemli bir mekanizmadır. Sürekli başarısız olan bağlantı denemelerini belirli bir eşikten sonra durdurmak, hem kaynakları korur hem de hata loglarının patlamasını engeller. Belirli bir süre sonra tekrar deneme yapılır ve başarılı olursa devre yeniden kapanır.

Bağlantı havuzu (connection pool) ayarları da retry stratejinizin bir parçası olmalıdır. Minimum ve maksimum bağlantı sayısı, boşta kalma süresi, bağlantı zaman aşımı gibi parametreleri doğru yapılandırmak, pek çok bağlantı sorununu baştan önler. Sequelize, Knex ve TypeORM gibi ORM'lerin hepsinde bu ayarlar mevcuttur.

Monitoring ve alerting, retry stratejinizin tamamlayıcısıdır. Her retry denemesini loglayın, başarısız denemelerin sayısını izleyin ve belirli eşiklerde uyarı gönderin. Bu sayede sorunları proaktif olarak tespit edebilir ve büyümeden müdahale edebilirsiniz.

JavaScript:
// Database Connection Retry Stratejisi
const { Sequelize } = require('sequelize');
const EventEmitter = require('events');

// ==========================================
// Retry Yapılandırma Sınıfı
// ==========================================
class RetryYapilandirma {
  constructor(secenekler = {}) {
    this.maksimumDeneme = secenekler.maksimumDeneme || 10;
    this.baslangicGecikmesi = secenekler.baslangicGecikmesi || 1000; // ms
    this.maksimumGecikme = secenekler.maksimumGecikme || 30000; // ms
    this.carpan = secenekler.carpan || 2;
    this.jitterOrani = secenekler.jitterOrani || 0.3; // %30 rastgelelik
    this.zamanAsimi = secenekler.zamanAsimi || 5000; // ms
  }

  // Üstel geri çekilme ile gecikme hesaplama
  gecikmeHesapla(denemeSayisi) {
    // Üstel gecikme: baslangic * (carpan ^ deneme)
    let gecikme = this.baslangicGecikmesi * Math.pow(this.carpan, denemeSayisi);

    // Maksimum gecikmeyi aşmasın
    gecikme = Math.min(gecikme, this.maksimumGecikme);

    // Jitter ekle (rastgele sapma)
    const jitter = gecikme * this.jitterOrani;
    const rastgeleSapma = (Math.random() * 2 - 1) * jitter;
    gecikme = Math.floor(gecikme + rastgeleSapma);

    return Math.max(gecikme, 100); // En az 100ms
  }
}

// ==========================================
// Veritabanı Bağlantı Yöneticisi
// ==========================================
class VeritabaniBaglanti extends EventEmitter {
  constructor(yapilandirma, retrySecenekler = {}) {
    super();
    this.yapilandirma = yapilandirma;
    this.retry = new RetryYapilandirma(retrySecenekler);
    this.sequelize = null;
    this.bagliMi = false;
    this.denemeSayisi = 0;
    this.istatistikler = {
      basariliDeneme: 0,
      basarisizDeneme: 0,
      sonBasariliZaman: null,
      sonHata: null
    };
  }

  // Sequelize örneği oluşturma
  sequelizeOlustur() {
    return new Sequelize({
      dialect: this.yapilandirma.dialect || 'postgres',
      host: this.yapilandirma.host || 'localhost',
      port: this.yapilandirma.port || 5432,
      database: this.yapilandirma.database || 'uygulama_db',
      username: this.yapilandirma.username || 'kullanici',
      password: this.yapilandirma.password || 'sifre',
      logging: false,
      pool: {
        max: this.yapilandirma.havuzMaksimum || 20,
        min: this.yapilandirma.havuzMinimum || 5,
        acquire: this.yapilandirma.edinmeZamanAsimi || 30000,
        idle: this.yapilandirma.bostaSuresi || 10000,
        evict: this.yapilandirma.temizlemeSuresi || 1000
      },
      retry: {
        max: 0 // Kendi retry mekanizmamızı kullanacağız
      },
      dialectOptions: {
        connectTimeout: this.retry.zamanAsimi,
        statement_timeout: 30000
      }
    });
  }

  // Bağlantıyı retry ile kurma
  async baglan() {
    this.denemeSayisi = 0;

    while (this.denemeSayisi < this.retry.maksimumDeneme) {
      try {
        console.log(
          '[Bağlantı] Deneme ' + (this.denemeSayisi + 1) + '/'
          + this.retry.maksimumDeneme + '...'
        );

        // Sequelize örneğini oluştur
        if (!this.sequelize) {
          this.sequelize = this.sequelizeOlustur();
        }

        // Bağlantıyı test et
        await this.sequelize.authenticate();

        this.bagliMi = true;
        this.istatistikler.basariliDeneme++;
        this.istatistikler.sonBasariliZaman = new Date().toISOString();

        console.log('[Bağlantı] Veritabanına başarıyla bağlanıldı!');
        this.emit('baglanildi', { deneme: this.denemeSayisi + 1 });

        // Bağlantı kopması olayını dinle
        this.baglantiKopmasiDinle();

        return this.sequelize;

      } catch (hata) {
        this.denemeSayisi++;
        this.istatistikler.basarisizDeneme++;
        this.istatistikler.sonHata = hata.message;

        console.error(
          '[Bağlantı Hatası] Deneme ' + this.denemeSayisi
          + ' başarısız: ' + hata.message
        );
        this.emit('hata', { deneme: this.denemeSayisi, hata: hata });

        // Son deneme değilse bekle
        if (this.denemeSayisi < this.retry.maksimumDeneme) {
          const bekleme = this.retry.gecikmeHesapla(this.denemeSayisi - 1);
          console.log('[Bağlantı] ' + bekleme + 'ms sonra tekrar denenecek...');
          await this.bekle(bekleme);
        }
      }
    }

    // Tüm denemeler başarısız
    const mesaj = 'Veritabanı bağlantısı kurulamadı. '
      + this.retry.maksimumDeneme + ' deneme yapıldı.';
    console.error('[Bağlantı] ' + mesaj);
    this.emit('baglanti_basarisiz', { toplamDeneme: this.denemeSayisi });
    throw new Error(mesaj);
  }

  // Bağlantı kopması durumunda otomatik yeniden bağlanma
  baglantiKopmasiDinle() {
    const kontrol = setInterval(async () => {
      try {
        await this.sequelize.authenticate();
      } catch (hata) {
        console.warn('[Bağlantı] Kopma tespit edildi, yeniden bağlanılıyor...');
        this.bagliMi = false;
        this.emit('koptu', { hata: hata.message });

        clearInterval(kontrol);

        // Mevcut bağlantıyı temizle
        try {
          await this.sequelize.close();
        } catch (kapatmaHatasi) {
          // Sessizce geç
        }
        this.sequelize = null;

        // Yeniden bağlan
        try {
          await this.baglan();
        } catch (yenidenBaglanmaHatasi) {
          console.error('[Bağlantı] Yeniden bağlanma başarısız');
          this.emit('kalici_hata', { hata: yenidenBaglanmaHatasi.message });
        }
      }
    }, 10000); // Her 10 saniyede kontrol
  }

  // Yardımcı bekleme fonksiyonu
  bekle(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  // Bağlantı istatistikleri
  istatistikleriGetir() {
    return {
      ...this.istatistikler,
      bagliMi: this.bagliMi,
      sonDeneme: this.denemeSayisi
    };
  }

  // Bağlantıyı kapat
  async kapat() {
    if (this.sequelize) {
      await this.sequelize.close();
      this.bagliMi = false;
      console.log('[Bağlantı] Veritabanı bağlantısı kapatıldı');
    }
  }
}

// ==========================================
// Circuit Breaker Pattern
// ==========================================
class DevreKesici {
  constructor(secenekler = {}) {
    this.esik = secenekler.esik || 5; // Hata eşiği
    this.sifirlamaSuresi = secenekler.sifirlamaSuresi || 60000; // ms
    this.yarimAcikDeneme = secenekler.yarimAcikDeneme || 1;

    this.durum = 'kapali'; // kapali, acik, yarim_acik
    this.hataSayaci = 0;
    this.sonHataZamani = null;
    this.basariliDenemeler = 0;
  }

  async calistir(fonksiyon) {
    if (this.durum === 'acik') {
      // Süre geçtiyse yarım açık duruma geç
      const gecenSure = Date.now() - this.sonHataZamani;
      if (gecenSure >= this.sifirlamaSuresi) {
        this.durum = 'yarim_acik';
        this.basariliDenemeler = 0;
        console.log('[Devre Kesici] Yarım açık duruma geçildi');
      } else {
        throw new Error('Devre kesici açık - işlem engellendi');
      }
    }

    try {
      const sonuc = await fonksiyon();

      if (this.durum === 'yarim_acik') {
        this.basariliDenemeler++;
        if (this.basariliDenemeler >= this.yarimAcikDeneme) {
          this.durum = 'kapali';
          this.hataSayaci = 0;
          console.log('[Devre Kesici] Devre kapandı - normal çalışmaya dönüldü');
        }
      }

      return sonuc;
    } catch (hata) {
      this.hataSayaci++;
      this.sonHataZamani = Date.now();

      if (this.hataSayaci >= this.esik) {
        this.durum = 'acik';
        console.log('[Devre Kesici] Devre açıldı! Hata eşiği aşıldı.');
      }

      throw hata;
    }
  }

  durumBilgisi() {
    return {
      durum: this.durum,
      hataSayaci: this.hataSayaci,
      sonHataZamani: this.sonHataZamani
    };
  }
}

// ==========================================
// Kullanım Örneği
// ==========================================
async function uygulamayiBaslat() {
  const db = new VeritabaniBaglanti(
    {
      dialect: 'postgres',
      host: 'localhost',
      port: 5432,
      database: 'uygulama_db',
      username: 'admin',
      password: 'guclu_sifre',
      havuzMaksimum: 20,
      havuzMinimum: 5
    },
    {
      maksimumDeneme: 8,
      baslangicGecikmesi: 1000,
      maksimumGecikme: 30000,
      carpan: 2,
      jitterOrani: 0.25
    }
  );

  // Olayları dinle
  db.on('baglanildi', (bilgi) => {
    console.log('Bağlandı! Deneme sayısı:', bilgi.deneme);
  });

  db.on('hata', (bilgi) => {
    console.log('Deneme', bilgi.deneme, 'başarısız');
  });

  db.on('koptu', (bilgi) => {
    console.log('Bağlantı koptu:', bilgi.hata);
  });

  try {
    const sequelize = await db.baglan();
    console.log('Uygulama veritabanına bağlandı');
    console.log('İstatistikler:', db.istatistikleriGetir());
  } catch (hata) {
    console.error('Uygulama başlatılamadı:', hata.message);
    process.exit(1);
  }
}

uygulamayiBaslat();

Sağlam bir retry stratejisi, uygulamanızın dayanıklılığını önemli ölçüde artırır. Exponential backoff, jitter ve circuit breaker pattern'ini birlikte kullanarak, geçici hataları zarif bir şekilde yönetebilirsiniz. Unutmayın ki hedef, hataları gizlemek değil, geçici sorunları otomatik olarak çözmek ve kalıcı sorunları hızlıca tespit etmektir.
 
Emeğiniz ve katkınız için teşekkür ederiz!
Konunuz gerekli incelemelerden geçerek onaylandı. Topluluğumuza katkılarınızın devamını bekliyoruz.
İyi forumlar! 🎉
 

Şu an konuyu görüntüleyenler (Toplam : 0, Üye: 0, Misafir: 0)

Geri
Üst