2 puan yazan GN⁺ 2025-03-01 | 1 yorum | WhatsApp'ta paylaş
  • Geçmişte sistemimde CPU kullanımı %3.200'e ulaştı; 32 çekirdeğin tamamı tamamen doluydu
  • Java 17 çalışma zamanını kullanıyordum ve thread dump içinde CPU süresine bakıp CPU süresine göre sıralayınca benzer çok sayıda thread buldum
  • Sorunlu kodun analizi
    • Stack trace üzerinden BusinessLogic sınıfının 29. satırını kontrol ettim
    • İlgili kod, unrelatedObjects listesini dolaşırken relatedObject değerini treeMap içine ekliyordu
    • Bu, döngü içinde unrelatedObject kullanılmadığı için verimsiz bir koddu

Kod düzeltmesi ve test

  • Gereksiz döngü kaldırıldı ve kod treeMap.put(relatedObject.a(), relatedObject.b()); satırına indirildi
  • Değişiklikten önce ve sonra unit test çalıştırdım, ancak sorunu yeniden üretemedim
  • treeMap ve unrelatedObjects boyutları ayrı ayrı 1.000.000'un üzerinde olsa bile sorun ortaya çıkmadı

Sorunun nedeninin bulunması

  • treeMape birden fazla thread aynı anda erişiyordu ve senkronizasyon yoktu
  • Sorun, birden fazla thread'in TreeMap üzerinde aynı anda değişiklik yapmasından kaynaklanıyordu

Deneyle sorunu yeniden üretme

  • Birden fazla thread'in paylaşılan TreeMapi rastgele güncellediği bir deney yaptım
  • try-catch bloğu kullanarak NullPointerException hatasını yok sayacak şekilde ayarladım
  • Deney sonucunda CPU kullanımının %500'e kadar çıktığını doğruladım

Sonuç

  • Senkronize edilmemiş TreeMap üzerinde eşzamanlı değişiklikler ciddi performans sorunlarına yol açabilir
  • Bu tür sorunları önlemek için TreeMapi senkronize etmek veya ConcurrentMap gibi thread-safe koleksiyonlar kullanmak önerilir

1 yorum

 
GN⁺ 2025-03-01
Hacker News görüşleri
  • Race condition'ların veri bozulmasına ya da deadlock'a yol açtığını düşünürdüm, ama performans sorunlarına da neden olabileceğini hiç düşünmemiştim. Veriler, sonsuz döngü oluşturacak şekilde bozulabilir

    • Bir projedeki hata, anormal davranış ve uyarıların prensip olarak düzeltilmesi gerektiğini düşünüyorum. Çünkü bunlar ilgisiz görünen başka sorunlara yol açabilir
    • Java'nın çekirdek koleksiyonlarının tasarım gereği thread-safe olmadığı iyi biliniyor. OP, kodun diğer bölümlerinde de birden fazla thread'in koleksiyonları değiştirip değiştirmediğini kontrol etmeli
    • TreeMap'i Collections.synchronizedMap ile sarmak ya da ConcurrentHashMap'e geçip gerektiğinde sıralamak en kolay çözüm
    • Tek tek map işlemlerini thread-safe hale getirebilirsiniz, ama bir dizi ardışık işlemin thread-safe olup olmadığından emin olamazsınız. TreeMap'e sahip olan nesnenin thread-safe olduğundan da emin olamazsınız
    • Tartışmalı bir çözüm olarak ziyaret edilen düğümleri takip etmek iyi bir yöntem değil. Koleksiyon hâlâ thread-safe değil ve daha ince başka şekillerde de başarısız olabilir
    • Detaylara dikkat eden bir geliştirici, thread ile TreeMap kombinasyonunu fark edebilir ya da sıralı öğelere ihtiyaç yoksa TreeMap kullanılmamasını önerebilirdi. Ama bu olayda öyle olmamış
    • Sorun, koleksiyonun sözleşmesini ihlal etmek ve TreeMap'i HashMap ile değiştirmek de hâlâ yanlış olur
  • Birden fazla thread'in çalıştığı kodda, tüm nesneleri immutable yapmak ve immutable yapılamayan nesneleri küçük, kendi kendine yeten ve sıkı biçimde kontrol edilen bölümlerle sınırlamak tek güvenilir strateji

    • Bu ilkelere uyarak çekirdek modülü yeniden yazdık ve bu bölüm, sürekli sorun çıkaran bir kaynaktan kod tabanının en dayanıklı bölümlerinden birine dönüştü
    • Bu yönergeler yerleşince code review çok daha kolay hale geldi
  • "ssh'e neredeyse bağlanamıyordum" ifadesi, yüksek lisans dönemimde Sun UltraSparc 170 kullandığım zamanları hatırlattı

    • Yeni bir kullanıcı ya da öğrenci paralel iş yapmaya çalışıyordu; büyük bir metin dosyasını satır numaralarına göre birkaç bölüme ayırıp her bölümü paralel işliyordu
    • Çok fazla RAM kullanıldı ve swap girişimleri, aynı dosyanın farklı bölümlerini okumak için şiddetli disk aramalarına neden oldu
    • Konsoldan login prompt bile alamıyorduk, ama zaten açık bir oturum vardı ve root session alarak sorunu çözebildik
    • Sorun, sistemin sınırlarının anlaşılmamış olmasıydı
  • Kod aslında basitçe şu hâle indirgenebilir

    • Orijinal kod, yalnızca <i>unrelatedObjects</i> boş değilse <i>treeMap.put</i> yapıyor. Bu bir bug olabilir
    • <i>a</i> ve <i>b</i>'nin her seferinde aynı değeri döndürdüğünü ve <i>treeMap</i>'in gerçekten bir map gibi davrandığını doğrulamak gerekir
  • Sonsuz döngü elde etmenin bir başka yolu da tutarlı bir total order uygulamayan bir <i>Comparator</i> veya <i>Comparable</i> implementasyonu kullanmak

    • Bunun concurrency ile ilgisi yok; belirli veriler ve işleme sırasına bağlı olarak ortaya çıkabilir
  • Artan bir sayaç kullanarak cycle tespiti yapmayı ve ağaç derinliğini ya da koleksiyon boyutunu aşarsa exception fırlatmayı düşünebilirsiniz

    • Bu neredeyse hiç bellek ya da CPU overhead'i gerektirmez ve kabul görme ihtimali daha yüksektir
  • Java'da thread-safe olmayan nesneler üzerinde eşzamanlı işlemler yapmak, en ilginç bug'ları üretir

  • Korunmasız bir TreeMap'in %3.200 kullanım yaratıp yaratamayacağı soruluyor

    • 2009 civarında benzer bir sorun görmüştüm ve bu hâlâ yaşanabiliyor
    • Data race'leri sadece biraz kötü sananlar için hayal kırıklığı yaratıcı
  • Yazar, Poison Pill'in bir türünü keşfetmiş. Bu, event sourcing sistemlerinde daha yaygındır; karşılaştığı her şeyi öldüren bir mesajdır

    • Veri yapısı illegal bir duruma ulaştığında, sonraki tüm thread'ler aynı mantıksal bombaya takılıp kalır
  • Thread'lerdeki exception'lar mutlak bir problemdir

    • C++, select() ve thread'lerin exception savurduğu dehşet verici bug avı hikâyeleri var