Copilot'un bellek sızıntısı sorunu
(stevenharman.net)ActiveSupport::Notifications ile ilgili bellek sızıntısını çözme sürecinin özeti
-
Bellek sızıntısının ortaya çıktığı durum
- Belirli bir andan itibaren
webDyno'nun bellek kullanımı anormal biçimde artmaya başladı - Pager çalmaya başladı ve bellek sızıntısı gibi görünen bir durum oluştu
- Belirli bir andan itibaren
-
Anlık müdahale
- Heroku'da bellek sızıntısından şüpheleniliyorsa Dyno'yu yeniden başlatmak geçici bir çözüm olabilir
- Normal deploy döngüsüne göre yeniden başlatmak veya bellek sınırına yaklaşan Dyno'ları elle yeniden başlatmak
-
Nedeni belirlemek için şüpheli kodların incelenmesi
- Bellek sıçramasından hemen önce deploy edilen kod değişiklikleri incelendi
- Neden olabileceğinden şüphelenilen bazı kodlar tek tek deploy edilerek bellek sızıntısı oluşturup oluşturmadıkları kontrol edildi
- Neden gibi görünen bir kod bulunamadığı için tooling değişiklikleri de geri alınıp kontrol edildi. Ancak bellek sızıntısı devam etti
-
Bellek artış deseninin analizi
- Sızıntı yalnızca
webDyno'da oluşuyordu. Sidekiq ve Delayed::Job Dyno'ları normaldi - Tüm
webDyno'larda her zaman sızıntı olmuyordu. Birkaç saat normal kullanımın ardından bir veya iki Dyno'da ya da tüm Dyno'larda sızıntı başlıyordu - Trafik miktarından çok belirli bir trafik türü tarafından tetiklendiğinden şüphelenildi
- Dyno içindeki tüm Puma worker'larında sızıntı oluşmuyordu; az sayıdaki worker toplam belleğin büyük bölümünü kullanıyordu
- Sızıntı yalnızca
-
Heap dump toplama ve analiz
rbtracekullanılarak sızıntı yaşanan Ruby process'inin heap dump'ı toplandıheroku ps:execile sızıntı yaşanan dyno'ya ssh bağlantısı kuruldupskomutuyla en çok bellek kullanan Ruby worker process'i seçildirbtraceile ilgili pid'e attach olunup bellek tahsisi izleme başlatıldı (ObjectSpace.trace_object_allocations_start)ObjectSpace.dump_allile heap dump toplandı. Boyut büyükse gzip ile sıkıştırıldıheroku ps:copyile dump dosyası lokale alındı
reapkullanılarak heap dump flamegraph olarak görselleştirildi- 1.9GB belleğe referans veren bir Thread ve onun altında 32.067 nesneye referans veren bir Array bulundu
sheapkullanılarak şüpheli nesneler incelendi- İlgili Thread'in Puma'nın worker thread'i olduğu ortaya çıktı
ActiveSupport::SubscriberQueueRegistrynesnesinin birHash'e referans verdiği, onun altında daStringveArraynesnelerinin bulunduğu görüldü- Sorunlu
Arrayiçinde 32.000'den fazlaActiveSupport::Notifications::Eventnesnesi birikmişti
-
Nedene dair çıkarım
ActiveSupport::NotificationsiçindekiEventnesnelerinin#childrenarray'ine yanlış şekilde biriktiği tahmin edildiActiveSupport::Notifications.instrumentblock'u içinde hata oluşursa ilgiliEventnesnesinin#childreniçinden çıkarılmadan kaldığı ve bunun bellek sızıntısına yol açtığı düşünüldü
-
Lokalde yeniden üretim
- Production'da bulunan şüpheli request path ve parametrelerle lokal ortamda istek gönderildi
500 Internal Server Errorile birlikteURI::InvalidURIErroroluştuğu doğrulandı- Bu isteği alan production dyno'nun bellek kullanımının hızla arttığı görüldü
-
Somut neden analizi
- Rails 7.1'de düzeltilmiş olan
ActiveSupport::NotificationsiçindekiEvent#childrenile ilgili bir bug vardı - Buna ek olarak Bugsnag gem'inde request URL'ini temizleme sırasında
URI.parseiçindeURI::InvalidURIErrorfırlatan bir bug da üst üste gelince bellek sızıntısı oluştu ActiveSupport::Notifications.subscribeblock'u içinde fırlatılan hata yakalanmadığı için ilgiliEventnesnesi#childrenarray'inden çıkarılmadan birikmeye devam etti ve bellek sızıntısı oluştu
- Rails 7.1'de düzeltilmiş olan
-
Çözüm yaklaşımı
- Kısa vadede:
URI::InvalidURIErroroluştuğunda da hata fırlatmayan bir sürüme geçmek için Bugsnag gem'i upgrade edildi - Uzun vadede:
ActiveSupport::Notificationsbug'ının düzeltildiği Rails 7.x sürümüne upgrade edilecek
- Kısa vadede:
GN⁺ görüşü
- Sorunun fark edilmesi ve nedenin sistematik biçimde adım adım bulunma süreci etkileyici. Bellek sızıntısından şüphelenildiğinde uygulanabilecek temel analiz adımları iyi özetlenmiş
- Ruby'de heap dump toplama, görselleştirme ve analiz için çeşitli açık kaynak araçların (
rbtrace,reap,sheapvb.) aktif biçimde geliştirildiği görülüyor. Yalnızca Ruby için değil, farklı dillerde de yararlı bellek analiz araçlarını bilmek ve probleme uygulayabilmek önemli görünüyor - Aslında bellek sızıntılarının nedeni çoğu zaman kullanılan belirli bir kütüphane veya framework bug'ı oluyor; ancak bu bug'ı doğrudan analiz edip düzeltip deploy etmek her zaman mümkün olmadığından, dolaşılabilir bir çözümü mümkün olduğunca hızlı uygulamak önemli. Bug raporuyla birlikte uygulanabilir alternatifler sunmak da iyi bir yaklaşım
- Yalnızca bellek sızıntısını gidermekle kalmayıp problemin root cause'unu derinlemesine araştırmış olmaları da değerli. Framework'ün iç kodunu dikkatle inceleyip temel nedene kadar iz sürmeye çalışan analiz yaklaşımı geliştiriciler için gerekli görünüyor
- Sonuçta bellek sızıntısının nedeni, başta tamamen alakasız görünen küçük bir kütüphane sürüm güncellemesiydi. Bu da bağımlılık yönetimi ve değişiklik takibinin önemini gösteren bir örnek. Küçük değişikliklerde bile etki analizi dikkatle yapılmalı ve deploy sonrasında da izleme sürdürülmeli
1 yorum
Hacker News yorumu
Manuel bellek yönetimi korkusu olmadan mühendislik disipliniyle çözülebilir
Bellek sızıntısı nedeniyle 5 milyon dolarlık kayıp vakası
Bellek sızıntısı hata ayıklama araçları ve çözüm yolları
Yazı stiline övgü