Pos tamu dari arsitek kinerja Intel Java Eric Kaczmarek (aslinya diterbitkan di sini) mengeksplorasi cara menyetel pengumpulan sampah Java (GC) untuk Apache HBase yang berfokus pada 100% pembacaan YCSB.
Apache HBase adalah proyek open source Apache yang menawarkan penyimpanan data NoSQL. Sering digunakan bersama dengan HDFS, HBase banyak digunakan di seluruh dunia. Pengguna terkenal termasuk Facebook, Twitter, Yahoo, dan banyak lagi. Dari perspektif pengembang, HBase adalah "database terdistribusi, berversi, non-relasional yang dimodelkan setelah Google Bigtable, sistem penyimpanan terdistribusi untuk data terstruktur". HBase dapat dengan mudah menangani throughput yang sangat tinggi baik dengan scaling up (yaitu, penyebaran di server yang lebih besar) atau scaling out (yaitu, penyebaran di lebih banyak server).
Dari sudut pandang pengguna, latensi untuk setiap kueri sangat penting. Saat kami bekerja dengan pengguna untuk menguji, menyesuaikan, dan mengoptimalkan beban kerja HBase, kami menemukan sejumlah besar sekarang yang benar-benar menginginkan latensi operasi persentil ke-99. Itu berarti bolak-balik, dari permintaan klien ke respons kembali ke klien, semuanya dalam 100 milidetik.
Beberapa faktor berkontribusi terhadap variasi dalam latensi. Salah satu penyusup latensi yang paling merusak dan tidak dapat diprediksi adalah jeda "stop the world" Java Virtual Machine (JVM) untuk pengumpulan sampah (pembersihan memori).
Untuk mengatasinya, kami mencoba beberapa eksperimen menggunakan kolektor Oracle jdk7u21 dan jdk7u60 G1 (Garbage 1st). Sistem server yang kami gunakan didasarkan pada prosesor Intel Xeon Ivy-bridge EP dengan Hyper-threading (40 prosesor logis). Itu memiliki 256GB DDR3-1600 RAM, dan tiga SSD 400GB sebagai penyimpanan lokal. Pengaturan kecil ini berisi satu master dan satu slave, dikonfigurasi pada satu node dengan beban yang diskalakan dengan tepat. Kami menggunakan HBase versi 0.98.1 dan sistem file lokal untuk penyimpanan HFile. Tabel uji HBase dikonfigurasikan sebagai 400 juta baris, dan berukuran 580GB. Kami menggunakan strategi heap HBase default:40% untuk blockcache, 40% untuk memstore. YCSB digunakan untuk menggerakkan 600 utas kerja yang mengirim permintaan ke server HBase.
Bagan berikut menunjukkan jdk7u21 menjalankan 100% pembacaan selama satu jam menggunakan -XX:+UseG1GC -Xms100g -Xmx100g -XX:MaxGCPauseMillis=100
. Kami menentukan pengumpul sampah yang akan digunakan, ukuran tumpukan, dan waktu jeda "menghentikan dunia" pengumpulan sampah (GC) yang diinginkan.
Gambar 1:Ayunan liar di GC Waktu jeda
Dalam hal ini, kami mendapat jeda GC yang berayun liar. Jeda GC memiliki rentang dari 7 milidetik hingga 5 detik penuh setelah lonjakan awal yang mencapai setinggi 17,5 detik.
Bagan berikut menunjukkan detail lebih lanjut, selama kondisi mapan:
Gambar 2:Detail jeda GC, selama kondisi tunak
Gambar 2 memberi tahu kita bahwa jeda GC sebenarnya datang dalam tiga kelompok berbeda:(1) antara 1 hingga 1,5 detik; (2) antara 0,007 detik sampai 0,5 detik; (3) lonjakan antara 1,5 detik hingga 5 detik. Ini sangat aneh, jadi kami menguji jdk7u60 yang paling baru dirilis untuk melihat apakah datanya akan berbeda:
Kami menjalankan tes baca 100% yang sama menggunakan parameter JVM yang sama persis:-XX:+UseG1GC -Xms100g -Xmx100g -XX:MaxGCPauseMillis=100
.
Gambar 3:Penanganan lonjakan waktu jeda yang sangat ditingkatkan
Jdk7u60 sangat meningkatkan kemampuan G1 untuk menangani lonjakan waktu jeda setelah lonjakan awal selama tahap penyelesaian. Jdk7u60 membuat 1029 Young dan GC campuran selama satu jam berjalan. GC terjadi setiap 3,5 detik. Jdk7u21 membuat 286 GC dengan setiap GC terjadi setiap 12,6 detik. Jdk7u60 mampu mengatur waktu jeda antara 0,302 hingga 1 detik tanpa lonjakan besar.
Gambar 4, di bawah, memberi kita gambaran lebih dekat tentang jeda 150 GC selama kondisi tunak:
Gambar 4:Lebih baik, tetapi tidak cukup baik
Selama kondisi mapan, jdk7u60 mampu mempertahankan waktu jeda rata-rata sekitar 369 milidetik. Itu jauh lebih baik daripada jdk7u21, tetapi masih tidak memenuhi persyaratan kami 100 milidetik yang diberikan oleh –Xx:MaxGCPauseMillis=100
.
Untuk menentukan apa lagi yang dapat kami lakukan untuk mendapatkan waktu jeda 100 juta detik, kami perlu memahami lebih lanjut tentang perilaku manajemen memori JVM dan pengumpul sampah G1 (Garbage First). Gambar berikut menunjukkan cara kerja G1 pada koleksi Generasi Muda.
Gambar 5:Slide dari presentasi JavaOne 2012 oleh Charlie Hunt dan Monica Beckwith:“Penyetelan Kinerja Kolektor Sampah G1”
Ketika JVM dimulai, berdasarkan parameter peluncuran JVM, ia meminta sistem operasi untuk mengalokasikan potongan memori kontinu yang besar untuk menampung tumpukan JVM. Potongan memori itu dipartisi oleh JVM ke dalam region.
Gambar 6:Slide dari presentasi JavaOne 2012 oleh Charlie Hunt dan Monica Beckwith:“Penyetelan Kinerja Kolektor Sampah G1”
Seperti yang ditunjukkan Gambar 6, setiap objek yang dialokasikan oleh program Java menggunakan Java API pertama kali datang ke ruang Eden pada Generasi Muda di sebelah kiri. Setelah beberapa saat, Eden menjadi penuh, dan GC generasi muda dipicu. Objek yang masih direferensikan (yaitu, "hidup") disalin ke ruang Survivor. Ketika objek bertahan beberapa GC di generasi Muda, mereka dipromosikan ke ruang generasi Lama.
Ketika Young GC terjadi, utas aplikasi Java dihentikan untuk menandai dan menyalin objek langsung dengan aman. Perhentian ini adalah jeda GC "stop-the-world" yang terkenal, yang membuat aplikasi tidak merespons hingga jeda berakhir.
Gambar 7:Slide dari presentasi JavaOne 2012 oleh Charlie Hunt dan Monica Beckwith:“Penyetelan Kinerja Kolektor Sampah G1”
Generasi Lama juga bisa ramai. Pada tingkat tertentu—dikendalikan oleh -XX:InitiatingHeapOccupancyPercent=?
di mana defaultnya adalah 45% dari total heap—GC campuran dipicu. Ini mengumpulkan Gen Muda dan Gen Tua. Jeda GC campuran dikendalikan oleh berapa lama waktu yang dibutuhkan gen Muda untuk membersihkan saat GC campuran terjadi.
Jadi kita bisa melihat di G1, jeda GC "stop the world" didominasi oleh seberapa cepat G1 dapat menandai dan menyalin objek langsung dari ruang Eden. Dengan mengingat hal ini, kami akan menganalisis bagaimana pola alokasi memori HBase akan membantu kami menyetel G1 GC untuk mendapatkan jeda 100 milidetik yang diinginkan.
Di HBase, ada dua struktur dalam memori yang menghabiskan sebagian besar tumpukannya:BlockCache
, menyimpan blok file HBase untuk operasi baca, dan Memstore menyimpan pembaruan terbaru dalam cache.
Gambar 8:Dalam HBase, dua struktur dalam memori menghabiskan sebagian besar heapnya.
Implementasi default BlockCache
HBase adalah LruBlockCache
, yang hanya menggunakan array byte besar untuk menampung semua blok HBase. Saat blok “digusur”, referensi ke objek Java blok tersebut akan dihapus, memungkinkan GC untuk merelokasi memori.
Objek baru membentuk LruBlockCache
dan Memstore
pergi ke ruang Eden generasi Muda dulu. Jika mereka hidup cukup lama (yaitu, jika mereka tidak diusir dari LruBlockCache
atau dikeluarkan dari Memstore), kemudian setelah beberapa generasi Muda GC, mereka menuju ke tumpukan Java generasi Lama. Ketika ruang kosong Generasi Lama kurang dari threshOld
yang diberikan (InitiatingHeapOccupancyPercent
untuk memulai), GC campuran masuk dan membersihkan beberapa objek mati di Generasi Lama, menyalin objek hidup dari gen Muda, dan menghitung ulang Eden gen Muda dan HeapOccupancyPercent
gen Tua . Akhirnya, ketika HeapOccupancyPercent
mencapai level tertentu, sebuah FULL GC
terjadi, yang membuat GC "hentikan dunia" besar berhenti untuk membersihkan semua benda mati di dalam gen Lama.
Setelah mempelajari log GC yang dihasilkan oleh “-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy
“, kami melihat HeapOccupancyPercent
tidak pernah tumbuh cukup besar untuk menginduksi GC penuh selama HBase 100% membaca. Jeda GC yang kami lihat didominasi oleh jeda "hentikan dunia" generasi muda dan peningkatan pemrosesan referensi dari waktu ke waktu.
Setelah menyelesaikan analisis tersebut, kami membuat tiga grup perubahan dalam setelan default G1 GC:
- Gunakan
-XX:+ParallelRefProcEnabled
Saat tanda ini diaktifkan, GC menggunakan beberapa utas untuk memproses referensi yang meningkat selama GC Muda dan campuran. Dengan tanda ini untuk HBase, waktu komentar GC berkurang 75%, dan waktu jeda GC keseluruhan berkurang 30%. Set -XX:-ResizePLAB and -XX:ParallelGCThreads=8+(logical processors-8)(5/8)
Promotion Local Allocation Buffer (PLABs) digunakan selama koleksi Young. Beberapa utas digunakan. Setiap utas mungkin perlu mengalokasikan ruang untuk objek yang disalin baik di ruang Survivor atau Lama. PLAB diperlukan untuk menghindari persaingan benang untuk struktur data bersama yang mengelola memori bebas. Setiap utas GC memiliki satu PLAB untuk ruang Survival dan satu untuk ruang Lama. Kami ingin menghentikan pengubahan ukuran PLAB untuk menghindari biaya komunikasi yang besar di antara utas GC, serta variasi selama setiap GC. Kami ingin memperbaiki jumlah utas GC menjadi ukuran yang dihitung dengan 8+(prosesor logis-8)( 5/8). Formula ini baru-baru ini direkomendasikan oleh Oracle. Dengan kedua pengaturan, kami dapat melihat jeda GC yang lebih halus selama proses.- Ubah
-XX:G1NewSizePercent
default dari 5 hingga 1 untuk tumpukan 100 GBBerdasarkan keluaran dari-XX:+PrintGCDetails and -XX:+PrintAdaptiveSizePolicy
, kami melihat alasan kegagalan G1 untuk memenuhi waktu jeda 100GC yang kami inginkan adalah waktu yang dibutuhkan untuk memproses Eden. Dengan kata lain, G1 membutuhkan rata-rata 369 milidetik untuk mengosongkan 5GB Eden selama pengujian kami. Kami kemudian mengubah ukuran Eden menggunakan-XX:G1NewSizePercent=
tandai dari 5 ke 1. Dengan perubahan ini, kami melihat waktu jeda GC berkurang menjadi 100 milidetik.
Dari eksperimen ini, kami menemukan bahwa kecepatan G1 untuk membersihkan Eden adalah sekitar 1 GB per 100 milidetik, atau 10 GB per detik untuk penyiapan HBase yang kami gunakan.
Berdasarkan kecepatan tersebut, kita dapat menyetel -XX:G1NewSizePercent=
jadi ukuran Eden bisa dijaga sekitar 1GB. Misalnya:
- tumpukan 32 GB,
-XX:G1NewSizePercent=3
- tumpukan 64GB, –
XX:G1NewSizePercent=2
- 100 GB ke atas tumpukan,
-XX:G1NewSizePercent=1
- Jadi, opsi baris perintah terakhir kami untuk server HRegion adalah:
-XX:+UseG1GC
-Xms100g -Xmx100g
(Ukuran tumpukan yang digunakan dalam pengujian kami)-XX:MaxGCPauseMillis=100
(Waktu jeda GC yang diinginkan dalam pengujian)- –
XX:+ParallelRefProcEnabled
-XX:-ResizePLAB
-XX:ParallelGCThreads= 8+(40-8)(5/8)=28
-XX:G1NewSizePercent=1
Berikut adalah grafik waktu jeda GC untuk menjalankan operasi baca 100% selama 1 jam:
Gambar 9:Lonjakan pengendapan awal tertinggi berkurang lebih dari setengahnya.
Dalam grafik ini, bahkan lonjakan penyelesaian awal tertinggi berkurang dari 3,792 detik menjadi 1,684 detik. Paku paling awal kurang dari 1 detik. Setelah penyelesaian, GC mampu menjaga waktu jeda sekitar 100 milidetik.
Bagan di bawah ini membandingkan jdk7u60 berjalan dengan dan tanpa penyetelan, selama kondisi mapan:
Gambar 10:jdk7u60 berjalan dengan dan tanpa penyetelan, selama kondisi tunak.
Penyetelan GC sederhana yang kami jelaskan di atas memberikan waktu jeda GC yang ideal, sekitar 100 milidetik, dengan rata-rata 106 milidetik dan standar deviasi 7 milidetik.
Ringkasan
HBase adalah aplikasi kritis terhadap waktu respons yang membutuhkan waktu jeda GC agar dapat diprediksi dan dikelola. Dengan Oracle jdk7u60, berdasarkan informasi GC yang dilaporkan oleh -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy
, kami dapat menyetel waktu jeda GC ke 100 milidetik yang kami inginkan.
Eric Kaczmarek adalah arsitek kinerja Java di Grup Solusi Perangkat Lunak Intel. Dia memimpin upaya di Intel untuk mengaktifkan dan mengoptimalkan kerangka kerja Big Data (Hadoop, HBase, Spark, Cassandra) untuk platform Intel.
Perangkat lunak dan beban kerja yang digunakan dalam pengujian kinerja mungkin telah dioptimalkan untuk kinerja hanya pada mikroprosesor Intel. Tes kinerja, seperti SYSmark dan MobileMark, diukur menggunakan sistem, komponen, perangkat lunak, operasi, dan fungsi komputer tertentu. Setiap perubahan pada salah satu faktor tersebut dapat menyebabkan hasil yang berbeda. Anda harus berkonsultasi dengan informasi lain dan pengujian kinerja untuk membantu Anda mengevaluasi sepenuhnya pembelian yang Anda renungkan, termasuk kinerja produk tersebut bila digabungkan dengan produk lain.
Nomor prosesor Intel bukanlah ukuran kinerja. Nomor prosesor membedakan fitur dalam setiap keluarga prosesor. Tidak di keluarga prosesor yang berbeda. Buka:http://www.intel.com/products/processor_number.
Hak Cipta 2014 Intel Corp. Intel, logo Intel dan Xeon adalah merek dagang dari Intel Corporation di AS dan/atau negara lain.