Log rotasyonu, saklama süresi ve dolan diskin sessiz maliyeti
Dolmakta olan disk yavaş bir kesintidir: yazma reddedilir, build patlar, yedek sessizce kesilir. Neyin büyüdüğünü bulmanın ve tekrarını önlemenin yolu.
Disk bir anda dolmaz ve kendini nadiren disk sorunu olarak gösterir. Belirtiler ayrı ayrı gelir: veritabanı bir yazmayı reddeder, deploy build adımının ortasında patlar, gece çalışan yedek dört dakika yerine dört saniyede biter. Baktığım bir platformda root disk haftalarca yüzde 90'ın üstünde durdu, görünür bir şey bozulmadı; bozulduğunda ise üç kişi tek sebebi olan üç ayrı hatayı ayrı ayrı ayıklıyordu.
Dolu disk aslında neyi bozar
Boş alan, her servisin okuyup saygı gösterdiği tek bir sayı değildir. Her biri farklı anda farklı duvara çarpar, hataların birbiriyle ilgisiz görünmesinin sebebi de budur.
- Çoğu dosya sistemi bir dilimi root için ayırır, ext4'te varsayılan yüzde beştir. Önce sıradan servisler reddedilir, root hâlâ yazabilir. Yani uygulamanın bir saattir öldüğü makineye rahatça girip gezinebilirsiniz.
- Veritabanının ihtiyacı satırlardan ibaret değildir. Write ahead log, belleğe sığmayan sort ve join için geçici dosyalar, tabloyu yeniden yazarken aldığı tam kopya; hepsi aynı disktedir. Büyük bir tablonun yeniden inşası, tablonun kapladığı kadar daha boş alan isteyebilir.
- Build yeni çıktıyı eskisini silmeden önce yazar, yani deploy sırasındaki tepe kullanım kabaca artifact boyutunun iki katıdır. Bir saat önce sorunsuz görünen diskte deploy'un patlamasının sebebi budur.
- Aynı diske alınan dump, kimsenin fark edeceği bir hata yerine yarım kalmış bir dosya üretir. Yedek betiği çıktıyı sıkıştırıcıya boru ile veriyor ve pipeline'ın tümünün çıkış kodunu kontrol etmiyorsa, geri yüklenemeyecek bir dosya için başarı raporlar.
- Inode'lar baytlardan bağımsız biter. Milyonlarca küçük session veya cache dosyası, df hâlâ gigabaytlarca boş alan gösterirken inode tablosunu tüketir; ardından her dosya oluşturma, o boş alan rakamının yanında hiç anlam ifade etmeyen bir yer yok hatasıyla düşer.
Neyin büyüdüğünü görmek
İki sayıyla başlayın, çünkü ayrı ayrı biterler:
df -h /
df -i /Sonra ağaçta tek seferde bir seviye inin. İnsan okunur boyuta göre sıralayıp sonuna bakmak, tüm listeyi okumaktan hızlıdır:
du -x -h -d1 / 2>/dev/null | sort -h | tail -15
du -x -h -d1 /var 2>/dev/null | sort -h | tail -15-x du'yu tek dosya sisteminde tutar, böylece mount edilmiş bir yedek diski tabloyu bozmaz. Listenin tepesinde ne varsa onun üzerinde tekrarlayın; cevap bir dizin olmaktan çıkıp dosya olana kadar.
Sürprizi genelde barındıran tekil büyük dosyalar için:
find / -xdev -type f -size +200M -printf '%10s %p\n' 2>/dev/null | sort -rn | head -20Benim gördüğüm kadarıyla sonuç neredeyse her seferinde aynı dört kategoriye düşüyor: hiçbir şeyin rotate etmediği bir uygulama log'u, hiç temizlenmemiş eski build çıktıları ve sürümler, makine kurulduğundan beri her deploy'dan kalan container image ve layer'ları, bir de birinin elle açıp sonra unuttuğu bir yedek dizini.
docker system df
du -sh /var/log/journal /var/log/*.log 2>/dev/null | sort -h | tailVeritabanı tarafında dosya sistemine değil veritabanına sorun, çünkü data dizini size hangi tablonun büyüdüğünü söylemez:
SELECT table_name,
ROUND((data_length + index_length) / 1024 / 1024) AS mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY mb DESC
LIMIT 10;On seferin dokuzunda en üst satır, yazılmak için tasarlanmış ama silinmek için hiç tasarlanmamış bir olay veya audit tablosudur.
Çözüm üç parça
İki tür log'u da rotate edin
Sistem log'larının rotasyonu genelde hazır gelir. Process manager'ın veya uygulamanın kendi yazdığı log'ların rotasyonu genelde yoktur, gigabaytlar da oradadır. Bir logrotate dosyası birkaç satırdır:
/srv/app/logs/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
copytruncate
su appuser appuser
}copytruncate şunun için var: uygulama dosyayı açık tutar, siz dosyayı yeniden adlandırsanız bile eski inode'a yazmaya devam eder. Process manager kendi log dosyalarını tutuyorsa ona da maksimum boyut ve saklanacak dosya sayısı verin. Birçoğu tek dosyayı sonsuza kadar büyütmekten memnundur. Rotasyon işinin de her şey gibi yeniden başlatmadan sağ çıkması gerekir, bu da süreç yöneticisi ve açılışta kalıcılık dersiyle aynı konudur.
Her tabloya bir saklama süresi verin
Her tablo, var oluş sebebine uyan bir süre alır ve sebepler birbirinden farklıdır:
- Request ve access log'ları: birkaç ay. Geçen hafta ne oldu sorusunu cevaplarlar, geçen yılı kimse sormadı.
- Teslim ve olay kayıtları: cevapladıkları operasyonel sorunun uzunluğu kadar, genelde altı ay civarı.
- Finansal kayıtlar ve defter satırları: en az bir yıl, çoğu durumda hiç silinmez, sadece daha ucuz bir yere arşivlenir.
Sonra atlanan kısım geliyor. Silme işleminin tarih kolonunda indekse ihtiyacı var. İndeks yoksa gece çalışan temizlik, elinizdeki en yoğun tabloda kilit tutan bir full table scan olur ve disk sorununu gecikme sorunuyla takas etmiş olursunuz:
CREATE INDEX idx_events_created_at ON events (created_at);Milyonlarca satıra dokunan tek bir ifade yerine aralarında bekleme olan partiler halinde silin:
DELETE FROM events
WHERE created_at < NOW() - INTERVAL 180 DAY
ORDER BY created_at
LIMIT 5000;Bunu sıfır satır etkileyene kadar döngüde çalıştırın, turlar arasında kısa bir sleep bırakın ki replikasyon ve diğer yazıcılar nefes alsın. Süreyi SQL'in içinde değil config'de tutun, böylece değiştirmek bir düzenleme olur, deploy olmaz.
Bir uyarı, insanlara bir akşamlarına mal oluyor: InnoDB'de satır silmek alanı dosya sistemine değil tabloya geri verir. Dosya boyutunu korur, boşalan sayfaları yeni satırlar için kullanır. Baytları bugün geri istiyorsanız tabloyu yeniden inşa etmeniz gerekir; o da kabaca tablo boyutu kadar boş alan ister, yani tam olarak elinizde olmayan şeyi. Silmeye bu işe hiç ihtiyaç duymayacak kadar erken başlayın.
Alarmı yüzde 75'e kurun
Yüzde 95'teki alarm, geç kaldığınızın bildirimidir. Hemen her çözüm boş alan ister: tabloyu yeniden inşa etmek, eski container çalışırken yeni image çekmek, makineden çıkarmadan önce dump yazmak. Yüzde 75, sorunu olay değil iş olarak çözecek alanı bırakır.
#!/bin/sh
set -eu
use=$(df -P / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$use" -ge 75 ]; then
printf 'root disk yuzde %s\n' "$use"
exit 1
fiBunu zaten bir insana ulaşan neyiniz varsa ona bağlayın. Tek eşik yerine iki eşik daha iyi çalışır: 75'te kanala düşen bir uyarı, 90'da telefonu çaldıran bir alarm.
Çalıştığını nasıl doğrularsınız
Üç kontrol, sırayla. Dosya sistemi rakamı hareket etmiş:
df -h / | awk 'NR==2 {print $5, $4}'
# 60% 74GRotasyon gerçekten kurulmuş, bunu anlamak için bir gün beklemek gerekmiyor:
logrotate -d /etc/logrotate.d/app 2>&1 | grep -i "rotating\|log needs"Ve en eski satır sürenin içinde:
SELECT COUNT(*) AS overdue, MIN(created_at) AS oldest
FROM events
WHERE created_at < NOW() - INTERVAL 180 DAY;overdue ilk tam turun ertesi sabahı sıfır olmalı ve sıfır kalmalı. Gün içinde yükselip gece düşüyorsa iş çalışıyordur. Yükselip hiç düşmüyorsa iş sessizce hata veriyordur, zamanlanmış her işin normal bozulma şekli de budur.
Nelere dikkat etmeli
- Çalışan bir sürecin açık tuttuğu silinmiş dosya tek bayt boşaltmaz. df dolu görünür, du hiçbir şey bulamaz.
lsof +L1sabah sildiğiniz log'un kimsenin restart etmediği bir sürece hâlâ bağlı olduğunu gösterene kadar da sinir bozucudur. copytruncate, kopyalama ile truncate arasında yazılan satırları kaybedebilir. Her satırın önemli olduğu bir audit log'unda bunun yerine reopen sinyali kullanın ve fazladan konfigürasyonu kabul edin.- Saat dilimleri saklama süresini yalancı yapar. Satırlar bir dilimde tutulup temizlik başka bir dilime göre karşılaştırıyorsa, her gece birkaç saat fazla veya eksik silersiniz, hem de sessizce. Bu yanlış saat diliminde zamanlanan kampanya tuzağının aynısıdır.
docker system prune -aile temizlik, geri dönmek isteyeceğiniz bir önceki sürümü de siler. Bilinen son sağlam tag'i temizliğin dışında tutun ya da onu hızlıca yeniden üretecek bir yol bırakın.- Saklama süresi yedek değildir. Eski satırı düşürmek ile geri yüklenebilir bir kopya tutmak farklı işlerdir, bozulma şekilleri de farklıdır. Koruduğu diskin üzerinde duran yedek ise o diskle birlikte gider.
Büyüme, kurduğunuz sistemin bir özelliğidir; dolayısıyla yeri olay kanalı değil tasarımdır. Sonuna yazılan her dosyaya bir rotasyon kuralı, yalnızca insert alan her tabloya bir süre ve onu destekleyen bir indeks, bir de düzeltmenin hâlâ ucuz olduğu anda çalan bir alarm. Aynı içgüdü toplu gelen iş için de geçerli, forma yapıştırılan yüz bin satırın kuyruğa ait olmasının sebebi de budur. Bütün bunların size aldığı şey, işleri küçükken düzeltme özgürlüğü.
Sorular ve cevaplar
- df boş alan gösteriyorken sunucu neden yer yok diyor?
- Büyük ihtimalle bayt değil inode bitti. Aynı dosya sistemi için df -i çalıştırın. Milyonlarca küçük session, cache veya mail dosyası diski doldurmadan çok önce inode tablosunu tüketir, ondan sonra her dosya oluşturma denemesi yer yok mesajıyla başarısız olur.
- Büyük log dosyasını sildim ama yer açılmadı, neden?
- Bir süreç dosyayı hâlâ açık tutuyor, dolayısıyla dosya sistemi blokları o süreç descriptor'ı kapatana veya yeniden başlayana kadar bırakmaz. lsof +L1 ile silinmiş ama hâlâ açık dosyaları listeleyin. Süreci restart edin ya da silmek yerine copytruncate veya reopen sinyaliyle düzgün rotasyon kurun.
- Eski satırları silmek diskte yer açar mı?
- Çoğu motorda hemen açmaz. InnoDB boşalan sayfaları tabloya geri verir, dosya boyutu aynı kalır ve o alan yeni satırlar için kullanılır. Baytları dosya sistemine döndürmek tabloyu yeniden inşa etmeyi gerektirir, o da yaklaşık tablo boyutu kadar boş alan ister. Yani bu işe hiç ihtiyaç duymayacak kadar erken silmeye başlayın.
- Saklama süresi kaç gün olmalı?
- Tüm veritabanı için tek bir süre yanlış kurgudur. Süreyi tablo tablo, o tablonun cevapladığı soruya göre seçin: request ve access log'ları için birkaç ay, teslim ve olay kayıtları için operasyonel sorunun uzunluğu, finansal kayıtlar için bir yıl ve üstü. Süreyi config'de tutun ki değiştirmek deploy gerektirmesin.