Sunucu & Performans

ClamAV Neden Sunucunuzu Çökertir? (OOM Vaka Analizi)

ClamAV Neden Sunucunuzu Çökertir? (OOM Vaka Analizi)

Kısa cevap: Sürekli çalışan ClamAV daemon’ı (clamd) virüs veritabanını RAM’e açar ve tek başına ~1 GB, veritabanı güncellemesi (reload) anında ~1.6–1.8 GB tutabilir. Az RAM’li ve swap’siz bir sunucuda bu sıçrama, kernel’in OOM killer’ını tetikler; en büyük tüketiciyi — yani clamd’ı ya da bazen Nginx/MariaDB’yi — öldürür ve site erişilemez hale gelir. Kalıcı çözüm üç adımdır: swap ekle, clamd’a systemd cgroup bellek limiti koy (MemoryMax + OOMPolicy=stop) ve ConcurrentDatabaseReload no ile reload sıçramasını kes.

Yönetimini yeni devraldığımız bir sunucuda, bir sabah site “DNS hatası” ile erişilemez hale geldi. Sunucu daha önce başkası tarafından swap’siz ve servis kaynak limitleri konmadan kurulmuştu. İlk bakışta DDoS gibi görünen tablo, aslında az RAM’li sunucularda çok sık yaşanan ama genellikle gözden kaçan bir soruna çıktı: kontrolsüz çalışan ClamAV daemon’ı. Bu yazı, sorunu nasıl teşhis edip kalıcı olarak çözdüğümüzü — özellikle 4 GB ve altı RAM’li sunucular için — adım adım anlatıyor. Sunucu AlmaLinux + Virtualmin, ancak mantık tüm modern systemd dağıtımlarında geçerli.

Belirti: Cloudflare arkasında “DNS hatası”

Kullanıcılar siteye girmeye çalıştığında DNS hatası alıyordu, ama sunucu hâlâ ayaktaydı. Cloudflare arkasındaki bir site için bu tablo genellikle tek bir anlama gelir: Cloudflare origin sunucuya ulaşamıyor. Yani sorun DNS’te değil, origin tarafında bir servisin düşmesinde. İlk kontrol de bunu doğruladı:

Swap eklenmeden önce free -h çıktısı: 3.5Gi RAM, 3.1Gi kullanımda, yalnızca 148Mi boş, swap 0B
Düzeltmeden önce: 3.5 Gi RAM'in 3.1 Gi'si dolu, yalnızca 148 Mi boş, swap yok.
  • Mem: 3.5 Gi toplam → 3.1 Gi kullanımda → yalnızca 148 Mi boş (389 Mi available)
  • Swap: 0 B — hiç swap yok

Sunucu 31 gündür açıktı ve belleği neredeyse doluydu. Swap’siz, belleği dolu bir sistem klasik bir OOM adayıdır: RAM tükendiğinde kernel’in başvurabileceği hiçbir tampon yoktur, doğrudan process öldürmeye geçer.

Teşhis: dmesg her şeyi söyledi

Tahmin yürütmek yerine kernel günlüğüne baktık. dmesg çıktısında, günlerce neredeyse her sabah aynı saat bandında tekrar eden kayıtlar vardı:

dmesg | grep -i “out of memory”
Out of memory: Killed process 1124725 (clamd) total-vm:1110092kB, anon-rss:987260kB ... UID:989
Out of memory: Killed process 685966  (clamd) total-vm:1842848kB, anon-rss:1642568kB ... UID:989
Out of memory: Killed process 2312039 (clamd) total-vm:2047836kB, anon-rss:1841544kB ... UID:989

Öldürülen process her seferinde clamd — yani sürekli çalışan ClamAV daemon’ı (UID 989, clamav kullanıcısı). Standalone clamscan değil; doğrudan daemon’ın kendisi. Bu ayrım önemli, çünkü çözümün doğru hedefe (servise) uygulanması buna bağlı.

anon-rss değerleri (gerçek fiziksel RAM kullanımı) net bir desen gösteriyordu:

  • Normal hal: clamd ~990 MB tutuyor (anon-rss ≈ 987–993 MB).
  • Reload anı: birkaç olayda kullanım 1.64–1.84 GB‘ye fırlamış (ör. anon-rss 1642568 kB, 1841544 kB).

Sebep şu: virüs veritabanı (main.cvd, daily.cvd, bytecode.cvd) RAM’e hash tabloları olarak açılır. Disk üzerinde küçük görünen bu dosyalar bellekte ~1 GB yer kaplar. Üstelik ConcurrentDatabaseReload (ClamAV’da varsayılan olarak açıktır), güncelleme anında eski ve yeni veritabanı aynı anda yüklendiği için kullanım geçici olarak neredeyse iki katına, ~1.6–1.8 GB’ye çıkar. Yani hiçbir ek ayar yapmasanız bile bu sıçramayı varsayılan davranış olarak yaşarsınız. OOM tam bu sıçrama anlarında tetikleniyordu: swap’siz, ~148 MB boş alanı olan sistemde reload sıçraması son damla oluyor, OOM killer en büyük tüketiciyi kesiyor, systemd yeniden başlatıyor ve döngü tekrar ediyordu.

Çözüm: üç adımda kalıcı düzeltme

1. Swap eklendi

Swap’siz çalışan bir sunucu, RAM dolduğunda doğrudan servis kayıplarına uğrar; kernel’in nefes alacağı bir tampon yoktur. 2 GB swap dosyası oluşturuldu ve vm.swappiness=10 ile kernel’in mümkün olduğunca RAM’i tercih edip swap’a yalnızca gerektiğinde geçmesi sağlandı (düşük swappiness, swap’a geçme eğilimini azaltır; tamamen engellemez).

Önce mevcut swap’i kontrol edin

Aşağıdaki komut yeni bir swap dosyası oluşturur. Sunucunuzda zaten swap varsa (swapon --show ile kontrol edin) bu adımı atlayın; aksi halde mevcut /swapfile üzerine yazma riski olur.

2 GB swap oluştur (yoksa)
# Önce mevcut swap var mı?
swapon --show

# Yoksa 2 GB swap oluştur:
dd if=/dev/zero of=/swapfile bs=1M count=2048
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
echo 'vm.swappiness=10' >> /etc/sysctl.conf
sysctl -p

Asıl nefesi açan adım büyük ölçüde budur: swap, OOM killer’a alan verdiği için servisleri öldüren döngü kırıldı. Ama swap tek başına yeterli değil — clamd’ı bir sınır içine almak da gerekir.

2. systemd drop-in ile bellek limiti

systemd’nin cgroup mekanizması kullanılarak clamd@scan servisine bir bellek tavanı konuldu. Bu sayede clamd limiti aşarsa OOM killer’ın gelişigüzel başka bir servisi (Nginx, PHP-FPM, MariaDB) öldürmesi yerine, OOMPolicy=stop ile yalnızca clamd durur — sistemin geri kalanı ayakta kalır. Yani amaç clamd’ı hızlandırmak değil, izole etmek.

clamd@scan bellek limiti (drop-in)
mkdir -p /etc/systemd/system/[email protected]/
cat > /etc/systemd/system/[email protected]/override.conf << 'EOF'
[Service]
MemoryMax=700M
MemorySwapMax=1G
OOMPolicy=stop
EOF
systemctl daemon-reload
systemctl restart clamd@scan

Buradaki sayılar ölçüme dayanıyor: bu sunucuda clamd çalışırken ~692 MB kullanıyordu, dolayısıyla MemoryMax=700M daemon’ı boğmayan ama net bir tavan koyan bir tabandır. MemorySwapMax=1G ise reload anındaki ~1.6 GB’lik geçici sıçramaya tampon sağlar; o fazlalık kısa süreliğine swap’e taşınır ve kill önlenir.

Önce cgroup ve systemd sürümünü doğrulayın

MemorySwapMax yalnızca cgroup v2 (birleşik hiyerarşi) altında çalışır; cgroup v1’de sessizce yok sayılır. OOMPolicy ise systemd 243+ ister. AlmaLinux/RHEL 9, Rocky 9 ve güncel Debian/Ubuntu bunları varsayılan olarak karşılar (bu vakadaki sunucu da öyle). Ama AlmaLinux/RHEL 8 varsayılan olarak cgroup v1 ve systemd 239’dur — orada MemorySwapMax etkisiz kalır ve reload sıçraması MemoryMax‘ı aşıp cgroup içinde OOM tetikler. Kontrol: stat -fc %T /sys/fs/cgroup çıktısı cgroup2fs ise v2’desiniz; systemctl --version ile de sürümü görün. v1’deyseniz ya cgroup v2’ye geçin ya da MemorySwapMax‘a güvenmeden MemoryMax‘ı reload tepe değerinin üzerine (ör. 2G) çekin.

Daha bol RAM’iniz varsa daha basiti yeter

Bu kombinasyon az RAM’li sunucular için tasarlandı. Rahat RAM’iniz varsa MemoryMax=2G koyup (clamd’ın reload tepe değerinin üzerinde) clamd’ı tamamen RAM’de çalıştırmak, swap’e hiç ihtiyaç duymadan yeterlidir; tarama hızı da etkilenmez.

3. ConcurrentDatabaseReload kapatıldı

Asıl OOM sıçramalarının kaynağı reload anındaki çift veritabanı yüküydü. /etc/clamd.d/scan.conf dosyasına tek satır eklemek bunu kesiyor:

/etc/clamd.d/scan.conf
ConcurrentDatabaseReload no

Bu davranış ClamAV’ın kendi dokümantasyonunda da açıkça belirtilir: özellik aktifken reload sırasında geçici olarak ikinci bir tarama motoru yüklenir (daha fazla RAM); no yapıldığında ise reload anında taramalar kısa süre bloke edilerek daha düşük RAM kullanımı elde edilir. Takas budur: reload sırasında clamd kısa bir süre tarama yapamaz. Düşük RAM’li bir sunucuda bu, RAM’in iki katına çıkıp OOM tetiklemesine kıyasla kabul edilebilir bir takastır. Bu tek ayar bile çoğu durumda krizin büyük kısmını çözer.

Sonuç: ölçülmüş fark

Swap eklendikten sonra free -h çıktısı: 2.0Gi swap aktif, available RAM 846Mi'ye yükseldi
Düzeltmeden sonra: 2.0 Gi swap aktif, kullanılabilir (available) RAM 846 Mi'ye yükseldi.
Metrik Önce Sonra
Boş RAM (free) 148 MB 303 MB
Kullanılabilir RAM (available) 389 MB 846 MB
Swap Yok 2 GB aktif (~785 MB kullanımda)
clamd bellek kullanımı ~990 MB normal, reload’da ~1.6 GB ~692 MB, limit 700 MB + 1 GB swap tamponu
OOM kill Günlerce, neredeyse her sabah Düzeltme sonrası 0
Site durumu Erişilemez Stabil

Not: dmesg’deki son OOM kill, düzeltmenin uygulandığı sabahın hemen öncesine aitti. Düzeltme devreye girdikten sonra yeni bir kill kaydı oluşmadı.

Hangi sunucuda hangi reçete?

Bu vakanın asıl dersi tek bir sihirli sayı değil, sunucu profiline göre doğru yaklaşımdır:

  • 4 GB ve altı RAM, swap’siz sunucular: Bu yazıdaki tam reçete — swap ekle, MemoryMax≈700M + MemorySwapMax=1G ile clamd’ı sınırla, ConcurrentDatabaseReload no ile reload sıçramasını kes. Az kaynakta ciddi bir ferahlama sağlar.
  • Daha bol RAM’li sunucular: Çoğu zaman daha basiti yeterli — MemoryMax=2G (clamd’ın reload tepe değerinin üzerinde) ve ConcurrentDatabaseReload no. Swap’e gerek kalmaz, clamd tamamen RAM’de çalışır, tarama hızı da etkilenmez.

Özellikle kendi sunucusunda e-posta barındıran, gelen ekleri ClamAV ile tarayan sunucularda bu konfigürasyon kritik önem taşır — clamd zaten bu senaryoda sürekli açık durur. ClamAV gerekli bir bileşendir; ama kaynak limiti konmadan bırakılırsa tüm sistemi aşağı çekebilir. swap + cgroup izolasyonu (MemoryMax + OOMPolicy=stop) tam bu iş içindir: bir servisin tek başına sunucuyu çökertmesini engeller. Aynı disiplini bir sunucunun tüm katmanlarına yaymak istiyorsanız Virtualmin/Webmin sunucu sıkılaştırma rehberimize bakın; WordPress tarafında bellek ve önbellek ayarını PHP-FPM, OPcache ve Redis tuning, veritabanı belleğini ise MariaDB tuning yazısında ele aldık.

Sıkça sorulan sorular

clamd neden bu kadar çok RAM kullanıyor?

ClamAV daemon’ı başlarken virüs veritabanını (main, daily, bytecode) diskten okuyup RAM’e hash tabloları olarak açar. Disk üzerinde küçük görünen bu dosyalar bellekte ~1 GB yer kaplar. Bu, clamd’ın normal ve beklenen davranışıdır; tarama hızını sağlayan şey de budur.

ConcurrentDatabaseReload’u kapatmak güvenli mi?

Evet. Kapattığınızda tek fark, veritabanı güncellendiği kısa an boyunca taramaların bloke olmasıdır; güncelleme bitince normale döner. Karşılığında reload anındaki RAM kullanımının iki katına çıkması önlenir. Düşük RAM’li sunucularda bu takas neredeyse her zaman doğrudur.

MemoryMax=700M clamd’ı bozar mı?

Bozmaz, çünkü değer ölçüme dayanır: bu sunucuda clamd ~692 MB kullanıyordu, 700M net bir tavan koyar. Reload anındaki geçici sıçrama için MemorySwapMax=1G tampon sağlar. Kendi sunucunuzda önce systemctl status clamd@scan veya ps ile clamd’ın gerçek kullanımını ölçüp tavanı ona göre belirleyin.

Swap eklemek sunucuyu yavaşlatmaz mı?

vm.swappiness=10 ile kernel RAM’i tercih eder, swap’a yalnızca gerektiğinde geçer; yani gündelik performansı etkilemez. Swap burada bir “acil durum tamponu” görevi görür: RAM aniden dolduğunda servisleri öldüren OOM yerine, fazlalık kısa süreliğine diske taşınır.

ClamAV’ı tamamen kaldırsam olmaz mı?

Sunucu mail alıyorsa ClamAV gerçek bir güvenlik ihtiyacıdır; kaldırmak sorunu değil korumayı ortadan kaldırır. Doğru yaklaşım onu kaldırmak değil, kaynak sınırıyla (cgroup + swap) sistemin geri kalanından izole etmektir.

DKS pratikte ne yapıyor?

Az kaynaklı sunucularda “her servis eşit derecede tehlikeli değildir” — bir tanesi (burada clamd) tüm sistemi aşağı çekebilir. Biz bir sunucuyu devraldığımızda ClamAV, PHP-FPM ve MariaDB gibi bellek-aç servisleri baştan cgroup limitleriyle sınırlıyor, swap tamponunu standart kuruyor ve dmesg/OOM kayıtlarını izliyoruz. Bu sürekli izleme ve limitleme işini bakım hizmetimizde üstleniyoruz; sıfırdan doğru kurulum için ise sunucu kurulumu ve altyapı tarafında çalışıyoruz.

İlgili yazı

Aynı “systemd ile servis disiplini” temasında, Virtualmin’de --nofork/forking modu farkından kaynaklanan başka bir vakayı da inceledik: Usermin “Failed to open PID file” hatası ve çözümü.

Sunucunuz OOM ile mi çöküyor?

Sitesi ara ara “erişilemez” olan, az RAM’li bir sunucunuz varsa sebebi çoğu zaman budur. Bizimle iletişime geçin; dmesg ve bellek profilini inceleyip kalıcı bir izolasyon planı çıkaralım.

Digital Kernel Software

Digital Kernel Software

WordPress tema geliştirme, hosting ve sunucu performansı üzerine yazıyoruz. İstanbul / Türkiye.

Projeniz mi var?

Okuduğunuzu
projenize uygulayalım

WordPress, hosting ve performans tarafında yazdıklarımızı sizin işinizde hayata geçirelim. Net konuşuruz — pazarlama dili değil, uygulanabilir çözüm.