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.