deniz.in

Piyasalar

Hava durumu

Hava durumu yükleniyor

· kaynak dev.to (home feed)

Post-mortem: Python'un çöp toplayıcısına emanet edilen temp dosyaları CI disklerini doldurdu

Flude ekibinin dev.to'da yayımladığı bir post-mortem, TemporaryDirectory nesnelerinin çöp toplayıcıya bırakılan temizliğinin CI disklerini Doxygen XML'leriyle nasıl doldurduğunu ve build'leri nasıl kilitlediğini, bunun da neden context manager ile çözüldüğünü anlatıyor.

Post-mortem: Python'un çöp toplayıcısına emanet edilen temp dosyaları CI disklerini doldurdu

Geceleyin çalışan bir build disk alanını bitiriyor

Hikâye birçok altyapı olayında olduğu gibi başlıyor. C++ analiz motoru Flude'ün arkasındaki ekibin dev.to'da yayımladığı bir post-mortem'e göre, planlanmış bir gece build'i CI runner üzerinde keskin errno mesajıyla, "No space left on device" ile başarısız oldu. Makine incelendiğinde suçlu ortaya çıktı: /tmp içinde binlerce artık dizin, her biri motorun kendi geçici çalışma alanı için kullandığı ude_xml_ önekini taşıyordu.

Sızıntı nereden geliyordu

Flude'ün motoru Doxygen'i alışılmadık bir şekilde çalıştırıyor: araca kaynak kodu veriyor, karşılığında büyük bir XML dump'ı alıyor ve bu dump'ı parse ediyor. Her çalışma tempfile.mkdtemp() ile bir geçici dizin oluşturuyor ve içine üretilmiş bir Doxyfile yerleştiriyordu; Doxygen de oraya yüzlerce megabaytlık XML yazıyordu. Plan, klasörü pipeline'ın en sonunda shutil.rmtree() ile kaldırmaktı. Kusur yapısaldaydı: parsing bir exception fırlatırsa, pipeline temizlik çağrısı çalışmadan duruyordu ve XML dolu bir dizin /tmp içinde süresiz olarak mahsur kalıyordu.

Ekip işleri try/finally içine sarmayı denedi, ancak pipeline'ın mantığı düzinelerce sınıfa yayılmıştı ve sızıntı hayatta kaldı. Çok büyük projeler derleyen ekiplerden raporlar geldi ve başarısızlık bir exception oluşup oluşmadığına bağlı olduğundan, sistematik değil flaky gibi görünüyordu.

Temizliği çöp toplayıcıya devretmek

Bir sonraki deneme görünüşte pythonic olan yolu izledi: mkdtemp yerine, finalizer'ı nesne çöp toplayıcı tarafından toplanınca klasörü silen tempfile.TemporaryDirectory kullanıldı. Her dizini motorun ihtiyaç duyduğu süre boyunca canlı tutmak için ekip, bu nesnelere ait referansları bir manager sınıfının tuttuğu bir listede sakladı.

Şikâyetler birkaç gün durdu, issue kapatıldı ve ardından CI runner'lar yeniden çöktü. İki ayrıntı gözden kaçmıştı.

Birincisi, büyük C++ projeleri için motor düzinelerce modulu art arda parse ediyordu ve her birine kendi dizini geliyordu. Parser'lar döngüsel referanslarla doluydu — bir AST düğümü parent sınıfına işaret ederken sınıf da çocuklarının listesini tutuyordu — bu yüzden nesneler derhal serbest bırakılmıyor, tam bir çöp toplama döngüsünü bekliyordu. Manager'ın referans listesi her TemporaryDirectory'yi erişilebilir tutuyordu ve yüzlercesi bellekte, işgal ettikleri disk alanıyla birlikte birikti.

İkincisi, toplayıcı sonunda finalizer'ları çalıştırdığında, tek devasa bir senkron silme işlemi yapıyor ve ana yürütme thread'ini dakikalarca bloke ediyordu. Aynı sunucudaki diğer servisler daha sonra timeout'larla başarısız olmaya başladı. İddia edilen düzeltme, yavaş bir disk sızıntısını periyodik çok dakikalık kilitlenmelere dönüştürmüştü.

Context manager ile deterministik temizlik

Ekip sonuçsert bir sonuca vardı: ağır sistem kaynaklarını serbest bırakmak için finalizer'lara güvenmek bir anti-pattern'dir, çünkü çöp toplayıcının ne zaman çalışacağını denetleyemezsiniz ve dolayısıyla ürettiği disk I/O yükünü de denetleyemezsiniz.

Pipeline'ı, her geçici klasörün yaşam döngüsünün with blokları tarafından yönetileceği şekilde yeniden kurdular. Oluşturma ve temizlik artık aynı yerde gerçekleşiyor ve dosyalara ihtiyaç duyan her bileşen onları bloğun içinde okuyor; bu da klasörün çıkışta — exception yoluyla yapılan çıkışlar dahil — silinmesini garanti ediyor. Regresyonlara karşı bir önlem olarak, bir janitor adımı her build'den önce /tmp'yi inceliyor, bulduğu yetim dizinleri zorla siliyor ve orijinal çağırıcının temizliği hiç çalıştırmadığına dair bir uyarı log'luyor.

Neden önemli

Bu, CI disklerinin dolmasının en yaygın yollarından biri: bir geçici dizin bir exception'dan sağ çıkıyor, bunu hiçbir şey raporlamıyor ve hasar bir build'e bir tane olmak üzere birikiyor, ta ki bir gece job'ı ölene kadar. TemporaryDirectory tam da temizliği bir finalizer içine gizlediği için kullanışlıdır, ama çöp toplama zamanlaması bir implementasyon detayıdır ve del, gerçek I/O maliyeti olan hiçbir şey için doğru yer değildir.

Çıkarım Python'ın ötesine genellenir: kaynak yaşam sürelerini context manager ile açık hale getirin, oluşturma ve serbest bırakmayı aynı scope'ta tutun ve artıklar için bir pre-flight süpürmesi ekleyin. Ve unutmayın, ertelenmiş temizlik hiç temizlik olmamasından daha kötü olabilir — dakikalarca süren senkron dosya silmeye dönüşen bir finalizer, en az uygun anda, ana thread üzerinde patlar.

  • #python
  • #ci-cd
  • #devops
  • #debugging
  • #post-mortem

İlgili yazılar