Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Performa tinggi adalah salah satu persyaratan utama saat bekerja dengan data besar. Di departemen pemuatan data di Sberbank, kami memompa hampir semua transaksi ke Data Cloud berbasis Hadoop dan oleh karena itu menangani arus informasi yang sangat besar. Tentu saja, kami selalu mencari cara untuk meningkatkan kinerja, dan sekarang kami ingin memberi tahu Anda bagaimana kami berhasil menambal RegionServer HBase dan klien HDFS, sehingga kami dapat meningkatkan kecepatan operasi baca secara signifikan.
Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Namun, sebelum beralih ke inti perbaikan, ada baiknya membicarakan batasan yang, pada prinsipnya, tidak dapat dilewati jika Anda menggunakan HDD.

Mengapa HDD dan pembacaan Akses Acak cepat tidak kompatibel
Seperti yang Anda ketahui, HBase, dan banyak database lainnya, menyimpan data dalam blok berukuran beberapa puluh kilobyte. Secara default ukurannya sekitar 64 KB. Sekarang bayangkan kita hanya perlu mendapatkan 100 byte dan kita meminta HBase untuk memberi kita data ini menggunakan kunci tertentu. Karena ukuran blok di HFiles adalah 64 KB, permintaannya akan 640 kali lebih besar (hanya satu menit!) dari yang diperlukan.

Selanjutnya, karena permintaan akan melalui HDFS dan mekanisme cache metadatanya ShortCircuitCache (yang memungkinkan akses langsung ke file), ini menyebabkan pembacaan sudah 1 MB dari disk. Namun hal ini dapat disesuaikan dengan parameternya dfs.client.read.short Circuit.buffer.size dan dalam banyak kasus masuk akal untuk mengurangi nilai ini, misalnya menjadi 126 KB.

Katakanlah kita melakukan ini, tetapi selain itu, ketika kita mulai membaca data melalui java api, seperti fungsi seperti FileChannel.read dan meminta sistem operasi untuk membaca jumlah data yang ditentukan, ia membaca "berjaga-jaga" 2 kali lebih banyak , yaitu. 256 KB dalam kasus kami. Ini karena Java tidak memiliki cara mudah untuk menyetel flag FADV_RANDOM untuk mencegah perilaku ini.

Hasilnya, untuk mendapatkan 100 byte, 2600 kali lebih banyak yang dibaca. Tampaknya solusinya sudah jelas, mari kurangi ukuran blok menjadi satu kilobyte, atur tanda yang disebutkan dan dapatkan percepatan pencerahan yang luar biasa. Namun masalahnya adalah dengan mengurangi ukuran blok sebanyak 2 kali lipat, kita juga mengurangi jumlah byte yang dibaca per unit waktu sebanyak 2 kali lipat.

Beberapa keuntungan dari menyetel flag FADV_RANDOM dapat diperoleh, tetapi hanya dengan multi-threading tinggi dan dengan ukuran blok 128 KB, tetapi ini maksimum beberapa puluh persen:

Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Pengujian dilakukan pada 100 file, masing-masing berukuran 1 GB dan ditempatkan pada 10 HDD.

Mari kita hitung apa yang pada prinsipnya dapat kita andalkan dengan kecepatan ini:
Katakanlah kita membaca dari 10 disk dengan kecepatan 280 MB/detik, mis. 3 juta kali 100 byte. Namun seingat kita, data yang kita butuhkan 2600 kali lebih sedikit dari yang dibaca. Jadi, kita membagi 3 juta dengan 2600 dan mendapatkan 1100 catatan per detik.

Menyedihkan, bukan? Itu sifatnya Akses acak akses ke data di HDD - berapa pun ukuran bloknya. Ini adalah batas fisik dari akses acak dan tidak ada database yang dapat melakukan lebih banyak lagi dalam kondisi seperti itu.

Lalu bagaimana database mencapai kecepatan yang jauh lebih tinggi? Untuk menjawab pertanyaan tersebut, mari kita lihat apa yang terjadi pada gambar berikut:

Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Di sini kita melihat bahwa untuk beberapa menit pertama kecepatannya sekitar seribu rekaman per detik. Namun, lebih jauh lagi, karena lebih banyak yang dibaca daripada yang diminta, data berakhir di buff/cache sistem operasi (linux) dan kecepatan meningkat menjadi lebih baik 60 ribu per detik.

Oleh karena itu, selanjutnya kita akan membahas percepatan akses hanya ke data yang ada di cache OS atau terletak di perangkat penyimpanan SSD/NVMe dengan kecepatan akses yang sebanding.

Dalam kasus kami, kami akan melakukan pengujian pada 4 server, yang masing-masing dikenakan biaya sebagai berikut:

CPU: Xeon E5-2680 v4 @ 2.40GHz 64 utas.
Memori: 730GB.
versi java: 1.8.0_111

Dan di sini poin kuncinya adalah jumlah data dalam tabel yang perlu dibaca. Faktanya adalah jika Anda membaca data dari tabel yang seluruhnya ditempatkan di cache HBase, maka data tersebut bahkan tidak akan dibaca dari buff/cache sistem operasi. Karena HBase secara default mengalokasikan 40% memori ke struktur yang disebut BlockCache. Pada dasarnya ini adalah ConcurrentHashMap, dengan kuncinya adalah nama file + offset blok, dan nilainya adalah data aktual pada offset ini.

Jadi, ketika membaca hanya dari struktur ini, kita kami melihat kecepatan luar biasa, seperti satu juta permintaan per detik. Namun bayangkan saja kita tidak bisa mengalokasikan ratusan gigabyte memori hanya untuk kebutuhan database, karena masih banyak hal berguna lainnya yang berjalan di server tersebut.

Misalnya, dalam kasus kami, volume BlockCache pada satu RS adalah sekitar 12 GB. Kami mendaratkan dua RS pada satu node, mis. 96 GB dialokasikan untuk BlockCache di semua node. Dan datanya jauh lebih banyak, misalnya 4 tabel, masing-masing 130 wilayah, yang filenya berukuran 800 MB, dikompresi oleh FAST_DIFF, mis. total 410 GB (ini adalah data murni, yaitu tanpa memperhitungkan faktor replikasi).

Dengan demikian, BlockCache hanya menyumbang sekitar 23% dari total volume data dan ini lebih mendekati kondisi sebenarnya dari apa yang disebut BigData. Dan di sinilah kesenangan dimulai - karena jelas, semakin sedikit cache yang ditemukan, semakin buruk kinerjanya. Lagi pula, jika Anda ketinggalan, Anda harus melakukan banyak pekerjaan - mis. turun ke memanggil fungsi sistem. Namun, hal ini tidak dapat dihindari, jadi mari kita lihat aspek yang benar-benar berbeda - apa yang terjadi pada data di dalam cache?

Mari kita sederhanakan situasinya dan asumsikan bahwa kita memiliki cache yang hanya memuat 1 objek. Berikut adalah contoh apa yang akan terjadi ketika kita mencoba bekerja dengan volume data 3 kali lebih besar dari cache, kita harus:

1. Tempatkan blok 1 di cache
2. Hapus blok 1 dari cache
3. Tempatkan blok 2 di cache
4. Hapus blok 2 dari cache
5. Tempatkan blok 3 di cache

5 tindakan selesai! Namun, situasi ini tidak bisa disebut normal; pada kenyataannya, kami memaksa HBase untuk melakukan banyak pekerjaan yang sama sekali tidak berguna. Ia terus-menerus membaca data dari cache OS, menempatkannya di BlockCache, hanya untuk segera membuangnya karena sebagian data baru telah tiba. Animasi di awal postingan menunjukkan inti masalahnya - Pengumpul Sampah menjadi tidak terkendali, suasana memanas, Greta kecil di Swedia yang jauh dan panas menjadi kesal. Dan kami, orang IT, sangat tidak suka jika anak-anak sedih, jadi kami mulai memikirkan apa yang bisa kami lakukan.

Bagaimana jika Anda tidak memasukkan semua blok ke dalam cache, tetapi hanya persentase tertentu saja, agar cache tidak meluap? Mari kita mulai dengan menambahkan beberapa baris kode saja ke awal fungsi untuk memasukkan data ke dalam BlockCache:

  public void cacheBlock(BlockCacheKey cacheKey, Cacheable buf, boolean inMemory) {
    if (cacheDataBlockPercent != 100 && buf.getBlockType().isData()) {
      if (cacheKey.getOffset() % 100 >= cacheDataBlockPercent) {
        return;
      }
    }
...

Intinya di sini adalah sebagai berikut: offset adalah posisi blok dalam file dan digit terakhirnya didistribusikan secara acak dan merata dari 00 hingga 99. Oleh karena itu, kita hanya akan melewatkan yang termasuk dalam kisaran yang kita butuhkan.

Misalnya, setel cacheDataBlockPercent = 20 dan lihat apa yang terjadi:

Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Hasilnya jelas. Pada grafik di bawah, menjadi jelas mengapa percepatan seperti itu terjadi - kami menghemat banyak sumber daya GC tanpa melakukan pekerjaan Sisyphean dengan menempatkan data di cache hanya untuk segera membuangnya ke saluran pembuangan anjing-anjing Mars:

Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Pada saat yang sama, pemanfaatan CPU meningkat, namun jauh lebih rendah dibandingkan produktivitas:

Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Perlu juga dicatat bahwa blok yang disimpan di BlockCache berbeda. Sebagian besar, sekitar 95%, merupakan data itu sendiri. Dan sisanya adalah metadata, seperti filter Bloom atau LEAF_INDEX dan Ρ‚.Π΄.. Data ini tidak cukup, tetapi sangat berguna, karena sebelum mengakses data secara langsung, HBase beralih ke meta untuk memahami apakah perlu mencari lebih jauh di sini dan, jika demikian, di mana tepatnya blok yang diinginkan berada.

Oleh karena itu, dalam kode kita melihat kondisi cek buf.getBlockType().isData() dan berkat meta ini, kami akan membiarkannya di cache.

Sekarang mari tingkatkan beban dan sedikit perketat fiturnya sekaligus. Pada pengujian pertama kami membuat persentase batas = 20 dan BlockCache sedikit kurang dimanfaatkan. Sekarang mari kita atur menjadi 23% dan tambahkan 100 thread setiap 5 menit untuk melihat pada titik mana saturasi terjadi:

Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Di sini kita melihat bahwa versi aslinya segera mencapai puncaknya dengan sekitar 100 ribu permintaan per detik. Sedangkan pada patchnya memberikan akselerasi hingga 300 ribu. Pada saat yang sama, jelas bahwa akselerasi lebih lanjut tidak lagi β€œgratis”; penggunaan CPU juga meningkat.

Namun, ini bukan solusi yang sangat elegan, karena kita tidak mengetahui sebelumnya berapa persentase blok yang perlu di-cache, ini tergantung pada profil beban. Oleh karena itu, mekanisme diterapkan untuk menyesuaikan parameter ini secara otomatis tergantung pada aktivitas operasi pembacaan.

Tiga opsi telah ditambahkan untuk mengontrol ini:

hbase.lru.cache.heavy.eviction.count.limit β€” mengatur berapa kali proses mengeluarkan data dari cache harus dijalankan sebelum kita mulai menggunakan pengoptimalan (yaitu melewati blok). Secara default, nilainya sama dengan MAX_INT = 2147483647 dan sebenarnya berarti fitur tersebut tidak akan pernah mulai bekerja dengan nilai ini. Karena proses penggusuran dimulai setiap 5 - 10 detik (tergantung beban) dan 2147483647 * 10/60/60/24/365 = 680 tahun. Namun, kami dapat menyetel parameter ini ke 0 dan membuat fitur tersebut berfungsi segera setelah diluncurkan.

Namun, ada juga payload di parameter ini. Jika beban kita sedemikian rupa sehingga pembacaan jangka pendek (misalnya pada siang hari) dan pembacaan jangka panjang (pada malam hari) terus-menerus diselingi, maka kita dapat memastikan bahwa fitur tersebut diaktifkan hanya ketika operasi pembacaan panjang sedang berlangsung.

Misalnya, kita tahu bahwa pembacaan jangka pendek biasanya berlangsung sekitar 1 menit. Tidak perlu membuang blok, cache tidak akan punya waktu untuk menjadi usang dan kemudian kita dapat mengatur parameter ini sama dengan, misalnya, 10. Ini akan mengarah pada fakta bahwa optimasi akan mulai bekerja hanya ketika lama- istilah membaca aktif telah dimulai, yaitu. dalam 100 detik. Jadi, jika kita melakukan pembacaan jangka pendek, maka semua blok akan masuk ke cache dan akan tersedia (kecuali blok yang akan dikeluarkan oleh algoritma standar). Dan saat kami melakukan pembacaan jangka panjang, fitur tersebut diaktifkan dan kami akan mendapatkan performa yang jauh lebih tinggi.

hbase.lru.cache.heavy.eviction.mb.size.limit β€” menetapkan berapa megabyte yang ingin kita masukkan ke dalam cache (dan, tentu saja, mengeluarkannya) dalam 10 detik. Fitur tersebut akan mencoba mencapai nilai ini dan mempertahankannya. Intinya begini: jika kita memasukkan gigabyte ke dalam cache, maka kita harus mengeluarkan gigabyte, dan ini, seperti yang kita lihat di atas, sangat mahal. Namun, Anda tidak boleh mencoba mengaturnya terlalu kecil, karena ini akan menyebabkan mode lewati blok keluar sebelum waktunya. Untuk server yang kuat (sekitar 20-40 inti fisik), optimal untuk menetapkan sekitar 300-400 MB. Untuk kelas menengah (~10 core) 200-300 MB. Untuk sistem yang lemah (2-5 core) 50-100 MB mungkin normal (tidak diuji pada sistem ini).

Mari kita lihat cara kerjanya: misalkan kita menyetel hbase.lru.cache.heavy.eviction.mb.size.limit = 500, ada semacam beban (pembacaan) dan kemudian setiap ~10 detik kita menghitung berapa byte yang ada diusir dengan rumus :

Overhead = Jumlah Byte yang Dibebaskan (MB) * 100 / Batas (MB) - 100;

Jika ternyata 2000 MB yang digusur, maka Overhead sama dengan:

2000*100/500 - 100 = 300%

Algoritme mencoba mempertahankan tidak lebih dari beberapa puluh persen, sehingga fitur tersebut akan mengurangi persentase blok yang di-cache, sehingga menerapkan mekanisme penyetelan otomatis.

Namun jika bebannya turun, misalkan hanya 200 MB yang dikeluarkan dan Overhead menjadi negatif (yang disebut overshooting):

200*100/500 - 100 = -60%

Sebaliknya, fitur tersebut akan meningkatkan persentase blok cache hingga Overhead menjadi positif.

Di bawah ini adalah contoh tampilannya pada data nyata. Tidak perlu berusaha mencapai 0%, itu tidak mungkin. Sangat baik bila sekitar 30 - 100%, ini membantu menghindari keluarnya prematur dari mode optimasi selama lonjakan jangka pendek.

hbase.lru.cache.heavy.eviction.overhead.coefisien β€” menetapkan seberapa cepat kita ingin mendapatkan hasilnya. Jika kami mengetahui dengan pasti bahwa sebagian besar pembacaan kami berlangsung lama dan tidak ingin menunggu, kami dapat meningkatkan rasio ini dan mendapatkan performa tinggi dengan lebih cepat.

Misalnya, kita menetapkan koefisien ini = 0.01. Ini berarti Overhead (lihat di atas) akan dikalikan dengan angka ini dengan hasil yang dihasilkan dan persentase blok yang di-cache akan berkurang. Misalkan Overhead = 300% dan koefisien = 0.01, maka persentase blok yang di-cache akan berkurang sebesar 3%.

Logika β€œTekanan Balik” serupa juga diterapkan untuk nilai Overhead negatif (overshooting). Karena fluktuasi jangka pendek dalam volume pembacaan dan penggusuran selalu mungkin terjadi, mekanisme ini menghindari keluarnya prematur dari mode pengoptimalan. Tekanan balik memiliki logika terbalik: semakin kuat overshootingnya, semakin banyak blok yang di-cache.

Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Kode implementasi

        LruBlockCache cache = this.cache.get();
        if (cache == null) {
          break;
        }
        freedSumMb += cache.evict()/1024/1024;
        /*
        * Sometimes we are reading more data than can fit into BlockCache
        * and it is the cause a high rate of evictions.
        * This in turn leads to heavy Garbage Collector works.
        * So a lot of blocks put into BlockCache but never read,
        * but spending a lot of CPU resources.
        * Here we will analyze how many bytes were freed and decide
        * decide whether the time has come to reduce amount of caching blocks.
        * It help avoid put too many blocks into BlockCache
        * when evict() works very active and save CPU for other jobs.
        * More delails: https://issues.apache.org/jira/browse/HBASE-23887
        */

        // First of all we have to control how much time
        // has passed since previuos evict() was launched
        // This is should be almost the same time (+/- 10s)
        // because we get comparable volumes of freed bytes each time.
        // 10s because this is default period to run evict() (see above this.wait)
        long stopTime = System.currentTimeMillis();
        if ((stopTime - startTime) > 1000 * 10 - 1) {
          // Here we have to calc what situation we have got.
          // We have the limit "hbase.lru.cache.heavy.eviction.bytes.size.limit"
          // and can calculte overhead on it.
          // We will use this information to decide,
          // how to change percent of caching blocks.
          freedDataOverheadPercent =
            (int) (freedSumMb * 100 / cache.heavyEvictionMbSizeLimit) - 100;
          if (freedSumMb > cache.heavyEvictionMbSizeLimit) {
            // Now we are in the situation when we are above the limit
            // But maybe we are going to ignore it because it will end quite soon
            heavyEvictionCount++;
            if (heavyEvictionCount > cache.heavyEvictionCountLimit) {
              // It is going for a long time and we have to reduce of caching
              // blocks now. So we calculate here how many blocks we want to skip.
              // It depends on:
             // 1. Overhead - if overhead is big we could more aggressive
              // reducing amount of caching blocks.
              // 2. How fast we want to get the result. If we know that our
              // heavy reading for a long time, we don't want to wait and can
              // increase the coefficient and get good performance quite soon.
              // But if we don't sure we can do it slowly and it could prevent
              // premature exit from this mode. So, when the coefficient is
              // higher we can get better performance when heavy reading is stable.
              // But when reading is changing we can adjust to it and set
              // the coefficient to lower value.
              int change =
                (int) (freedDataOverheadPercent * cache.heavyEvictionOverheadCoefficient);
              // But practice shows that 15% of reducing is quite enough.
              // We are not greedy (it could lead to premature exit).
              change = Math.min(15, change);
              change = Math.max(0, change); // I think it will never happen but check for sure
              // So this is the key point, here we are reducing % of caching blocks
              cache.cacheDataBlockPercent -= change;
              // If we go down too deep we have to stop here, 1% any way should be.
              cache.cacheDataBlockPercent = Math.max(1, cache.cacheDataBlockPercent);
            }
          } else {
            // Well, we have got overshooting.
            // Mayby it is just short-term fluctuation and we can stay in this mode.
            // It help avoid permature exit during short-term fluctuation.
            // If overshooting less than 90%, we will try to increase the percent of
            // caching blocks and hope it is enough.
            if (freedSumMb >= cache.heavyEvictionMbSizeLimit * 0.1) {
              // Simple logic: more overshooting - more caching blocks (backpressure)
              int change = (int) (-freedDataOverheadPercent * 0.1 + 1);
              cache.cacheDataBlockPercent += change;
              // But it can't be more then 100%, so check it.
              cache.cacheDataBlockPercent = Math.min(100, cache.cacheDataBlockPercent);
            } else {
              // Looks like heavy reading is over.
              // Just exit form this mode.
              heavyEvictionCount = 0;
              cache.cacheDataBlockPercent = 100;
            }
          }
          LOG.info("BlockCache evicted (MB): {}, overhead (%): {}, " +
            "heavy eviction counter: {}, " +
            "current caching DataBlock (%): {}",
            freedSumMb, freedDataOverheadPercent,
            heavyEvictionCount, cache.cacheDataBlockPercent);

          freedSumMb = 0;
          startTime = stopTime;
       }

Sekarang mari kita lihat semua ini dengan menggunakan contoh nyata. Kami memiliki skrip pengujian berikut:

  1. Mari kita mulai melakukan Scan (25 thread, batch = 100)
  2. Setelah 5 menit, tambahkan multi-get (25 thread, batch = 100)
  3. Setelah 5 menit, matikan multi-gets (yang tersisa hanya scan lagi)

Kami melakukan dua proses, pertama hbase.lru.cache.heavy.eviction.count.limit = 10000 (yang sebenarnya menonaktifkan fitur), dan kemudian menetapkan limit = 0 (mengaktifkannya).

Dalam log di bawah ini kita melihat bagaimana fitur tersebut diaktifkan dan mengatur ulang Overshooting menjadi 14-71%. Dari waktu ke waktu beban berkurang, yang mengaktifkan Backpressure dan HBase menyimpan lebih banyak blok lagi.

Server Wilayah Log
diusir (MB): 0, rasio 0.0, overhead (%): -100, penghitung penggusuran berat: 0, DataBlock cache saat ini (%): 100
diusir (MB): 0, rasio 0.0, overhead (%): -100, penghitung penggusuran berat: 0, DataBlock cache saat ini (%): 100
diusir (MB): 2170, rasio 1.09, overhead (%): 985, penghitung penggusuran berat: 1, DataBlock cache saat ini (%): 91 <mulai
diusir (MB): 3763, rasio 1.08, overhead (%): 1781, penghitung penggusuran berat: 2, DataBlock cache saat ini (%): 76
diusir (MB): 3306, rasio 1.07, overhead (%): 1553, penghitung penggusuran berat: 3, DataBlock cache saat ini (%): 61
diusir (MB): 2508, rasio 1.06, overhead (%): 1154, penghitung penggusuran berat: 4, DataBlock cache saat ini (%): 50
diusir (MB): 1824, rasio 1.04, overhead (%): 812, penghitung penggusuran berat: 5, DataBlock cache saat ini (%): 42
diusir (MB): 1482, rasio 1.03, overhead (%): 641, penghitung penggusuran berat: 6, DataBlock cache saat ini (%): 36
diusir (MB): 1140, rasio 1.01, overhead (%): 470, penghitung penggusuran berat: 7, DataBlock cache saat ini (%): 32
diusir (MB): 913, rasio 1.0, overhead (%): 356, penghitung penggusuran berat: 8, DataBlock cache saat ini (%): 29
diusir (MB): 912, rasio 0.89, overhead (%): 356, penghitung penggusuran berat: 9, DataBlock cache saat ini (%): 26
diusir (MB): 684, rasio 0.76, overhead (%): 242, penghitung penggusuran berat: 10, DataBlock cache saat ini (%): 24
diusir (MB): 684, rasio 0.61, overhead (%): 242, penghitung penggusuran berat: 11, DataBlock cache saat ini (%): 22
diusir (MB): 456, rasio 0.51, overhead (%): 128, penghitung penggusuran berat: 12, DataBlock cache saat ini (%): 21
diusir (MB): 456, rasio 0.42, overhead (%): 128, penghitung penggusuran berat: 13, DataBlock cache saat ini (%): 20
diusir (MB): 456, rasio 0.33, overhead (%): 128, penghitung penggusuran berat: 14, DataBlock cache saat ini (%): 19
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 15, DataBlock cache saat ini (%): 19
diusir (MB): 342, rasio 0.32, overhead (%): 71, penghitung penggusuran berat: 16, DataBlock cache saat ini (%): 19
diusir (MB): 342, rasio 0.31, overhead (%): 71, penghitung penggusuran berat: 17, DataBlock cache saat ini (%): 19
diusir (MB): 228, rasio 0.3, overhead (%): 14, penghitung penggusuran berat: 18, DataBlock cache saat ini (%): 19
diusir (MB): 228, rasio 0.29, overhead (%): 14, penghitung penggusuran berat: 19, DataBlock cache saat ini (%): 19
diusir (MB): 228, rasio 0.27, overhead (%): 14, penghitung penggusuran berat: 20, DataBlock cache saat ini (%): 19
diusir (MB): 228, rasio 0.25, overhead (%): 14, penghitung penggusuran berat: 21, DataBlock cache saat ini (%): 19
diusir (MB): 228, rasio 0.24, overhead (%): 14, penghitung penggusuran berat: 22, DataBlock cache saat ini (%): 19
diusir (MB): 228, rasio 0.22, overhead (%): 14, penghitung penggusuran berat: 23, DataBlock cache saat ini (%): 19
diusir (MB): 228, rasio 0.21, overhead (%): 14, penghitung penggusuran berat: 24, DataBlock cache saat ini (%): 19
diusir (MB): 228, rasio 0.2, overhead (%): 14, penghitung penggusuran berat: 25, DataBlock cache saat ini (%): 19
diusir (MB): 228, rasio 0.17, overhead (%): 14, penghitung penggusuran berat: 26, DataBlock cache saat ini (%): 19
diusir (MB): 456, rasio 0.17, overhead (%): 128, penghitung penggusuran berat: 27, DataBlock cache saat ini (%): 18 <tambahan yang didapat (tetapi tabelnya sama)
diusir (MB): 456, rasio 0.15, overhead (%): 128, penghitung penggusuran berat: 28, DataBlock cache saat ini (%): 17
diusir (MB): 342, rasio 0.13, overhead (%): 71, penghitung penggusuran berat: 29, DataBlock cache saat ini (%): 17
diusir (MB): 342, rasio 0.11, overhead (%): 71, penghitung penggusuran berat: 30, DataBlock cache saat ini (%): 17
diusir (MB): 342, rasio 0.09, overhead (%): 71, penghitung penggusuran berat: 31, DataBlock cache saat ini (%): 17
diusir (MB): 228, rasio 0.08, overhead (%): 14, penghitung penggusuran berat: 32, DataBlock cache saat ini (%): 17
diusir (MB): 228, rasio 0.07, overhead (%): 14, penghitung penggusuran berat: 33, DataBlock cache saat ini (%): 17
diusir (MB): 228, rasio 0.06, overhead (%): 14, penghitung penggusuran berat: 34, DataBlock cache saat ini (%): 17
diusir (MB): 228, rasio 0.05, overhead (%): 14, penghitung penggusuran berat: 35, DataBlock cache saat ini (%): 17
diusir (MB): 228, rasio 0.05, overhead (%): 14, penghitung penggusuran berat: 36, DataBlock cache saat ini (%): 17
diusir (MB): 228, rasio 0.04, overhead (%): 14, penghitung penggusuran berat: 37, DataBlock cache saat ini (%): 17
diusir (MB): 109, rasio 0.04, overhead (%): -46, penghitung penggusuran berat: 37, DataBlock cache saat ini (%): 22 <tekanan balik
diusir (MB): 798, rasio 0.24, overhead (%): 299, penghitung penggusuran berat: 38, DataBlock cache saat ini (%): 20
diusir (MB): 798, rasio 0.29, overhead (%): 299, penghitung penggusuran berat: 39, DataBlock cache saat ini (%): 18
diusir (MB): 570, rasio 0.27, overhead (%): 185, penghitung penggusuran berat: 40, DataBlock cache saat ini (%): 17
diusir (MB): 456, rasio 0.22, overhead (%): 128, penghitung penggusuran berat: 41, DataBlock cache saat ini (%): 16
diusir (MB): 342, rasio 0.16, overhead (%): 71, penghitung penggusuran berat: 42, DataBlock cache saat ini (%): 16
diusir (MB): 342, rasio 0.11, overhead (%): 71, penghitung penggusuran berat: 43, DataBlock cache saat ini (%): 16
diusir (MB): 228, rasio 0.09, overhead (%): 14, penghitung penggusuran berat: 44, DataBlock cache saat ini (%): 16
diusir (MB): 228, rasio 0.07, overhead (%): 14, penghitung penggusuran berat: 45, DataBlock cache saat ini (%): 16
diusir (MB): 228, rasio 0.05, overhead (%): 14, penghitung penggusuran berat: 46, DataBlock cache saat ini (%): 16
diusir (MB): 222, rasio 0.04, overhead (%): 11, penghitung penggusuran berat: 47, DataBlock cache saat ini (%): 16
diusir (MB): 104, rasio 0.03, overhead (%): -48, penghitung penggusuran berat: 47, DataBlock cache saat ini (%): 21 < interupsi didapat
diusir (MB): 684, rasio 0.2, overhead (%): 242, penghitung penggusuran berat: 48, DataBlock cache saat ini (%): 19
diusir (MB): 570, rasio 0.23, overhead (%): 185, penghitung penggusuran berat: 49, DataBlock cache saat ini (%): 18
diusir (MB): 342, rasio 0.22, overhead (%): 71, penghitung penggusuran berat: 50, DataBlock cache saat ini (%): 18
diusir (MB): 228, rasio 0.21, overhead (%): 14, penghitung penggusuran berat: 51, DataBlock cache saat ini (%): 18
diusir (MB): 228, rasio 0.2, overhead (%): 14, penghitung penggusuran berat: 52, DataBlock cache saat ini (%): 18
diusir (MB): 228, rasio 0.18, overhead (%): 14, penghitung penggusuran berat: 53, DataBlock cache saat ini (%): 18
diusir (MB): 228, rasio 0.16, overhead (%): 14, penghitung penggusuran berat: 54, DataBlock cache saat ini (%): 18
diusir (MB): 228, rasio 0.14, overhead (%): 14, penghitung penggusuran berat: 55, DataBlock cache saat ini (%): 18
diusir (MB): 112, rasio 0.14, overhead (%): -44, penghitung penggusuran berat: 55, DataBlock cache saat ini (%): 23 <tekanan balik
diusir (MB): 456, rasio 0.26, overhead (%): 128, penghitung penggusuran berat: 56, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.31, overhead (%): 71, penghitung penggusuran berat: 57, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 58, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 59, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 60, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 61, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 62, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 63, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.32, overhead (%): 71, penghitung penggusuran berat: 64, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 65, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 66, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.32, overhead (%): 71, penghitung penggusuran berat: 67, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 68, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.32, overhead (%): 71, penghitung penggusuran berat: 69, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.32, overhead (%): 71, penghitung penggusuran berat: 70, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 71, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 72, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 73, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 74, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 75, DataBlock cache saat ini (%): 22
diusir (MB): 342, rasio 0.33, overhead (%): 71, penghitung penggusuran berat: 76, DataBlock cache saat ini (%): 22
diusir (MB): 21, rasio 0.33, overhead (%): -90, penghitung penggusuran berat: 76, DataBlock cache saat ini (%): 32
diusir (MB): 0, rasio 0.0, overhead (%): -100, penghitung penggusuran berat: 0, DataBlock cache saat ini (%): 100
diusir (MB): 0, rasio 0.0, overhead (%): -100, penghitung penggusuran berat: 0, DataBlock cache saat ini (%): 100

Pemindaian diperlukan untuk menunjukkan proses yang sama dalam bentuk grafik hubungan antara dua bagian cache - tunggal (di mana blok yang belum pernah diminta sebelumnya) dan multi (data yang "diminta" setidaknya sekali disimpan di sini):

Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Dan terakhir, seperti apa pengoperasian parameter dalam bentuk grafik. Sebagai perbandingan, cache dimatikan sepenuhnya di awal, kemudian HBase diluncurkan dengan caching dan menunda dimulainya pekerjaan pengoptimalan selama 5 menit (30 siklus penggusuran).

Kode lengkap dapat ditemukan di Pull Request HBASE 23887 di github.

Namun, 300 ribu pembacaan per detik bukanlah satu-satunya yang dapat dicapai pada perangkat keras ini dalam kondisi seperti ini. Faktanya adalah ketika Anda perlu mengakses data melalui HDFS, mekanisme ShortCircuitCache (selanjutnya disebut SSC) digunakan, yang memungkinkan Anda mengakses data secara langsung, menghindari interaksi jaringan.

Pembuatan profil menunjukkan bahwa meskipun mekanisme ini memberikan keuntungan yang besar, namun pada titik tertentu juga menjadi hambatan, karena hampir semua operasi berat terjadi di dalam kunci, yang sering kali menyebabkan pemblokiran.

Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Setelah menyadari hal ini, kami menyadari bahwa masalah ini dapat diatasi dengan membuat serangkaian SSC independen:

private final ShortCircuitCache[] shortCircuitCache;
...
shortCircuitCache = new ShortCircuitCache[this.clientShortCircuitNum];
for (int i = 0; i < this.clientShortCircuitNum; i++)
  this.shortCircuitCache[i] = new ShortCircuitCache(…);

Dan kemudian kerjakan dengan mereka, tidak termasuk persimpangan juga pada digit offset terakhir:

public ShortCircuitCache getShortCircuitCache(long idx) {
    return shortCircuitCache[(int) (idx % clientShortCircuitNum)];
}

Sekarang Anda dapat mulai menguji. Untuk melakukan ini, kita akan membaca file dari HDFS dengan aplikasi multi-thread sederhana. Tetapkan parameter:

conf.set("dfs.client.read.shortcircuit", "true");
conf.set("dfs.client.read.shortcircuit.buffer.size", "65536"); // ΠΏΠΎ Π΄Π΅Ρ„ΠΎΠ»Ρ‚Ρƒ = 1 ΠœΠ‘ ΠΈ это сильно замСдляСт Ρ‡Ρ‚Π΅Π½ΠΈΠ΅, поэтому Π»ΡƒΡ‡ΡˆΠ΅ привСсти Π² соотвСтствиС ΠΊ Ρ€Π΅Π°Π»ΡŒΠ½Ρ‹ΠΌ Π½ΡƒΠΆΠ΄Π°ΠΌ
conf.set("dfs.client.short.circuit.num", num); // ΠΎΡ‚ 1 Π΄ΠΎ 10

Dan baca saja filenya:

FSDataInputStream in = fileSystem.open(path);
for (int i = 0; i < count; i++) {
    position += 65536;
    if (position > 900000000)
        position = 0L;
    int res = in.read(position, byteBuffer, 0, 65536);
}

Kode ini dijalankan di thread terpisah dan kami akan meningkatkan jumlah file yang dibaca secara bersamaan (dari 10 menjadi 200 - sumbu horizontal) dan jumlah cache (dari 1 menjadi 10 - grafik). Sumbu vertikal menunjukkan percepatan yang dihasilkan dari peningkatan SSC dibandingkan jika hanya ada satu cache.

Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Cara membaca grafik: Waktu eksekusi 100 ribu pembacaan dalam blok 64 KB dengan satu cache memerlukan waktu 78 detik. Sedangkan dengan 5 cache membutuhkan waktu 16 detik. Itu. ada percepatan ~5 kali. Seperti yang dapat dilihat dari grafik, efeknya tidak terlalu terlihat untuk sejumlah kecil pembacaan paralel, namun mulai memainkan peran penting ketika terdapat lebih dari 50 pembacaan thread. Juga terlihat bahwa peningkatan jumlah SSC dari 6 dan di atasnya memberikan peningkatan kinerja yang jauh lebih kecil.

Catatan 1: karena hasil pengujian cukup fluktuatif (lihat di bawah), dilakukan 3 kali proses dan nilai yang dihasilkan dirata-ratakan.

Catatan 2: Peningkatan kinerja dari konfigurasi akses acak adalah sama, meskipun aksesnya sendiri sedikit lebih lambat.

Namun perlu diklarifikasi bahwa, berbeda dengan HBase, akselerasi ini tidak selalu gratis. Di sini kita β€œmembuka” kemampuan CPU untuk melakukan pekerjaan lebih banyak, daripada terus-terusan mengunci.

Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Di sini Anda dapat mengamati bahwa, secara umum, peningkatan jumlah cache memberikan peningkatan penggunaan CPU yang kira-kira sebanding. Namun, ada kombinasi yang lebih unggul.

Misalnya, mari kita lihat lebih dekat pengaturan SSC = 3. Peningkatan kinerja pada kisaran tersebut sekitar 3.3 kali lipat. Di bawah ini adalah hasil dari ketiga proses terpisah.

Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Sedangkan konsumsi CPU meningkat sekitar 2.8 kali lipat. Perbedaannya tidak terlalu besar, namun Greta kecil sudah bahagia dan mungkin punya waktu untuk bersekolah dan mengikuti pelajaran.

Oleh karena itu, hal ini akan berdampak positif untuk alat apa pun yang menggunakan akses massal ke HDFS (misalnya Spark, dll.), asalkan kode aplikasinya ringan (yaitu colokannya ada di sisi klien HDFS) dan ada daya CPU gratis . Untuk memeriksanya, mari kita uji apa pengaruh penggunaan gabungan optimasi BlockCache dan penyetelan SSC untuk membaca dari HBase.

Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Dapat dilihat bahwa dalam kondisi seperti itu, efeknya tidak sebesar pada pengujian yang disempurnakan (membaca tanpa pemrosesan apa pun), tetapi sangat mungkin untuk mendapatkan tambahan 80K di sini. Bersama-sama, kedua pengoptimalan memberikan percepatan hingga 4x.

PR juga dibuat untuk optimasi ini [HDFS-15202], yang telah digabungkan dan fungsi ini akan tersedia pada rilis mendatang.

Dan terakhir, menarik untuk membandingkan kinerja membaca database kolom lebar serupa, Cassandra dan HBase.

Untuk melakukan ini, kami meluncurkan contoh utilitas pengujian beban YCSB standar dari dua host (total 800 thread). Di sisi server - 4 instance RegionServer dan Cassandra pada 4 host (bukan yang menjalankan klien, untuk menghindari pengaruhnya). Bacaan berasal dari tabel ukuran:

HBase – 300 GB pada HDFS (data murni 100 GB)

Cassandra - 250 GB (faktor replikasi = 3)

Itu. volumenya kira-kira sama (di HBase sedikit lebih banyak).

Parameter HBase:

dfs.client.short.circir.num = 5 (optimasi klien HDFS)

hbase.lru.cache.heavy.eviction.count.limit = 30 - ini berarti patch akan mulai berfungsi setelah 30 penggusuran (~5 menit)

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 β€” target volume cache dan penggusuran

Log YCSB diurai dan dikompilasi menjadi grafik Excel:

Cara meningkatkan kecepatan baca dari HBase hingga 3 kali lipat dan dari HDFS hingga 5 kali lipat

Seperti yang Anda lihat, pengoptimalan ini memungkinkan untuk membandingkan kinerja database ini dalam kondisi ini dan mencapai 450 ribu pembacaan per detik.

Kami berharap informasi ini dapat bermanfaat bagi seseorang selama perjuangan seru untuk produktivitas.

Sumber: www.habr.com

Tambah komentar