İçeriğe Atla
Mustafa Erbay
Teknoloji İnsan tarafından yazıldı Production Diaries · 9 dk okuma · görüntülenme Read in English
100%

VPS'te 3. Kez OOM: Paralel Build'ler ve flock Mutex Hikayesi

Blog otomasyonum başka projenin build'iyle çakıştı. RAM tükendi, sshd reset etti. Hard reboot + flock ile global build mutex çözümü.

VPS'te 3. Kez OOM: Paralel Build'ler ve flock Mutex Hikayesi — yaşanmış hikaye kapak görseli

Sahne: “siteler kapandı dostum”

7 Mayıs Çarşamba, öğleden sonra. Telefonda mesaj geldi: “siteler kapandı dostum.”

Hızlı kontrol ettim: kendi blogum (mustafaerbay.com.tr), aynı VPS’te yaşayan diğer Next.js uygulamaları, islistesi — hiçbiri açılmıyor. Sadece hesapciyiz.com ve spamkalkani.com 200 dönüyor. Onlar farklı bir altyapıda barındığı için bu sefer onlar kurtulmuş.

İlk SSH denemesi:

$ ssh vps
Connection timed out during banner exchange
Connection to 141.95.1.22 port 22 timed out

Bu hata mesajını üçüncü defa görüyorum. Banner exchange timeout demek “TCP açıldı ama sshd hello bile yollayamadı” demek. Tipik tek nedeni var: sistem RAM’i bitmiş, sshd yeni connection için fork edemiyor.

curl da aynı hikayeyi anlatıyordu:

$ curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" --max-time 8 https://mustafaerbay.com.tr
000 8.006s

Network seviyesinde port açık, ama process’ler yetişemiyor.

Pattern tanıdık geldi

Bu olay aynı VPS’te bana üçüncü kez olan bir senaryo:

  • 28 Nisan: Disk %100 doldu (Docker 33 GB build cache + 23 GB unused image). Yarım gün outage.
  • 4 Mayıs: RAM tükenip swap fistirması (kcompactd %92 CPU, sshd accept edemedi). Hard reboot.
  • 7 Mayıs: Şimdi yine aynı tablo.

İlk ikisinde kendimi savunmaya hazırlamıştım. Çoklu katman koruma kurmuştum:

  • Pre-flight resource guard (workflow başında disk/load/RAM check, eşik altıysa skip).
  • Disk-cleanup timer (Docker build cache + dangling image otomatik temizlik).
  • Polling-based deploy wait (sleep 360 yerine canlı URL polling — kaynak harcamaz).
  • Pipeline-health monitor (state-change’de tek mail, spam yok).

Bunların hepsi çalışıyordu. Yine de bugün patladı. Demek ki eksik bir şey vardı.

Diagnostic akışı

VPS’e ulaşamadığım için ilk hamle GitHub Actions tarafından oldu. Aktif workflow var mı kontrol ettim:

$ gh run list --status=in_progress --limit 5
in_progress  Generate Content  schedule  25490764410  18m10s  ...

18 dakikadır koşan bir content-generate cron’u vardı. Normalde 12-14 dk sürer, 18 dakika tıkanmış demek. Cancel komutunu yolladım:

$ gh run cancel 25490764410
 Request to cancel workflow 25490764410 submitted.

Cancel’ı yolladım ama ulaşacağına da emin değildim. Cancel sinyali GitHub’dan VPS’teki self-hosted runner’a, oradan da step process’ine inecek. Eğer process zaten OOM altında thrashing yapıyorsa, SIGTERM bile düzgün delivere edilmiyor.

Beklediğim gibi oldu — birkaç dakika boyunca SSH kabul etmedi:

$ for i in 1 2 3 4; do sleep 25; ssh -o ConnectTimeout=15 vps 'uptime'; done
[Try 1] Connection timed out during banner exchange
[Try 2] Connection timed out during banner exchange
[Try 3] Connection timed out during banner exchange
[Try 4] Connection timed out during banner exchange

100 saniye sonunda hâlâ timeout. Linux OOM killer bile yetişemiyor çünkü kendi de memory ister, sistem o kadar swap thrashing yapıyor ki kernel çağrıları yavaş geliyor.

Tek çare kaldı: hard reset. OVH panelinden VPS’i sıfırdan başlattım. 90 saniye sonra geri geldi:

$ ssh vps 'uptime; free -h | head -2'
 11:01:14 up 1 min,  2 users,  load average: 0.98, 0.43, 0.16
               total        used        free      shared  buff/cache   available
Mem:           7.6Gi       1.6Gi       4.5Gi       105Mi       1.8Gi       6.0Gi

Tüm container’lar restart: unless-stopped ile auto-start oldu, postgres’ler WAL ile recover etti, nginx ayağa kalktı. ~2 dakikada tüm siteler 200 dönüyordu.

Ama asıl iş kök sebep.

Kök sebep: paralel build varsayımı

Cancel ettiğim mustafaerbay workflow’un log’una baktım. Validate adımındaydı, astro build yapıyordu. Tek başına 2.5 GB RAM yiyor. 7.6 GB sistemde tek başınaysa sorun değil — VPS’in 5 GB’ı diğer container’lara (postgres’ler, redis’ler, app’ler) zaten ayrılmış. Geriye 1-2 GB serbest, build için yeterli.

Ama o esnada başka bir projenin docker-compose build’i de fırlamış. Aynı VPS’te yarım düzine farklı proje yaşıyor — kendi yan ürünlerim ve müşteri uygulamaları karışık. Mustafaerbay (Astro), gercekveri.com, islistesi gibi kendi projelerim, ek olarak Next.js + postgres + redis kullanan birkaç başka uygulama.

Her biri kendi update mekanizmasıyla deploy ediyor. Bağımsız döngüleri var. Bir gün geliyor, ikisi aynı dakika içinde build’e giriyor.

2 paralel build × ~2 GB her biri = 4 GB ek RAM ihtiyacı. VPS’in elinde olmayan bir miktar. Sistem swap’a iniyor, swap dolduğunda kcompactd memory compaction için CPU’yu bitiriyor, fork-able RAM kalmıyor, sshd connection alamıyor, hostage durumu.

Şimdiye kadar kurduğum tüm korumalar kendi pipeline’ım için zamansal: workflow başında bakıyorum, eşik geçti mi? Geçtiyse skip. Sorun şu: build başladıktan sonra başka projenin build’i girerse ben göremiyorum. Pre-flight bir snapshot, build süreci 4-5 dakika sürüyor. Arada her şey değişebilir.

Yani benim sistem: “single-tenant VPS” varsayımıyla yazılmış. Gerçeklik: multi-tenant.

Çözüm: VPS-wide build mutex

Klasik mutex problemi. Tek bir kaynağa (RAM) birden fazla işin erişimi var, sıralanması lazım. Linux’ta bunu en temiz flock ile yaparsın:

# Klasik kullanım
flock -w 900 /var/lock/vps-build.lock <build-komutu>

-w 900: lock 15 dakika içinde alınmazsa pes et, exit 124 dön. Bu graceful skip için kritik — sonsuz beklersen runner’ı meşgul edersin, kuyruk kabarır.

Üç yere uyguladım:

1. mustafaerbay’in VPS deploy script’i (deploy/update.sh):

echo "building (into dist-new)"
rm -rf dist-new
if ! flock -w 900 /var/lock/vps-build.lock \
       env DEPLOY_TARGET=node npx astro build --out-dir dist-new; then
  echo "BUILD FAILED OR LOCK TIMEOUT — sonraki deploy denesin"
  rm -rf dist-new
  exit 0
fi

İki şey aynı anda olur: lock alınır → build çalışır. Lock alınamazsa (başka proje build ediyor) 15 dk bekler. Süre dolarsa skip — bir sonraki dakikada deploy timer yine pull edip dener.

2. GitHub Actions validate adımı (content-generate.yml):

if flock -w 900 /var/lock/vps-build.lock \
    env DEPLOY_TARGET=node npx astro build --out-dir dist-validate; then

Aynı lock’u workflow build’i de saygı gösteriyor.

3. Diğer projeler için reusable wrapper (/usr/local/bin/vps-build-lock.sh):

#!/usr/bin/env bash
exec flock -w 900 /var/lock/vps-build.lock "$@"

Diğer projelerimin deploy script’lerinin başına bunu eklemem yeter:

# Önce
docker-compose build app

# Sonra
/usr/local/bin/vps-build-lock.sh docker-compose build app

Neden flock ve neden 15 dakika

flock Linux çekirdek seviyesinde fcntl-tabanlı dosya kilidi. Process die ederse (kill -9 dahil) lock otomatik bırakılır. Bu kritik — eğer manuel state file kullansaydım, process patlarsa stale lock kalırdı, manuel temizlemek lazım olurdu.

15 dakika seçimi keyfi değil:

  • Tek build maksimum ~4 dk sürüyor (Astro + Pagefind dahil).
  • En kötü senaryoda 3 build kuyrukta beklerse sonuncu 12 dk sonra başlar.
  • 15 dk eşiği “3 build geriye düştüm” durumunda graceful skip ediyor.
  • Cron her 30 dk koştuğu için skip = “bir sonraki dakikada tekrar dene” anlamına geliyor.

Kritik nokta: Bekleme aşamasında RAM tüketimi yok. flock blok’lanan process zaten asıl iş yapmaya başlamamış olur, çekirdekte uyumakta. Sadece bir kaç MB stack tutuyor. RAM yangını çıkmaz çünkü asıl yangının kaynağı — paralel build memory talebi — engellenmiş.

Daha derin ders

Bu olay bana 3 şey öğretti:

1. “Single-tenant” varsayımı multi-tenant ortamda gizli bomba.

Her projeye kendi içinde “iyi vatandaş” davranma kuralları yazdım: rate-limit, retry-with-backoff, preflight check, cleanup timer. Ama bunlar projenin kendi bilincinde olduğu kaynaklar için. Paylaşılmış kaynak — bu durumda RAM — için sistem seviyesinde koordinasyon gerek. Her projenin kendi etiğini bilmesi yetmiyor.

2. SIGTERM her zaman ulaşmaz.

GitHub’dan workflow cancel ettim, ulaşmadı. Çünkü system-wide thrashing altında signal delivery yavaşlıyor, bazen kaybolabiliyor. Cancel’ı kurtuluş çaresi olarak görme. Sorunu baştan engelle, nükleer seçenek olarak hard reboot kalsın.

3. Aynı hatayı 3 kez yaptıysan, problemi çözmediğin demektir.

İlk OOM’da disk-cleanup ekledim. İkinci OOM’da polling-wait. Bu üçüncü olayda anladım: Asıl sorun ne disk ne timing — kaynak rezervasyonu. Önceki çözümler semptomları etkiledi, hastalığı değil. flock, hastalığı çözüyor. Hâlâ paralel build başlatabilirsin, ama başlattığında sıraya giriyor, eş zamanlı koşmuyor.

Sonuç

Şu an df6fd98 commit’iyle mutex aktif. Bir sonraki cron’da iki paralel build başlatılırsa biri 15 dk diğerini bekleyecek. RAM yangını mümkün değil çünkü her an tek build koşar.

Bu yazıyı bunu yaşadığım gün yazıyorum. Hâlâ duş alır gibi yorgunum. Ama bu duş yerine post-mortem yazmanın değeri var: bir daha aynı saatte aynı şey yaşandığında, “geçen seferki notu okuyup ne yaptım hatırlayayım” diyebileceğim.

Bu tarafta bir VPS’in olsun. Multi-tenant’a gidersin er geç. flock’u baştan koy.

Paylaş:

Bu yazı faydalı oldu mu?

Yükleniyor...

Bu yazı nasıldı?

ME

Mustafa Erbay

Sistem Mimarisi · Network Uzmanı · Altyapı, Güvenlik ve Yazılım

2006'dan bu yana sistem mimarisi, network, sunucu altyapıları, büyük yapıların kurulumu, yazılım ve sistem güvenliği ekseninde çalışıyorum. Bu blogda sahada karşılığı olan teknik deneyimlerimi paylaşıyorum.

Kişisel Notlar

Bu notlar sadece sizde saklanır. Tarayıcınızda yerel olarak tutulur.

Hazır 0 karakter

Yorumlar

Sunucu Taraflı AI Moderasyon

Yorumlar sunucuda yapay zeka ile denetlenir ve kalıcı olarak saklanır.

?
0/2000

Sunucu taraflı AI denetim

✉️ Ücretsiz · Spam yok · İstediğin an çık

Haftalık özet — AI değil, bizzat ben seçiyorum

Haftada bir mail: o haftanın en önemli yazısı, perde arkası notları, ve "bu hafta gerçekten kullandığım araç" bölümü. Az gürültü, çok sinyal.

  • 📌
    Haftanın en iyisi Sadece okumaya değer tek yazı
  • 🔧
    Alet çantası Bu hafta kullandığım araçlar
  • 🧠
    Perde arkası Blog'a girmeyen notlar

Spam yapmıyoruz. İstediğiniz zaman ayrılabilirsiniz. · Sadece Umami (self-hosted, Google yok) ile takip.

Okuma İstatistikleriniz

0

Yazı Okundu

0dk

Okuma Süresi

0

Gün Serisi

-

Favori Kategori

İlgili Yazılar