Gece MySQL kendiliğinden kapanmış, site "Error establishing a database connection" veriyor ya da bir Node.js uygulaması sebepsiz yere yeniden başlamış. Uygulama loglarında hiçbir hata yok. Böyle durumlarda ilk şüphelenmeniz gereken, Linux çekirdeğinin OOM killer (Out Of Memory killer) mekanizmasıdır: bellek tamamen tükendiğinde çekirdek, sistemi ayakta tutmak için bir süreci seçip zorla sonlandırır.
Bu rehberde OOM olayının loglarda nasıl göründüğünü, dmesg ve journalctl ile nasıl bulunacağını, çekirdeğin kurbanı nasıl seçtiğini (oom_score ve oom_score_adj), MySQL'in neden çoğu zaman ilk hedef olduğunu, systemd ile servis bazında bellek sınırı koymayı ve kalıcı çözümleri anlatıyoruz. Komutlar Ubuntu 24.04 LTS içindir.
Belirtiler

- Bir servis uyarısız kapanıyor,
systemctl statusiçindesignal=KILLveyaoom-killifadesi görünüyor. - Uygulama logunda kapanış nedeni yazmıyor; çünkü SIGKILL sinyali yakalanamaz, süreç kendi logunu yazamadan ölür.
- Olaydan önce sunucu belirgin şekilde yavaşlıyor, swap yoğun kullanılıyor.
- Sorun hep yoğun saatlerde veya belirli bir cron işi sırasında oluyor.
Adım 1: OOM olayını loglarda bulun
OOM killer çekirdek mesajı yazar. Linux çekirdeği kaynak kodu: mm/oom_kill.c dosyasındaki mesaj biçimlerine göre aranacak ifadeler şunlardır:
sudo dmesg -T | grep -iE "out of memory|oom-killer|killed process"
sudo journalctl -k --since "2 days ago" | grep -iE "out of memory|oom-killer|killed process"
# Bir servis özelinde:
sudo journalctl -u mysql --since today | tail -n 30Tipik bir olay iki ana satırdan oluşur (sayılar örnektir):
php-fpm8.3 invoked oom-killer: gfp_mask=0x140cca(GFP_HIGHUSER_MOVABLE|__GFP_COMP), order=0, oom_score_adj=0
...
Out of memory: Killed process 1234 (mysqld) total-vm:2457600kB, anon-rss:1310720kB, file-rss:0kB, shmem-rss:0kB, UID:110 pgtables:3200kB oom_score_adj:0| Parça | Anlamı |
|---|---|
... invoked oom-killer | Bellek isteyip karşılanamayan süreç. Kurban olmak zorunda değildir. |
Out of memory: Killed process | Sistem genelinde bellek bitti ve bu süreç öldürüldü. |
Memory cgroup out of memory | Sistem değil, bir cgroup sınırı (örneğin systemd MemoryMax veya bir Docker konteyner limiti) aşıldı. |
anon-rss | Sürecin kullandığı anonim bellek (heap, buffer pool gibi); kurbanın ne kadar yer tuttuğunu gösterir. |
oom_score_adj | Kurbanın ayar değeri; aşağıda anlatılıyor. |
Adım 2: Çekirdek kurbanı nasıl seçer?
proc_pid_oom_score(5) kılavuz sayfasına göre /proc/[pid]/oom_score, çekirdeğin o sürece OOM için verdiği güncel puandır; puan ne kadar yüksekse sürecin seçilme olasılığı o kadar yüksektir ve puanın temeli sürecin kullandığı bellek miktarıdır. proc_pid_oom_score_adj(5) sayfasına göre oom_score_adj değeri bu puana eklenir; en düşük değer olan -1000, o süreç için OOM ile öldürmeyi fiilen kapatır.
# En yüksek OOM puanına sahip 10 süreç
for p in /proc/[0-9]*; do
printf "%s %s %s\n" "$(cat $p/oom_score 2>/dev/null)" "${p#/proc/}" "$(cat $p/comm 2>/dev/null)"
done | sort -rn | head -n 10
# Belirli bir sürecin ayar değeri
cat /proc/$(pgrep -o mysqld)/oom_score_adjMySQL neden hep ilk ölen?

Puan büyük ölçüde bellek kullanımına dayandığı için, sunucudaki en büyük süreç genellikle veritabanıdır. MySQL/InnoDB, verileri ve indeksleri buffer pool adı verilen bellek alanında tutar. MySQL 8.4 Reference Manual: InnoDB Buffer Pool sayfası, yalnızca veritabanına ayrılmış sunucularda fiziksel belleğin yüzde 80'ine kadarının buffer pool'a verildiğini belirtir. Aynı sunucuda Nginx, PHP-FPM, Redis ve başka servisler de çalışıyorsa bu oran fazladır ve diğer servisler büyüdükçe bellek biter; çekirdek de en büyük süreci, yani MySQL'i seçer.
sudo mysql -e "SELECT @@innodb_buffer_pool_size/1024/1024 AS buffer_pool_mb;"
free -h- Paylaşılan sunucuda buffer pool'u, diğer servislerin ihtiyacını çıkardıktan sonra kalan belleğe göre boyutlandırın.
- PHP-FPM işçi sayısı (
pm.max_children) çarpı işçi başına bellek, en sık gözden kaçan tüketicidir. Hesap yöntemi PHP-FPM yapılandırma yazısında. - Bellek sızıntısı olan uygulamalarda PHP için
pm.max_requests(PHP belgesine göre işçiyi belirli sayıda istekten sonra yeniden başlatır; varsayılan 0, yani kapalı) yardımcı olabilir.
Adım 3: Kimin ne kadar bellek kullandığını görün
free -h
ps -eo pid,user,rss,%mem,cmd --sort=-rss | head -n 15 # rss KiB cinsinden
systemd-cgtop -m # servis bazında bellekfree -h çıktısında available sütunu, uygulamaların swap'a düşmeden kullanabileceği tahmini belleği gösterir; free sütununun küçük olması tek başına sorun değildir, çünkü Linux boş belleği önbellek için kullanır. Anlık ölçümün yanında vmstat 2 ile si/so sütunlarını izleyin; ayrıntılar load average rehberinde.
Adım 4: systemd ile servis bazında bellek sınırı
Sorunlu bir servisin tüm sunucuyu çökertmesini önlemek için onu kendi bellek sınırına hapsedebilirsiniz. systemd.resource-control(5) belgesine göre MemoryHigh= bir kısma (throttling) sınırıdır: aşılırsa süreçler ciddi şekilde yavaşlatılır. MemoryMax= ise mutlak sınırdır: kullanım bu sınırın altında tutulamazsa OOM killer yalnızca o birimin içinde çalışır. Belge, ana kontrol olarak MemoryHigh= kullanılmasını ve MemoryMax='ın son savunma hattı olarak tutulmasını önerir. K, M, G, T son ekleri ve fiziksel belleğe göre yüzde değerleri kabul edilir.
sudo systemctl edit php8.3-fpm[Service]
MemoryHigh=1500M
MemoryMax=2Gsudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p MemoryHigh -p MemoryMax
systemctl status php8.3-fpm # Memory satırında sınır görünürDeğerler örnektir; kendi sunucunuzun RAM'ine ve ölçtüğünüz kullanıma göre belirleyin. Bu sınır aşıldığında loglarda "Memory cgroup out of memory" mesajını görürsünüz ve yalnızca o servis etkilenir, veritabanınız ayakta kalır. Drop-in dosyaları ve servis yapısı için systemd servis dosyası rehberi.
Adım 5: Kritik servisi korumak: OOMScoreAdjust
systemd.exec(5) belgesine göre OOMScoreAdjust=, birimin süreçleri için -1000 ile 1000 arasında bir değer alır; -1000 OOM ile öldürmeyi kapatır, 1000 ise bellek baskısında öldürülmeyi çok olası kılar. Veritabanının daha geç seçilmesini isterseniz:
sudo systemctl edit mysql
# Açılan dosyaya:
# [Service]
# OOMScoreAdjust=-500
sudo systemctl restart mysqlKalıcı çözümler

| Neden | Çözüm |
|---|---|
| Swap hiç yok | Küçük bir swap, ani sıçramalarda tampon sağlar: Sunucuda swap alanı. Kernel belgelerine göre vm.swappiness varsayılanı 60'tır. |
| PHP-FPM işçi sayısı fazla | pm.max_children değerini işçi başı belleğe göre hesaplayın: PHP-FPM yapılandırma |
| MySQL buffer pool büyük | Diğer servisleri hesaba katarak küçültün |
| Bellek sızıntısı | Uygulamayı düzeltin; geçici olarak pm.max_requests veya servis bazında MemoryMax |
| Gerçekten RAM yetmiyor | Daha fazla RAM'li plana geçin veya veritabanını ayrı sunucuya taşıyın |
Önleme ve izleme
- Bellek kullanımını ve
availabledeğerini sürekli izleyin; sunucu izleme temelleri ve Prometheus ve Grafana. - Servis çöktüğünde haber almak için dışarıdan izleme kurun: Uptime Kuma kurulumu.
- Kritik servislerde systemd
Restart=on-failurekullanın; servis öldürülse bile otomatik geri gelir (nedeni yine araştırılmalıdır). - OOM sonrası veritabanında tutarsızlık riskine karşı düzenli yedek alın: veritabanı yedekleme stratejileri.
- Bellek kullanan bir süreç CPU'yu da meşgul ediyorsa CPU yüzde 100 tespit rehberine bakın.
Sık Sorulan Sorular
OOM killer'ı tamamen kapatabilir miyim?
Önerilmez. OOM killer, bellek bittiğinde sistemin tamamen kilitlenmesini önleyen son mekanizmadır. Kapatmaya çalışmak yerine bellek tüketimini sınırlayın ve kapasiteyi doğru planlayın.
Swap eklemek OOM'u tamamen çözer mi?
Kısa süreli sıçramaları yumuşatır, ama sürekli bellek açığını çözmez. Swap aktif olarak kullanılıyorsa sunucu ciddi şekilde yavaşlar; bu da ayrı bir sorundur. Swap'ı tampon olarak görün, RAM yerine değil.
Docker konteynerim neden öldü?
Konteynere bellek sınırı verdiyseniz ve aşıldıysa çekirdek loglarında "Memory cgroup out of memory" görürsünüz. Bu durumda sorun sunucunun değil konteynerin sınırıdır; sınırı uygulamanın gerçek ihtiyacına göre ayarlayın.
Hangi servis öldürüldü, sonradan nasıl anlarım?
journalctl -k çıktısında "Killed process" satırını arayın; PID ve süreç adı oradadır. Kalıcı log ayarları ve log okuma için Linux loglarını okuma ve logrotate rehberi yazılarına bakın.
Yazılım ve hizmet adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen projeler veya şirketlerle ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Load average nedir · CPU yüzde 100 tespiti · inode doldu hatası · logrotate rehberi · Sunucuda swap alanı · PHP-FPM yapılandırma · systemd servis dosyası