2 poin oleh GN⁺ 2025-03-01 | 1 komentar | Bagikan ke WhatsApp
  • Di masa lalu, penggunaan CPU sistem saya pernah mencapai 3.200%, artinya semua 32 core terpakai penuh
  • Saat itu saya menggunakan runtime Java 17, dan setelah memeriksa waktu CPU dari thread dump lalu mengurutkannya berdasarkan waktu CPU, ditemukan banyak thread serupa
  • Analisis kode yang bermasalah
    • Melalui stack trace, ditemukan baris ke-29 pada kelas BusinessLogic
    • Kode tersebut berbentuk iterasi atas daftar unrelatedObjects sambil memasukkan nilai relatedObject ke treeMap
    • Ini adalah kode yang tidak efisien karena unrelatedObject tidak digunakan di dalam loop

Perbaikan Kode dan Pengujian

  • Loop yang tidak perlu dihapus dan diubah menjadi satu baris: treeMap.put(relatedObject.a(), relatedObject.b());
  • Unit test dijalankan sebelum dan sesudah perbaikan, tetapi masalahnya tidak dapat direproduksi
  • Bahkan saat ukuran treeMap dan unrelatedObjects masing-masing melebihi 1.000.000 item, masalah tetap tidak muncul

Penyebab Masalah Ditemukan

  • treeMap ternyata diakses secara bersamaan oleh banyak thread, dan tidak disinkronkan
  • Masalah ini terjadi karena beberapa thread memodifikasi TreeMap secara bersamaan

Mereproduksi Masalah lewat Eksperimen

  • Dilakukan eksperimen di mana beberapa thread memperbarui TreeMap bersama secara acak
  • Blok try-catch digunakan agar NullPointerException diabaikan
  • Hasil eksperimen menunjukkan penggunaan CPU bisa naik hingga 500%

Kesimpulan

  • Modifikasi bersamaan pada TreeMap yang tidak disinkronkan dapat menyebabkan masalah performa yang serius
  • Untuk mencegah masalah seperti ini, disarankan menyinkronkan TreeMap atau menggunakan koleksi yang thread-safe seperti ConcurrentMap

1 komentar

 
GN⁺ 2025-03-01
Komentar Hacker News
  • Saya kira race condition menyebabkan korupsi data atau deadlock, tetapi saya tidak terpikir bahwa hal itu juga bisa memicu masalah performa. Data bisa rusak dengan cara yang menciptakan loop tak berujung

    • Saya berpendapat bahwa error, perilaku aneh, atau peringatan dalam sebuah proyek pada prinsipnya harus diperbaiki. Sebab hal itu bisa memicu masalah lain yang tampaknya tidak terkait
    • Sudah diketahui luas bahwa koleksi inti Java memang tidak thread-safe secara desain. OP perlu memeriksa apakah ada beberapa thread yang memanipulasi koleksi di bagian lain kode juga
    • Solusi termudah adalah membungkus TreeMap dengan Collections.synchronizedMap atau beralih ke ConcurrentHashMap lalu mengurutkannya saat diperlukan
    • Operasi map individual bisa dibuat thread-safe, tetapi belum tentu rangkaian operasi yang berurutan juga thread-safe. Tidak bisa dipastikan bahwa objek yang memiliki TreeMap itu sendiri thread-safe
    • Sebagai solusi yang masih bisa diperdebatkan, melacak node yang sudah dikunjungi bukanlah pendekatan yang baik. Koleksinya tetap tidak thread-safe dan masih bisa gagal dengan cara-cara halus lainnya
    • Pengembang yang teliti mungkin akan menyadari kombinasi thread dan TreeMap, atau menyarankan untuk tidak memakai TreeMap bila elemen yang terurut tidak diperlukan. Namun dalam kasus ini tidak demikian
    • Masalahnya adalah pelanggaran terhadap kontrak koleksi, dan mengganti TreeMap dengan HashMap pun tetap salah
  • Dalam kode yang dijalankan oleh beberapa thread, satu-satunya strategi yang benar-benar pasti adalah membuat semua objek menjadi immutable, dan untuk objek yang tidak bisa dibuat immutable, membatasinya ke bagian yang kecil, mandiri, dan dikendalikan dengan ketat

    • Setelah mengikuti prinsip ini dan menulis ulang modul inti, bagian tersebut berubah dari sumber masalah yang terus-menerus menjadi salah satu bagian codebase yang paling tangguh
    • Dengan pedoman seperti ini, code review menjadi jauh lebih mudah
  • Penyebutan bahwa "hampir tidak bisa masuk lewat ssh" mengingatkan pada masa kuliah pascasarjana saat menggunakan Sun UltraSparc 170

    • Seorang pengguna baru atau mahasiswa mencoba menjalankan pekerjaan secara paralel, membagi file teks besar menjadi beberapa bagian berdasarkan nomor baris, lalu memproses tiap bagian secara paralel
    • Banyak RAM terpakai, dan upaya swap melakukan pencarian disk secara liar untuk membaca bagian-bagian berbeda dari file yang sama
    • Tidak bisa mendapatkan prompt login dari konsol, tetapi sudah ada sesi yang login, dan sesi root bisa didapat untuk memperbaiki masalahnya
    • Masalahnya adalah tidak memahami batas sistem
  • Kodenya bisa disederhanakan menjadi seperti berikut

    • Kode aslinya hanya melakukan <i>treeMap.put</i> saat <i>unrelatedObjects</i> tidak kosong. Ini bisa jadi sebuah bug
    • Perlu dipastikan apakah <i>a</i> dan <i>b</i> selalu mengembalikan nilai yang sama setiap kali, dan apakah <i>treeMap</i> benar-benar berperilaku seperti map
  • Cara lain untuk mendapatkan loop tak berujung adalah menggunakan implementasi <i>Comparator</i> atau <i>Comparable</i> yang tidak menerapkan total ordering yang konsisten

    • Ini tidak ada kaitannya dengan konkurensi, dan bisa terjadi tergantung data tertentu dan urutan pemrosesan
  • Bisa dipertimbangkan metode untuk mendeteksi siklus dengan memakai counter yang terus bertambah, lalu melempar exception jika melebihi kedalaman tree atau ukuran koleksi

    • Ini hampir tidak menambah overhead memori atau CPU, dan kemungkinan lebih mudah diterima
  • Menjalankan operasi konkurensi pada objek yang tidak thread-safe di Java menghasilkan bug yang paling menarik

  • Ada pertanyaan apakah TreeMap yang tidak dilindungi bisa menyebabkan utilisasi 3.200%

    • Saya pernah melihat masalah serupa sekitar tahun 2009, dan hal ini tampaknya masih bisa terjadi
    • Cukup mengecewakan bagi orang yang mengira data race itu hanya sedikit buruk
  • Penulis menemukan salah satu jenis Poison Pill. Ini lebih umum di sistem event sourcing, berupa pesan yang membunuh apa pun yang ditemuinya

    • Saat struktur data mencapai keadaan ilegal, semua thread berikutnya akan terjebak dalam bom logika yang sama
  • Exception di thread adalah masalah yang benar-benar serius

    • Ada kisah perburuan bug yang mengerikan tentang C++, select(), dan thread yang melempar exception