Alışveriş Sepeti

Sepetiniz boş

Mimari & Mühendislik · 15 dk

Node.js & V8 Bellek Yönetimi: Yüksek Trafikte Memory Leak Teşhisi ve GC Optimizasyonu

Canlıdaki bir e-ticaret sunucusunun birkaç gün içinde belleği tüketip çökmesinin arkasındaki gizli sebepler: V8 Heap mimarisi, Event Listener sızıntıları ve Heap Snapshot ile bellek profilleme rehberi.

Yazar: Stilgen Core Engineering

Node.js & V8 Bellek Yönetimi: Yüksek Trafikte Memory Leak Teşhisi ve GC Optimizasyonu

1. V8 Heap Anatomisi: Bellek Nasıl Organize Edilir?

Node.js'in altında Google Chrome'un meşhur C++ tabanlı JavaScript motoru olan V8 çalışır. 64-bit bir işletim sisteminde V8, varsayılan olarak yaklaşık 1.4 GB ile 2 GB arasında bir Heap bellek alanı tahsis eder.

Bu bellek alanı tek bir parça değildir; nesnelerin yaşama süresine göre iki ana alana (Generational Hypothesis) ayrılır:

  • New Space (Genç Alan - 16MB - 64MB): Yeni oluşturulan tüm değişkenler, geçici nesneler ve fonksiyon değişkenleri buraya düşer. Çok hızlı ve hafif olan Scavenger (Cheney's Algorithm) çöp toplayıcısı tarafından mikrosaniyeler içinde temizlenir.
  • Old Space (Yaşlı Alan): İki çöp toplama döngüsünden sağ çıkan nesneler buraya terfi eder (Tenuring). Burada biriken veriler Mark-Sweep-Compact algoritmasıyla temizlenir; bu işlem CPU-yoğundur ve uygulamanın milisaniyelerce duraklamasına (Stop-the-World pause) neden olabilir.

2. E-Ticarette En Sık Yaşanan 3 Ölümcül Memory Leak Sebebi

Stilmerce ekibinin danışmanlık yaptığı birçok projede sunucuların her 4 günde bir OOM (Out Of Memory) hatasıyla yeniden başlamasına yol açan temel hatalar:

  1. Sınırsız Büyüyen Bellek İçi Önbellekler (Uncapped In-Memory Caches):

    Geliştirici hız kazanmak için const productCache = {}; objesi tanımlar. Ürünler ve oturumlar bu objeye yazılır ancak hiçbir zaman temizleme (LRU Eviction / TTL) mantığı kurulmaz. 100.000 farklı ziyaretçi geldiğinde RAM dolar.

  2. Temizlenmeyen Event Listener'lar (Dangling Event Listeners):

    Her HTTP isteğinde req.on('close', ...) veya global bir EventEmitter'a listener eklenir ancak istek bittiğinde removeListener çağrılmaz. Binlerce sahipsiz closure bellekte asılı kalır.

  3. Kapanmayan Veritabanı Stream'leri ve Büyük Buffer'lar:

    E-fatura veya büyük ürün listesi export edilirken veritabanı cursor'ı veya disk stream'i düzgün sonlandırılmaz.

3. Heap Snapshot Alarak Bellek Sızıntısını 5 Dakikada Yakalamak

Canlıdaki bir Node.js sürecini kapatmadan bellek sızıntısının hangi değişkende olduğunu tespit etmek için yerleşik v8 modülü kullanılır:

// diagnostic.controller.ts: Canlıda Snapshot Alma Uç Noktası
import * as v8 from 'v8';
import * as fs from 'fs';
import { Controller, Post, UseGuards } from '@nestjs/common';
import { AdminSecretGuard } from './admin-secret.guard';

@Controller('admin/diagnostics')
export class DiagnosticsController {
  @Post('heap-snapshot')
  @UseGuards(AdminSecretGuard)
  generateSnapshot() {
    const filename = `/tmp/heap-${Date.now()}.heapsnapshot`;
    const snapshotStream = v8.getHeapSnapshot();
    const fileStream = fs.createWriteStream(filename);
    snapshotStream.pipe(fileStream);

    return { message: 'Snapshot oluşturuldu', path: filename };
  }
}

Oluşturulan .heapsnapshot dosyasını Chrome DevTools -> Memory -> Load bölümünden açtığınızda, hangi sınıfın (Constructor) milyonlarca nesne tuttuğunu ve GC tarafından neden silinemediğini (Retainer Tree) net bir şekilde görebilirsiniz.

4. V8 ve Docker Konfigürasyonunun Ayarlanması

Kubernetes pod'unda Node.js çalıştırırken işletim sistemi bellek sınırı ile V8 bellek sınırının birbirine uyumlu olması şarttır:

# Kubernetes Pod Sınırı: 4 GB
# V8 Heap Sınırı: 3.2 GB (Geri kalan 800 MB; Buffer'lar, C++ binding'leri ve OS içindir)
CMD ["node", "--max-old-space-size=3200", "--optimize-for-size", "dist/main.js"]

Mühendislik Kuralı

V8'e asla Pod'un toplam RAM'ini vermeyin! Node.js'te Buffer ve SSL TLS oturumları V8 Heap dışında (Native C++ Memory) tutulur. V8'e toplam RAM'in %75-%80'ini tahsis etmek OOMKilled hatalarını kesin olarak önler.