Unbound DNS Sunucusunda Cache Ayarları ve Performans Optimizasyonu
DNS sunucunuzun cache ayarlarını doğru yapılandırmazsanız, günde milyonlarca sorgu işleyen bir sistemde bile performansın beklenenden çok daha kötü olduğunu fark edersiniz. Bunu ilk kez bir e-ticaret müşterisinde yaşadım: Unbound kuruluydu, çalışıyordu, ama yük altında yanıt süreleri 200-300ms’ye çıkıyordu. Sorun cache değildi, cache’in yanlış boyutlandırılmasıydı. Bu yazıda Unbound’da cache mekanizmasını, doğru boyutlandırmayı ve gerçek anlamda fark yaratan optimizasyon parametrelerini ele alacağım.
Unbound Cache Nasıl Çalışır?
Unbound’un cache sistemi birkaç farklı bileşenden oluşur ve her birinin ayrı bir rolü vardır. Bunu anlamadan yapılandırmaya başlamak, karanlıkta ayar yapmaya benzer.
rrset-cache: DNS kayıt setlerini (A, AAAA, MX, vb.) depolar. Bu cache’in boyutu, ne kadar çok farklı domain’i bellekte tutabileceğinizi doğrudan belirler.
msg-cache: Tam DNS mesajlarını saklar. Bir sorguya gelen tüm yanıt paketini önbelleğe alır. Genellikle rrset-cache’in yarısı kadar boyutlandırılır.
key-cache: DNSSEC doğrulaması için kullanılan anahtarları tutar. DNSSEC kullanıyorsanız bu cache kritik önem taşır.
neg-cache: NXDOMAIN ve NOERROR yanıtlarını, yani “bu domain yok” cevaplarını saklar. Özellikle spam/botnet trafiğini bertaraf etmede çok işe yarar.
Unbound bu cache’leri varsayılan olarak oldukça muhafazakâr boyutlarda açar. Üretim ortamında bu varsayılanlarla gidilmez.
Temel Cache Parametrelerini Anlamak
/etc/unbound/unbound.conf dosyasına bakmadan önce mevcut cache kullanımını görmek iyi bir başlangıç noktası:
# Mevcut cache istatistiklerini görüntüle
unbound-control stats_noreset | grep -E "cache|num."
# Veya daha okunabilir formatta
unbound-control stats_noreset | grep cache
Şimdi parametrelerin ne anlama geldiğine bakalım:
rrset-cache-size: RRset önbelleğinin boyutu. Bayt cinsinden veya m/k/g sonekleriyle girilir. Varsayılan 4m, üretim için genellikle yetersiz.
msg-cache-size: Mesaj önbelleğinin boyutu. Kural olarak rrset-cache-size’ın yarısı yapılır.
key-cache-size: DNSSEC anahtar önbelleği. DNSSEC aktifse ihmal etmeyin.
cache-min-ttl: Bir kaydın cache’de kalacağı minimum süre. Çok düşük TTL’li domainler için bant genişliği tasarrufu sağlar.
cache-max-ttl: Maksimum cache süresi. Authoritative sunucuların verdiği TTL’den uzun tutmak istemezsiniz genellikle.
cache-max-negative-ttl: Negatif yanıtların (NXDOMAIN) maksimum cache süresi.
serve-expired: Cache’deki süresi dolmuş kayıtları TTL yenilenirken geçici olarak sunmaya devam etme özelliği. Latency açısından muazzam fark yaratır.
prefetch: TTL’nin %10’u kalmışken kaydı arka planda yenilemeye başlar. serve-expired ile birlikte kullanılınca çok güçlüdür.
Temel Yapılandırma Dosyası
Küçük-orta ölçekli bir ortam için başlangıç noktası olarak şu yapılandırmayı kullanabilirsiniz:
# /etc/unbound/unbound.conf - Temel cache optimizasyonu
server:
# Cache boyutları
rrset-cache-size: 256m
msg-cache-size: 128m
key-cache-size: 32m
# Negatif cache
cache-max-negative-ttl: 3600
# TTL sınırları
cache-min-ttl: 300
cache-max-ttl: 86400
# Prefetch ve expired serve
prefetch: yes
prefetch-key: yes
serve-expired: yes
serve-expired-ttl: 3600
serve-expired-reply-ttl: 30
# Thread ve slice ayarları
num-threads: 4
msg-cache-slabs: 8
rrset-cache-slabs: 8
infra-cache-slabs: 8
key-cache-slabs: 8
Slab sayısını thread sayısının iki katı yapmanız, lock contention’ı azaltır. 4 thread için 8 slab idealdir.
Cache Boyutlandırma Hesabı
“Kaç MB yeter?” sorusunun cevabı sunucunuzun profiline göre değişir ama bir formül verebilirim. Önce şunu çalıştırın:
# Son 24 saatin unique domain sayısını tahmin etmek için
unbound-control stats_noreset | grep "total.num.queries"
unbound-control stats_noreset | grep "cache.count"
# Cache hit oranını hesapla
unbound-control stats_noreset | grep -E "cache_hits|cache_miss"
Genel kural şu: Her cached RRset ortalama 100-200 byte yer kaplar. 1 milyon unique domain tutmak istiyorsanız yaklaşık 150-200MB rrset-cache yeterli olur. Ancak her ortam farklı sorgu dağılımına sahiptir.
Sunucunuzdaki RAM durumunu da hesaba katın:
# Sistem bellek durumu
free -h
# Unbound'un şu an ne kadar bellek kullandığı
ps aux | grep unbound | awk '{print $6}'
# Daha detaylı
cat /proc/$(pgrep unbound)/status | grep -E "VmRSS|VmSize"
Pratik önerim: Sunucunuzdaki toplam RAM’in %20-25’ini Unbound cache’e ayırın. 16GB RAM’li bir sunucuda 3-4GB cache tamamen makul bir hedeftir.
İleri Seviye Cache Optimizasyonu
Temel ayarların ötesine geçelim. Bu parametreler çoğu belgede bahsedilmez ama fark yaratan detaylardır.
Serve-Expired ile Sıfır Kesinti
serve-expired özelliği Unbound’un en güçlü cache özelliklerinden biridir. TTL süresi dolduğunda Unbound normalde yeni sorgu yapana kadar bekler. serve-expired açıkken eski kaydı hemen döner, arka planda yenileme yapar:
server:
serve-expired: yes
serve-expired-ttl: 86400 # Süresi dolan kayıt kaç saniye daha sunulsun
serve-expired-reply-ttl: 30 # Client'a döndürülen TTL değeri
serve-expired-client-timeout: 0 # 0 = hemen eski kaydı ver, background'da yenile
serve-expired-client-timeout parametresine dikkat edin. 0 yaparsanız Unbound hiç beklemeden eski kaydı döner. Pozitif bir değer (örneğin 1800 ms) girerseniz, o süre içinde yeni yanıt gelirse yeni kaydı, gelmezse eski kaydı döner. Latency öncelikli ortamlarda 0 tercih edilir.
Prefetch Davranışını İncelemek
# Prefetch istatistiklerini izle
watch -n 5 "unbound-control stats_noreset | grep prefetch"
# Toplam istatistikler
unbound-control stats | grep -E "total.|cache."
Prefetch’in cache hit oranına etkisini görmek için bunu birkaç saat izleyin. Popüler domainlerin yüksek trafikte neredeyse hiç miss vermediğini göreceksiniz.
Infrastructure Cache Ayarları
Infra-cache, upstream DNS sunucularının yanıt süreleri, RTT değerleri ve güvenilirlik bilgilerini tutar. Bu cache iyi boyutlandırılmazsa Unbound her seferinde kötü bir upstream’e gidip zaman kaybedebilir:
server:
infra-cache-numhosts: 100000 # Cache'de tutulacak maksimum host sayısı
infra-cache-slabs: 8
infra-host-ttl: 900 # Host bilgisinin cache süresi (saniye)
infra-cache-min-rtt: 50 # Minimum RTT tahmini (ms)
infra-cache-numhosts değerini çok düşük bırakmak, Unbound’un upstream sunucu seçimini bozabilir. Büyük ağlarda bu değeri 500000’e kadar çıkarmanız gerekebilir.
Gerçek Dünya Senaryosu: Yüksek Yük Altında Optimizasyon
Bir ISP altyapısında Unbound’u optimize etmek durumunda kaldım. Sunucu günde yaklaşık 50 milyon sorgu işliyordu ve cache hit oranı %62 civarındaydı. Hedef %85’ti.
Sorun analizi:
# Detailed stats dump
unbound-control stats_noreset > /tmp/before_opt.txt
# Cache'de ne kadar yer dolduğuna bak
unbound-control stats | grep "cache.count"
# Query type dağılımı
unbound-control stats | grep "num.query.type"
Cache count, max kapasiteye yakındı. Yani cache doluydu ve yeni kayıtlar eski kayıtları dışarı atıyordu (eviction). Çözüm cache boyutunu artırmaktı ama önce neyin cache’de yer kapladığına bakmak gerekiyordu.
AAAA sorguları toplam sorguların %38’ini oluşturuyordu ama network’te IPv6 yoktu. Bu sorgular NXDOMAIN veya hata dönüyordu. cache-max-negative-ttl 60 saniyeydi, yani bu başarısız AAAA sorguları sürekli upstream’e gidiyordu. Çözüm:
server:
# IPv6 yoksa AAAA sorgularını optimize et
cache-max-negative-ttl: 3600 # 60'tan 3600'e çıkardık
# Do-not-query IPv6
do-ip6: no # IPv6 yoksa sorgu atmayı durdur
# Cache boyutunu artır
rrset-cache-size: 1024m
msg-cache-size: 512m
Bu değişiklikten sonra cache hit oranı 48 saat içinde %91’e çıktı. Upstream sorgular neredeyse üçte ikisi azaldı.
Thread ve Slab Optimizasyonu
Modern sunucularda çok çekirdek kullanmak kritik öneme sahiptir. Unbound varsayılan olarak tek thread açar ve bu büyük bir kayıptır:
# CPU çekirdek sayısını öğren
nproc
# Unbound için thread sayısını ayarla
# unbound.conf içinde:
server:
num-threads: 8 # Fiziksel çekirdek sayısı kadar
# Slab sayıları - thread sayısının katları olmalı
msg-cache-slabs: 16 # 2x thread sayısı
rrset-cache-slabs: 16
infra-cache-slabs: 16
key-cache-slabs: 16
# Her thread için SO_REUSEPORT
so-reuseport: yes # Linux 3.9+ gerekli
so-reuseport parametresi kernel seviyesinde yük dengeleme sağlar. Bu olmadan threadler arasında bağlantı dağılımı dengesiz olabilir.
Buffer ve Network Optimizasyonu
Cache’in yanı sıra network katmanında da optimizasyon yapılabilir:
server:
# Socket buffer boyutları
so-rcvbuf: 8m
so-sndbuf: 8m
# UDP ve TCP için
udp-upstream-without-downstream: no
# Outgoing port çeşitliliği - güvenlik ve performans
outgoing-range: 8192
num-queries-per-thread: 4096
# EDNS buffer
edns-buffer-size: 1232 # RFC 8085 önerisi
so-rcvbuf ve so-sndbuf için kernel sınırlarını da artırmanız gerekebilir:
# Sistem seviyesinde kernel buffer sınırlarını artır
echo 'net.core.rmem_max=16777216' >> /etc/sysctl.conf
echo 'net.core.wmem_max=16777216' >> /etc/sysctl.conf
echo 'net.core.rmem_default=8388608' >> /etc/sysctl.conf
echo 'net.core.wmem_default=8388608' >> /etc/sysctl.conf
sysctl -p
# Unbound'un bu limitlere ulaşıp ulaşmadığını kontrol et
unbound-control stats | grep "mem.streamwait"
Cache İzleme ve Alarm Kurulumu
Optimizasyonu yapıp bırakmak olmaz. Sürekli izleme şart:
#!/bin/bash
# /usr/local/bin/unbound-cache-monitor.sh
STATS=$(unbound-control stats_noreset)
TOTAL_QUERIES=$(echo "$STATS" | grep "total.num.queries=" | cut -d= -f2)
CACHE_HITS=$(echo "$STATS" | grep "total.num.cachehits=" | cut -d= -f2)
CACHE_MISS=$(echo "$STATS" | grep "total.num.cachemiss=" | cut -d= -f2)
if [ "$TOTAL_QUERIES" -gt 0 ]; then
HIT_RATIO=$(echo "scale=2; $CACHE_HITS * 100 / $TOTAL_QUERIES" | bc)
echo "Cache Hit Ratio: %${HIT_RATIO}"
# %70 altına düşerse uyar
THRESHOLD=70
HIT_INT=$(echo "$HIT_RATIO" | cut -d. -f1)
if [ "$HIT_INT" -lt "$THRESHOLD" ]; then
echo "UYARI: Cache hit orani dusuk: %${HIT_RATIO}"
# Buraya mail/slack bildirimi eklenebilir
logger -t unbound-monitor "WARN: Cache hit ratio below threshold: ${HIT_RATIO}%"
fi
fi
# Cache doluluk oranı
RRSET_COUNT=$(echo "$STATS" | grep "rrset.cache.count=" | cut -d= -f2)
echo "RRset cache kayit sayisi: $RRSET_COUNT"
Bu scripti cron’a ekleyin:
chmod +x /usr/local/bin/unbound-cache-monitor.sh
echo "*/5 * * * * root /usr/local/bin/unbound-cache-monitor.sh >> /var/log/unbound-cache.log 2>&1" >> /etc/crontab
Cache Temizleme ve Güncelleme
Bazen belirli bir domain’in cache’ini temizlemeniz gerekir. Örneğin DNS değişikliği yaptınız ama Unbound eski kaydı tutmaya devam ediyor:
# Belirli bir domain'i cache'den sil
unbound-control flush example.com
# Bir domain ve tüm alt domainlerini sil
unbound-control flush_zone example.com
# Tüm cache'i temizle (dikkatli kullanın!)
unbound-control flush_all
# Belirli bir tip için cache'i temizle
unbound-control flush_type example.com A
# Negative cache'i temizle
unbound-control flush_negative
# Cache'in içeriğini dump et (debug için)
unbound-control dump_cache > /tmp/cache_dump.txt
# Cache'i geri yükle (restart sonrası cache ısınma için)
unbound-control load_cache < /tmp/cache_dump.txt
load_cache ve dump_cache ikilisi özellikle restart senaryolarında çok değerlidir. Servis yeniden başlatılsa bile cache’i kaybetmezsiniz, bu büyük sistemlerde kritik öneme sahiptir.
DNSSEC ile Cache Optimizasyonu
DNSSEC aktif ortamlarda cache yönetimi biraz daha özel dikkat ister:
server:
# DNSSEC doğrulama
auto-trust-anchor-file: "/var/lib/unbound/root.key"
# DNSSEC cache optimizasyonu
key-cache-size: 64m
key-cache-slabs: 8
prefetch-key: yes # Anahtarları da önceden getir
# Aggressive NSEC - cache'i daha verimli kullan
aggressive-nsec: yes # NSEC kayıtlarından negatif yanıt üret
# NSEC3 için
val-nsec3-keysize-iterations: "1024 150 2048 500 4096 2500"
aggressive-nsec parametresi özellikle dikkat çekici: NSEC kayıtlarını kullanarak cache’deki bilgilerden negatif yanıt üretir, upstream’e gitmeden. DNSSEC imzalı zone’larda upstream sorgularını önemli ölçüde azaltır.
Tam Optimizasyon Yapılandırması
Tüm bunları bir araya getiren production-ready bir yapılandırma:
server:
# Temel ağ ayarları
interface: 0.0.0.0
port: 53
do-ip4: yes
do-ip6: no
do-udp: yes
do-tcp: yes
# Thread ve slab - sunucuya göre ayarla
num-threads: 8
msg-cache-slabs: 16
rrset-cache-slabs: 16
infra-cache-slabs: 16
key-cache-slabs: 16
so-reuseport: yes
# Cache boyutları - RAM'e göre ayarla
rrset-cache-size: 512m
msg-cache-size: 256m
key-cache-size: 64m
infra-cache-numhosts: 200000
# TTL yönetimi
cache-min-ttl: 300
cache-max-ttl: 86400
cache-max-negative-ttl: 3600
# Prefetch ve serve-expired
prefetch: yes
prefetch-key: yes
serve-expired: yes
serve-expired-ttl: 86400
serve-expired-reply-ttl: 30
serve-expired-client-timeout: 0
# Network buffers
so-rcvbuf: 8m
so-sndbuf: 8m
outgoing-range: 8192
num-queries-per-thread: 4096
edns-buffer-size: 1232
# DNSSEC
auto-trust-anchor-file: "/var/lib/unbound/root.key"
aggressive-nsec: yes
# Logging - production'da minimal tut
verbosity: 1
log-queries: no
log-replies: no
Bu yapılandırmayı uyguladıktan sonra mutlaka test edin:
# Yapılandırma doğruluğunu kontrol et
unbound-checkconf /etc/unbound/unbound.conf
# Servisi yeniden başlat
systemctl restart unbound
# İlk istatistikleri al
sleep 60
unbound-control stats_noreset
Sonuç
Unbound cache optimizasyonu tek seferlik bir iş değildir. Önce mevcut durumu ölçün, sonra değişiklik yapın, ardından tekrar ölçün. Cache boyutu, thread sayısı, prefetch ve serve-expired ayarları tek başlarına bile büyük fark yaratır ama asıl güç bunların birlikte doğru yapılandırılmasından gelir.
Pratikte en çok etkiyi yaratan değişiklikler sırasıyla şunlardır: serve-expired açmak, cache-max-negative-ttl‘yi artırmak, slab sayılarını doğru ayarlamak ve cache boyutunu gerçekçi olarak boyutlandırmak. Bunları yaptıktan sonra izleme altyapısını kurun, cache hit oranını takip edin ve zaman içinde sisteminizin profiline göre ince ayar yapmaya devam edin.
Unbound’un istatistik sistemi oldukça zengindir. O verileri düzenli okuyorsanız sisteminizin size ne söylediğini anlayabilirsiniz. Söylemiyorsa, veriye bakmıyorsunuzdur.
