MongoDB WiredTiger Cache Boyutu ve Bellek Optimizasyonu

MongoDB’yi production’a aldığınız an, performans sorunlarıyla yüzleşmek kaçınılmaz oluyor. Yavaş sorgular, yüksek disk I/O, artan latency… Bunların büyük çoğunluğunun arkasında yanlış yapılandırılmış WiredTiger cache boyutu yatıyor. Bu yazıda WiredTiger’ın bellek yönetimini derinlemesine inceleyeceğiz ve production ortamında gerçekten işe yarayan optimizasyon tekniklerini ele alacağız.

WiredTiger Nedir ve Neden Önemlidir

MongoDB 3.2 sürümünden itibaren varsayılan storage engine olarak kullanılan WiredTiger, document-level concurrency ve sıkıştırma özellikleriyle eski MMAPv1 engine’ine kıyasla ciddi avantajlar sunuyor. Ancak bu gücü doğru kullanmak için cache mekanizmasını anlamak şart.

WiredTiger, sıkça erişilen verileri bellekte tutmak için bir internal cache sistemi kullanır. Bu cache, disk I/O’yu minimize ederek sorgu performansını dramatik biçimde artırır. Ama yanlış boyutlandırıldığında hem MongoDB’nin performansını hem de üzerinde çalıştığı sunucunun genel sağlığını olumsuz etkiler.

Varsayılan olarak WiredTiger cache boyutu şu formülle hesaplanır:

Max(1 GB, (RAM – 1 GB) / 2)

Yani 16 GB RAM’li bir sunucuda WiredTiger cache’i varsayılan olarak 7.5 GB olarak ayarlanır. Bu her zaman doğru seçim değildir.

Mevcut Cache Durumunu İnceleme

Önce neyle uğraştığınızı anlamak gerekiyor. Mongoshell üzerinden mevcut cache istatistiklerine bakabilirsiniz:

mongosh --eval "db.serverStatus().wiredTiger.cache"

Daha okunabilir bir çıktı için:

mongosh --quiet --eval "
var cache = db.serverStatus().wiredTiger.cache;
print('Configured cache size (GB): ' + (cache['maximum bytes configured'] / 1024 / 1024 / 1024).toFixed(2));
print('Currently in cache (GB): ' + (cache['bytes currently in the cache'] / 1024 / 1024 / 1024).toFixed(2));
print('Dirty bytes in cache (GB): ' + (cache['tracked dirty bytes in the cache'] / 1024 / 1024 / 1024).toFixed(2));
print('Cache hit ratio: ' + ((cache['pages read into cache'] > 0) ? (1 - cache['pages read into cache'] / (cache['pages read into cache'] + cache['pages requested from the cache'])) * 100 : 100).toFixed(2) + '%');
"

İzlemeniz gereken kritik metrikler şunlardır:

  • maximum bytes configured: WiredTiger’a ayrılan maksimum bellek
  • bytes currently in the cache: Anlık cache kullanımı
  • tracked dirty bytes in the cache: Henüz diske yazılmamış değiştirilmiş sayfalar
  • pages read into cache: Diskten cache’e alınan sayfa sayısı
  • unmodified pages evicted: Cache’den atılan temiz sayfalar
  • modified pages evicted: Cache’den atılan kirli sayfalar (bu yüksekse sorun var)

Eviction metrikleri sürekli artıyorsa cache’iniz yetersiz demektir ve sistem sürekli disk okuma/yazma yapıyor.

Cache Boyutunu Yapılandırma

mongod.conf ile Statik Ayarlama

Production ortamının standart yolu budur. /etc/mongod.conf dosyanızı düzenleyin:

sudo vim /etc/mongod.conf

İlgili bölümü şu şekilde düzenleyin:

storage:
  dbPath: /var/lib/mongodb
  engine: wiredTiger
  wiredTiger:
    engineConfig:
      cacheSizeGB: 12
      journalCompressor: snappy
      directoryForIndexes: false
    collectionConfig:
      blockCompressor: snappy
    indexConfig:
      prefixCompression: true

Bu değişikliği uygulamak için MongoDB’yi yeniden başlatmanız gerekir:

sudo systemctl restart mongod
sudo systemctl status mongod

Komut Satırından Geçici Değişiklik

Test ve debugging aşamasında önce çalışan instance üzerinde değişikliği deneyebilirsiniz. Ancak bu kalıcı değildir, servis yeniden başlatıldığında conf dosyasındaki değer geçerli olur:

mongosh admin --eval "
db.adminCommand({
  setParameter: 1,
  wiredTigerEngineRuntimeConfig: 'cache_size=8G'
})
"

Değişikliğin uygulandığını doğrulamak için:

mongosh --eval "
db.adminCommand({getParameter: 1, wiredTigerEngineRuntimeConfig: 1})
"

Doğru Cache Boyutunu Hesaplama

Bu noktada herkese uyan tek bir cevap yok. Çalışma ortamınıza göre hesaplama yapmanız gerekiyor.

Senaryo 1: Dedicated MongoDB Sunucusu

MongoDB tek başına çalışıyorsa ve sunucu 32 GB RAM’e sahipse:

# Sistem ihtiyaçları için 2-4 GB ayır
# İşletim sistemi filesystem cache için 4-8 GB bırak
# MongoDB process overhead için 1-2 GB hesapla
# Kalanı WiredTiger cache'e ver

# 32 GB RAM örneği:
# OS: 2 GB
# Filesystem cache: 6 GB
# MongoDB overhead: 2 GB
# WiredTiger cache: 22 GB

Bunu hesaplamak için hızlı bir bash scripti:

#!/bin/bash

TOTAL_RAM_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}')
TOTAL_RAM_GB=$(echo "scale=2; $TOTAL_RAM_KB / 1024 / 1024" | bc)

# Reserved: OS (2GB) + Filesystem cache (toplamin %20'si) + MongoDB overhead (2GB)
OS_RESERVED=2
FS_CACHE=$(echo "scale=2; $TOTAL_RAM_GB * 0.20" | bc)
MONGO_OVERHEAD=2

RECOMMENDED=$(echo "scale=2; $TOTAL_RAM_GB - $OS_RESERVED - $FS_CACHE - $MONGO_OVERHEAD" | bc)

echo "Toplam RAM: ${TOTAL_RAM_GB} GB"
echo "OS icin ayrilacak: ${OS_RESERVED} GB"
echo "Filesystem cache: ${FS_CACHE} GB"
echo "MongoDB overhead: ${MONGO_OVERHEAD} GB"
echo "Onerilen WiredTiger cache: ${RECOMMENDED} GB"
echo ""
echo "mongod.conf icin:"
echo "cacheSizeGB: $(echo $RECOMMENDED | cut -d'.' -f1)"

Senaryo 2: Paylaşımlı Sunucu

MongoDB’nin yanında başka servisler de çalışıyorsa (Elasticsearch, Redis, uygulama sunucusu gibi) çok daha dikkatli olmanız gerekiyor:

# Mevcut bellek kullanımını kontrol et
free -h

# Process bazlı bellek kullanımı
ps aux --sort=-%mem | head -20

# Cgroups memory limit varsa kontrol et (container ortamı)
cat /sys/fs/cgroup/memory/memory.limit_in_bytes

Containerized ortamlarda özellikle dikkatli olun. Container’a 8 GB limit koyduğunuzda MongoDB fiziksel host RAM’ini görebilir ve ona göre cache hesaplayabilir. Bu çok ciddi bir OOM sorununa yol açar.

Container ortamı için WiredTiger cache’i container limit’ine göre manuel ayarlayın:

# Docker container'da cgroup limitini oku
CONTAINER_LIMIT_BYTES=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes)
CONTAINER_LIMIT_GB=$(echo "scale=2; $CONTAINER_LIMIT_BYTES / 1024 / 1024 / 1024" | bc)

# Container limitinin %50-60'ini kullan
CACHE_SIZE=$(echo "scale=2; $CONTAINER_LIMIT_GB * 0.55" | bc)
echo "Container limit: ${CONTAINER_LIMIT_GB} GB"
echo "Onerilen cache: ${CACHE_SIZE} GB"

Eviction Politikası ve Dirty Cache Yönetimi

WiredTiger cache dolduğunda sayfaları diskle senkronize ederek yer açar. Bu eviction process’i yanlış ayarlandığında performans ciddi biçimde düşer.

İki önemli eşik değeri var:

  • eviction_target: Cache bu doluluk oranına ulaştığında background eviction başlar (varsayılan: %80)
  • eviction_trigger: Bu eşiğe ulaşıldığında uygulama thread’leri de eviction yapmaya zorlanır (varsayılan: %95)

Yoğun write workload’larında dirty page eşikleri de kritik:

  • eviction_dirty_target: Dirty sayfalar bu orana ulaştığında eviction başlar (varsayılan: %5)
  • eviction_dirty_trigger: Bu eşikte uygulama bloklanır (varsayılan: %20)

Bu değerleri ayarlamak için:

mongosh admin --eval "
db.adminCommand({
  setParameter: 1,
  wiredTigerEngineRuntimeConfig: 'eviction=(threads_min=4,threads_max=8),eviction_target=75,eviction_trigger=90,eviction_dirty_target=10,eviction_dirty_trigger=25'
})
"

Kalıcı yapılandırma için mongod.conf’ta:

storage:
  wiredTiger:
    engineConfig:
      configString: "eviction=(threads_min=4,threads_max=8),eviction_target=75,eviction_trigger=90"

Checkpoint ve Journal Optimizasyonu

WiredTiger belirli aralıklarla memory’deki dirty data’yı diske yazar. Bu checkpoint işlemi varsayılan olarak 60 saniyede bir veya 2 GB dirty data biriktiğinde tetiklenir.

Journal (Write-Ahead Log) ise veri güvenliği için önemlidir ancak I/O yükü yaratır. Kritik olmayan workload’larda journal commit interval’ı artırabilirsiniz:

# mongod.conf
storage:
  wiredTiger:
    engineConfig:
      cacheSizeGB: 12
      journalCompressor: snappy
      
setParameter:
  wiredTigerConcurrentReadTransactions: 128
  wiredTigerConcurrentWriteTransactions: 128

Checkpoint durumunu izlemek için:

mongosh --eval "
var wt = db.serverStatus().wiredTiger;
print('Checkpoint duration (ms): ' + wt.transaction['transaction checkpoint most recent time (msecs)']);
print('Total checkpoints: ' + wt.transaction['transaction checkpoints']);
print('Checkpoint max time (ms): ' + wt.transaction['transaction checkpoint max time (msecs)']);
"

Checkpoint süresi sürekli 10 saniyenin üzerindeyse bu ciddi bir işaret. Disk hızınızı kontrol edin veya cache boyutunu artırın.

Sıkıştırma ile Etkin Cache Kullanımı

WiredTiger veriler üzerinde blok sıkıştırma uygular. Cache’de veriler sıkıştırılmamış halde tutulur, diskten okurken açılır. Bu yüzden 100 GB’lık sıkıştırılmış veri için cache’de daha fazla yer gerekebilir.

Sıkıştırma oranlarını kontrol edin:

mongosh --eval "
db.getCollectionNames().forEach(function(col) {
  var stats = db[col].stats();
  if(stats.storageSize > 0) {
    var ratio = (stats.size / stats.storageSize).toFixed(2);
    print(col + ': ' + ratio + 'x compression ratio');
  }
})
"

Snappy sıkıştırma genellikle 2-3x oran sağlar. Eğer oranınız 1.2x civarındaysa verileriniz zaten sıkıştırılmış binary data içeriyor demektir (medya dosyaları, şifreli veriler gibi).

NUMA Mimarisinde WiredTiger

Çok işlemcili sunucularda NUMA (Non-Uniform Memory Access) mimarisi WiredTiger performansını doğrudan etkiler. MongoDB’nin NUMA farkında şekilde çalışması için:

# NUMA durumunu kontrol et
numactl --hardware

# NUMA istatistiklerini izle
numastat -p $(pgrep mongod)

Eğer remote memory erişimi yüksekse mongod’u belirli bir NUMA node’a bağlayın:

# systemd service dosyasını düzenle
sudo systemctl edit mongod

# Eklenecek içerik:
[Service]
ExecStart=
ExecStart=/usr/bin/numactl --interleave=all /usr/bin/mongod --config /etc/mongod.conf

Ya da /etc/mongod.conf‘a şunu ekleyin:

processManagement:
  fork: true

# Systemd unit dosyasinda Environment degiskeni olarak:
# MONGODB_CONFIG_OVERRIDE_NOFORK=1

Monitoring ve Alerting Kurulumu

Cache boyutunu bir kez ayarlayıp unutmak olmaz. Sürekli izlemeniz gerekir.

Basit bir bash monitoring scripti:

#!/bin/bash

MONGO_URI="mongodb://localhost:27017"
ALERT_THRESHOLD=90  # Cache %90 dolunca uyar
LOG_FILE="/var/log/mongodb-cache-monitor.log"

while true; do
    TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
    
    STATS=$(mongosh --quiet "$MONGO_URI" --eval "
    var c = db.serverStatus().wiredTiger.cache;
    var maxBytes = c['maximum bytes configured'];
    var currentBytes = c['bytes currently in the cache'];
    var dirtyBytes = c['tracked dirty bytes in the cache'];
    var usagePct = ((currentBytes / maxBytes) * 100).toFixed(2);
    var dirtyPct = ((dirtyBytes / maxBytes) * 100).toFixed(2);
    print(usagePct + '|' + dirtyPct + '|' + (maxBytes/1024/1024/1024).toFixed(2));
    " 2>/dev/null)
    
    USAGE_PCT=$(echo $STATS | cut -d'|' -f1)
    DIRTY_PCT=$(echo $STATS | cut -d'|' -f2)
    MAX_GB=$(echo $STATS | cut -d'|' -f3)
    
    echo "$TIMESTAMP - Cache: ${USAGE_PCT}% dolu, Dirty: ${DIRTY_PCT}%, Max: ${MAX_GB}GB" | tee -a "$LOG_FILE"
    
    # Threshold asildiysa alert
    if (( $(echo "$USAGE_PCT > $ALERT_THRESHOLD" | bc -l) )); then
        echo "$TIMESTAMP - UYARI: WiredTiger cache %${USAGE_PCT} dolu!" | tee -a "$LOG_FILE"
        # Buraya mail veya Slack notification eklenebilir
    fi
    
    sleep 60
done

Bu scripti systemd service olarak çalıştırabilir veya cron’a ekleyebilirsiniz.

Gerçek Dünya: Bir E-ticaret Senaryosu

Yaklaşık 200 GB veri içeren bir e-ticaret platformunu düşünün. 64 GB RAM’li dedicated MongoDB sunucusu, ağır okuma workload’u ve peak saatlerinde yavaşlama şikayeti var.

İlk bakışta şu tabloyla karşılaşılabilir:

  • Varsayılan cache: 31.5 GB (64-1)/2
  • Aktif working set boyutu: ~45 GB
  • Cache hit ratio: %67
  • Eviction rate: çok yüksek

Sorun açık: Working set cache’e sığmıyor. Çözüm:

# /etc/mongod.conf guncellemesi
storage:
  wiredTiger:
    engineConfig:
      cacheSizeGB: 48
      journalCompressor: snappy
    collectionConfig:
      blockCompressor: zstd  # snappy yerine daha iyi sikistirma
    indexConfig:
      prefixCompression: true

# OS ve sistem icin 16 GB birakildi:
# OS: 2 GB
# Filesystem cache: 8 GB
# MongoDB process: 4 GB
# Diger: 2 GB

Bu değişiklikten sonra cache hit ratio %67’den %94’e çıkar, disk I/O dramatik biçimde düşer ve peak saatlerindeki latency yarıya iner.

Working Set Boyutunu Hesaplama

Cache boyutunu doğru ayarlamak için önce working set’inizi bilmeniz gerekir. Working set, sık erişilen veri kümesidir ve tamamının cache’de olması idealdir.

mongosh --eval "
// Son 1 saatte en cok erisilen collection'lari bul
var ops = db.currentOp({'active': true});
print('Aktif operasyonlar: ' + ops.inprog.length);

// Collection istatistikleri
db.getCollectionNames().forEach(function(col) {
    var stats = db[col].stats();
    var indexSize = stats.totalIndexSize / 1024 / 1024 / 1024;
    var dataSize = stats.size / 1024 / 1024 / 1024;
    if(dataSize > 0.1) {  // 100 MB'tan buyuk collection'lar
        print(col + ':');
        print('  Data: ' + dataSize.toFixed(2) + ' GB');
        print('  Index: ' + indexSize.toFixed(2) + ' GB');
        print('  Toplam: ' + (dataSize + indexSize).toFixed(2) + ' GB');
    }
})
"

Pratik kural şudur: WiredTiger cache boyutu en azından toplam index boyutunu karşılamalıdır. İdeal olarak sıkça erişilen collection’ların hot data’sını da barındırmalıdır.

Sonuç

WiredTiger cache optimizasyonu tek seferlik bir iş değil, sürekli devam eden bir süreç. Sunucunuza MongoDB’yi kurduğunuzda varsayılan ayarlarla bırakmak kısa vadede işe yarasa da büyüyen veri ve artan trafik kaçınılmaz biçimde sorun çıkarır.

Özetlemek gerekirse takip etmeniz gereken adımlar şunlardır:

  • Mevcut cache kullanımını ve hit ratio’yu düzenli izleyin
  • Working set boyutunuzu hesaplayın ve cache’i buna göre ayarlayın
  • Eviction rate’i yakından takip edin, yüksekse cache artırın veya working set’i küçültün
  • Container ortamlarında fiziksel RAM yerine container limitini baz alın
  • NUMA mimarisinde memory interleaving’i etkinleştirin
  • Monitoring scriptlerinizi hazırlayın ve alert threshold’larını belirleyin

Doğru yapılandırılmış bir WiredTiger cache, MongoDB’nizi disk I/O darboğazından kurtarır ve sorgu performansınızı katlar. Bu işe harcanan zaman her zaman kendini geri öder.

Yorum yapın