teknikbakis
Saha derlemesi · Eylül 2026'dan dört vaka

Beş gece "OK", beş boş yedek: sonucu değil adımı ölçen başarı sinyalleri

Eylül 2026'da üç ayrı sistemde dört kez aynı şeyle karşılaştım: arıza vardı, izleme "başarılı" diyordu. Yedek betiği OK yazdı, servis active göründü, sayfa 200 döndü, hat kaydı "işlendi" diye kapattı. Hepsi doğru söylüyordu — yalnız ölçtükleri şey, benim sandığım şey değildi.

KAYIT TB-2026-009
KAPSAM 3 sistem
VAKA SAYISI 4 + 2 bağlantılı
DÖNEM 2026-09
TİP Derleme
ORTAK TEŞHİS Adım ölçülüyor, sonuç değil

Sessizlik değil, yanlış bir evet

TB-2026-004'ün son başlığında sessiz başarısızlığı anlatmıştım: bozulan bir sistem bunu söylemiyorsa bozukluk sıraya hiç girmez. Bu derleme bir adım ötesi. Buradaki sistemlerin hiçbiri susmadı; her biri düzenli olarak başarılı olduğunu bildirdi. Sorun sinyalin yokluğu değil, yanlış bir evetti.

Dört vakayı yan yana koyunca ortak nokta çıktı: her başarı sinyali bir adımın tamamlandığını ölçüyordu — döküm komutunun bitmesini, sürecin ayakta olmasını, sunucunun yanıt vermesini, kaydın hattan geçmesini. Hiçbiri o adımın sonucunu ölçmüyordu: yedekte canlı veri var mı, veri hâlâ geliyor mu, çalışan kod yeni kod mu, kayıt gerçekten zenginleşti mi.

1. Beş gece "OK", beş boş yedek

Bir müşterinin panel uygulamasında veritabanı 30 Ağustos'ta yeni bir ada taşındı. Gecelik yedek betiği eski adı dökmeye devam etti. Eski veritabanı silinmemişti; boş şemasıyla yerinde duruyordu. Betik hata vermedi, çünkü verecek bir hata yoktu: var olan bir veritabanını başarıyla döktü.

EK-1 Beş gecenin yedek logu · tarih damgaları çıkarıldı
MySQL OK 24034 bayt
MySQL OK 24034 bayt
MySQL OK 24034 bayt
MySQL OK 24034 bayt
MySQL OK 24034 bayt

Canlı veri 83,8 MB'tı ve hiç yedeği yoktu. Beş yedeğin beşi 24 KB'lık boş şemaydı. Tek işaret, boyutun her gece bayt bayt aynı çıkmasıydı.

İzleme açısından her şey yeşildi: systemctl --failed temiz, zamanlayıcı her gece tetiklenmiş, log "OK". Oysa yaşayan bir veritabanının yedeği her gece birkaç bayt da olsa değişir. Beş gece aynı kalan boyut, ölü bir kaynağın imzasıydı.

Betiği okurken ikinci kusur çıktı. Eski yedekleri temizleyen satırda derinlik ve ad sınırı yoktu:

# önce: alt klasörler dahil her şeyi siler
find "$D" -type f -mtime +14 -delete

# sonra: yalnız üst dizin, PRE- ile başlayan anlık görüntüler hariç
find "$D" -maxdepth 1 -type f -mtime +14 ! -name "PRE-*" -delete

İlk hâliyle, elle alınmış yedeklerin durduğu alt klasörü ve taşımadan önce alınmış anlık görüntüyü — canlı veriyi taşıyan tek yedeği — on dört gün dolunca silecekti. İki hafta içinde fark edilmeseydi elimizde yalnız boş yedekler kalacaktı.

EK-2 Yedeği "alındı" saymadan önceki üç kontrol · örnek yol ve şema
Y=/yedek/gece/son.sql.gz
gzip -t "$Y" || echo "ARŞİV BOZUK"
zgrep -c '^CREATE TABLE' "$Y"        # canlıdaki tablo sayısıyla aynı mı?

for t in $(mysql -N -e "SELECT table_name FROM information_schema.tables
                         WHERE table_schema = 'uygulama'
                         ORDER BY data_length DESC LIMIT 5"); do
  n=$(zgrep -c "^INSERT INTO \`$t\`" "$Y")
  echo "$t: $n INSERT satırı"        # 0 ise yedek boştur
done

Tablo adları canlı veritabanından okunuyor, tahmin edilmiyor. Boş şema da gzip -t'den geçer; içinde veri olup olmadığını yalnız üçüncü kontrol söyler.

NASIL YAKALANIR

Yedeğin başarısını betiğin çıkış kodundan değil, içeriğinden ölçün. Boyutu izliyorsanız değişmemesini de alarm sayın. Veritabanı adını, kullanıcısını ya da şemasını değiştiren her iş, onu kullanan yedek betiğini aynı oturumda doğrulamayı kapsar; silme kuralına derinlik sınırı ve taban anlık görüntüler için ad istisnası konur.

2. Bir kez öten, sonra "sağlıklı" diyen gözlemci

Bir tuzak sensöründen gelen veri akışını izleyen gözlemciye, bildirim kanalını boğmasın diye "olay başına tek alarm" kuralı koymuştum: sorun ilk görüldüğünde bir bildirim gider, işaret konur, sonraki turlarda aynı sorun listeden düşer. Spam'e karşı doğru bir karar — ta ki o tek bildirim kaçana kadar.

27 Eylül'de bildirim geldi ve yoğun bir çalışma oturumunun ortasında gözümden kaçtı. Ardından durum satırı her beş dakikada bir "sağlıklı" yazdı, üç buçuk gün boyunca. Asıl arıza sensörün kayıtlarını okuyan ayrıştırıcıdaydı: olay döngüsünü kilitlemiş, işlemciyi %100'de tutuyordu. systemd ise onu "active" görüyordu, çünkü süreç yaşıyordu. Yaşıyordu ama hiçbir şey yapmıyordu.

Ortaya çıkışı da bir alarmla olmadı. "Şu an kaç kaynaktan veri alıyoruz?" sorusunu cevaplamak için kaynak başına son olay zamanına baktım; bir kaynağın son olayı üç buçuk gün öncesindeydi.

EK-3 Kaynak tazeliği · beklenen listeden başlayan sorgu (örnek şema)
SELECT k.ad,
       max(o.ts)         AS son_olay,
       now() - max(o.ts) AS sessizlik
  FROM kaynaklar k
  LEFT JOIN olaylar o
         ON o.kaynak = k.ad
        AND o.ts > now() - interval '7 days'
 GROUP BY k.ad
 ORDER BY son_olay NULLS FIRST;

Sorgu olay tablosundan değil, beklenen kaynakların listesinden başlıyor. Olaylardan başlasaydı tamamen susan bir kaynak sonuçta hiç görünmezdi — sorgunun kendisi sessiz başarıya düşerdi.

Tek alarm kuralını kaldırmadım; kanalı susturmak hâlâ doğru. Değişen şu: süren bir sorun artık durum satırında "SÜREN SORUN (süre)" olarak, ilk görüldüğü andan beri geçen süreyle birlikte duruyor ve altı saatte bir yeniden hatırlatılıyor. "Bildirim gönderildi" işareti ile "sorun sürüyor" durumu ayrı tutuluyor; biri ötekini silmiyor.

NASIL YAKALANIR

"active" bir sağlık ölçüsü değildir; kilitlenmiş süreç de active'tir. Sağlığı çıktıdan ölçün: kaynak başına son olay zamanı, gerekirse işlemci ve kuyruk derinliği. Tek seferlik bildirim, süren durumu göstermenin yerine geçmez.

3. 200 dönen eski kod

Bir web sitesinin Next.js uygulamasını output: "standalone" ile derleyip rsync ile sunucuya taşıyordum. Bilinen kural şudur: standalone çıktısı public/ ve .next/static/ dizinlerini kopyalamaz, onları ayrıca taşırsın. Static'i ayrı senkronladığım için ana komuta --exclude='.next' ekledim. Mantıklı görünüyordu.

Değildi. Derlenmiş uygulamanın kendisi .next/standalone/.next/server/ altında duruyor; köke bağlanmamış bir dışlama kalıbı ağacın her yerindeki .next dizinine uyar. Sunucuya yalnız server.js ile node_modules gitti. Daha sinsi olanı: rsync dışlanan yolları --delete'ten de korur. Sunucudaki eski .next/server silinmedi, yerinde kaldı ve yeni server.js onu sorunsuzca çalıştırdı.

Servis active, ana sayfa 200, bütün sayfalar açılıyor, hiçbir logda hata yok. CDN önbelleğini de temizlemiştim; "önbellekten geliyordur" diye suçlayacak bir şey de yoktu. Çalışan sürüm yalnızca eski sürümdü.

EK-4 Dağıtımın içerik kanıtı
curl -s https://ornek-alan.com/ -o /tmp/p.html
grep -c "yeni-eklenen-id" /tmp/p.html    # 1 olmalı
grep -c "Kaldırılan Buton" /tmp/p.html   # 0 olmalı

HTTP kodu sürümü kanıtlamaz. Kanıt bir dizgi çiftidir: yeni sürümde olması gereken bir şey ile kalkmış olması gereken bir şey, birlikte sayılır.

Doğru dizilimde dışlamalar köke bağlı (/ ile başlıyor) ve yalnız ayrıca taşınan iki dizini --delete'ten koruyor:

rsync -a --delete --exclude='.env' --exclude='/public' --exclude='/.next/static' \
      .next/standalone/  sunucu:/opt/uygulama/
rsync -a --delete .next/static/ sunucu:/opt/uygulama/.next/static/
rsync -a --delete public/       sunucu:/opt/uygulama/public/
NASIL YAKALANIR

Dağıtımın başarısını süreçten ve durum kodundan değil, yanıtın içeriğinden ölçün. Tek dizgi yarım bir dağıtımı kaçırabilir; "yeni olan var" ile "eski olan yok"u birlikte sayınca hem yeninin geldiğini hem eskinin gittiğini görürsünüz.

4. Hızlandırınca kaybolan veri

Dış sağlayıcılardan IP bilgisi toplayan bir zenginleştirme hattı yavaştı. Eşzamanlılığı 5'ten 10'a çıkardım, birikim varken partiler arasındaki uykuyu 300 saniyeden 5 saniyeye indirdim. Testler sahte sağlayıcılarla koştu ve hepsi geçti; hat gerçekten hızlanmıştı.

Canlıda ilk partide, anahtarsız kullandığım ücretsiz bir IP bilgi servisi 429 döndü. Devre kesici açıldı ve yeni zenginleşen IP'lerde o servisten gelen alanın kapsamı %100'den %0'a indi; iki itibar servisi de 429 döndürüyordu. Eski kodun yavaşlığı, farkında olmadan, kotanın altında kalmasını sağlıyormuş.

Hat bu sırada hata vermedi; kayıtlar hattan geçti, yalnız alanları boştu. Bir kez "tamam" işaretlenen kayıt bir daha sorgulanmayabilir — bu, sessiz ve kalıcı veri kaybı demek. Yakalayan şey hız metriği değil, dağıtımdan hemen sonra baktığım alan bazında kapsam oranıydı. Beş dakika içinde geri aldım, etkilenen 50 kaydı yeniden kuyruğa koydum.

SELECT date_trunc('hour', zenginlesti_at)          AS saat,
       count(*)                                     AS kayit,
       round(100.0 * count(kurulus) / count(*), 1)  AS kurulus_kapsami
  FROM adresler
 WHERE zenginlesti_at > now() - interval '2 days'
 GROUP BY 1
 ORDER BY 1;
NASIL YAKALANIR

Hızı değiştiren her dağıtımdan sonra alan bazında kapsamı (dolu / toplam) önceki dönemle karşılaştırın. Sahte sağlayıcılı test hızı kanıtlar, kotayı kanıtlamaz. Eşzamanlılık sınırını sağlayıcının belgelenmiş kotasından türetin; çekirdek bir sağlayıcı atlandıysa kaydı "tamam" işaretlemeyin, yeniden kuyruğa bırakın. Geri alma yolunu dağıtımdan önce hazırlayın.

5–6. İki kardeş kayıt

Aynı desenin iki örneğini ayrı kayıtlarda ayrıntılı anlattım; burada yalnız bu derlemeye bağlandıkları yeri yazıyorum.

TB-2026-007 — SMTP'nin 250 yanıtı "iletiyi aldım" demektir, "teslim ettim" değil. Posta sunucusu iletiyi kabul edip hiç dışarı çıkarmadığında da, imzalayamadığı iletiyi DKIM'siz gönderdiğinde de 250 döner. Sinyal yine bir adımı, kabulü ölçüyor.

TB-2026-008 — Güvenlik başlıkları yapılandırmada tanımlıydı ve nginx -t sorunsuz geçiyordu. Ama bir location bloğundaki tek bir add_header, üst seviyedeki başlıkların o bloğa kalıtılmasını kesiyordu. Yapılandırma geçerliydi, yanıtta başlık yoktu.


Ortak teşhis

Altı vakayı tek tabloya koyunca örüntü netleşiyor. Her satırda soldaki sinyal doğru söylüyor; yalnız sorduğumuz soruya cevap vermiyor.

SinyalKanıtladığıKanıtlamadığıSonucu ölçen kontrol
"MySQL OK"Döküm komutu bittiDökülen veritabanı canlı olanBoyut değişimi, tablo sayısı, INSERT satırı
systemd "active"Süreç yaşıyorSüreç iş yapıyorKaynak başına son olay zamanı
Tek seferlik alarmSorun bir kez görüldüSorun bittiSüren sorun, süresiyle durum satırında
HTTP 200Bir sunucu yanıt verdiYanıt veren kod yeni sürümVar olmalı / yok olmalı dizgi çifti
"İşlendi"Kayıt hattan geçtiAlanlar dolduAlan bazında kapsam oranı
SMTP 250Sonraki halka kabul ettiİleti imzalı teslim edildiAlıcı tarafta başlık ve imza
nginx -tSöz dizimi geçerliBaşlık yanıttacurl -I ile yanıt başlıkları

Ortak kusur şu: başarıyı, sinyali üreten bileşene sorduk. Döküm komutu işini bitirdiğini bilir; dökmesi gereken veritabanının hangisi olduğunu bilmez. systemd sürecin yaşadığını bilir; olay döngüsünün kilitlendiğini bilmez. Sonuç, adımı atan bileşenin dışında ölçülür: yedeğin içinde, verinin tazeliğinde, yanıtın gövdesinde, kaydın alanlarında, alıcının kutusunda.

İkinci ortak nokta: dört vakanın her birinde görünür tek işaret bir değişmezlikti. Her gece aynı bayt, üç buçuk gün aynı "sağlıklı", dağıtımdan sonra değişmeyen sayfa, sıfıra çakılan kapsam. Yaşayan sistemler gürültülüdür; gürültünün kesilmesi de bir sinyaldir.

Yakalama listesi

  1. Her OK'nin yanına bir içerik ölçüsü koyun. Boyut, satır sayısı, tablo sayısı ya da bir dizgi. Çıkış kodu tek başına kayda geçmesin.
  2. Değişmezliği alarm sayın. Bayt bayt aynı yedek, saatlerdir olay göndermeyen kaynak, sıfıra inen alan kapsamı birer bulgudur.
  3. Beklenen listeden başlayın. Kaynak, tablo ve alan listesini sorgunun soluna koyun; olaylardan başlayan sorgu susanı göremez.
  4. "active" yerine tazelik ölçün. Süreç durumunu değil, sürecin ürettiği son çıktının yaşını izleyin.
  5. Süren sorunu görünür tutun. Tek bildirim kanalı korur; ama durum satırı sorun bitene kadar süresiyle birlikte söylemeli ve seyrek hatırlatmalı.
  6. Dağıtımı içerikle doğrulayın. Yeni sürümde olması ve kalkması gereken iki dizgiyi birlikte sayın; HTTP kodunu kanıt saymayın.
  7. Hız değişikliğinde kapsamı ölçün. Önce ve sonra alan bazında doluluk oranı; çekirdek sağlayıcısı atlanan kayıt "tamam" değildir.
  8. Yeniden adlandırmada bağımlıları aynı oturumda doğrulayın. Ad, kullanıcı ya da yol değiştiren iş, onu kullanan yedek ve zamanlanmış görevleri de kapsar.
  9. Karşı uçtan bakın. E-postayı alıcıda, başlığı yanıtta, yedeği içinde doğrulayın.

Kendi sisteminizde deneyin: izlediğiniz her yeşil ışığın yanına "bu neyi ölçüyor — adımı mı, sonucu mu?" diye yazın. "Adım" yazan her satır, bu derlemenin bir sonraki vakası olmaya aday.