Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Prestasi tinggi adalah salah satu keperluan utama apabila bekerja dengan data besar. Di jabatan pemuatan data di Sberbank, kami mengepam hampir semua transaksi ke Cloud Data berasaskan Hadoop kami dan oleh itu menangani aliran maklumat yang sangat besar. Sememangnya, kami sentiasa mencari cara untuk meningkatkan prestasi, dan kini kami ingin memberitahu anda bagaimana kami berjaya menampal RegionServer HBase dan klien HDFS, yang mana kami dapat meningkatkan kelajuan operasi membaca dengan ketara.
Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Walau bagaimanapun, sebelum beralih kepada intipati penambahbaikan, patut dibincangkan tentang sekatan yang, pada dasarnya, tidak boleh dielakkan jika anda menggunakan HDD.

Mengapa bacaan HDD dan Random Access pantas tidak serasi
Seperti yang anda ketahui, HBase, dan banyak pangkalan data lain, menyimpan data dalam blok bersaiz beberapa puluh kilobait. Secara lalai ia adalah kira-kira 64 KB. Sekarang mari kita bayangkan bahawa kita perlu mendapatkan hanya 100 bait dan kami meminta HBase untuk memberikan kami data ini menggunakan kunci tertentu. Memandangkan saiz blok dalam HFiles ialah 64 KB, permintaan akan menjadi 640 kali lebih besar (hanya seminit!) daripada yang diperlukan.

Seterusnya, kerana permintaan akan melalui HDFS dan mekanisme caching metadatanya ShortCircuitCache (yang membenarkan akses terus kepada fail), ini membawa kepada membaca sudah 1 MB daripada cakera. Walau bagaimanapun, ini boleh diselaraskan dengan parameter dfs.client.read.shortcircuit.buffer.size dan dalam banyak kes adalah masuk akal untuk mengurangkan nilai ini, contohnya kepada 126 KB.

Katakan kita melakukan ini, tetapi sebagai tambahan, apabila kita mula membaca data melalui java api, seperti fungsi seperti FileChannel.read dan minta sistem pengendalian membaca jumlah data yang ditentukan, ia berbunyi β€œsekiranya” 2 kali lebih banyak. , iaitu 256 KB dalam kes kami. Ini kerana java tidak mempunyai cara mudah untuk menetapkan bendera FADV_RANDOM untuk menghalang tingkah laku ini.

Akibatnya, untuk mendapatkan 100 bait kami, 2600 kali lebih banyak dibaca di bawah hud. Nampaknya penyelesaiannya jelas, mari kita kurangkan saiz blok kepada kilobait, tetapkan bendera yang disebutkan dan dapatkan pecutan pencerahan yang hebat. Tetapi masalahnya ialah dengan mengurangkan saiz blok sebanyak 2 kali, kami juga mengurangkan bilangan bait yang dibaca setiap unit masa sebanyak 2 kali.

Beberapa keuntungan daripada menetapkan bendera FADV_RANDOM boleh diperolehi, tetapi hanya dengan berbilang benang yang tinggi dan dengan saiz blok 128 KB, tetapi ini adalah maksimum beberapa puluh peratus:

Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Ujian telah dijalankan pada 100 fail, setiap satu bersaiz 1 GB dan terletak pada 10 HDD.

Mari kita mengira apa yang kita boleh, pada dasarnya, bergantung pada kelajuan ini:
Katakan kita membaca dari 10 cakera pada kelajuan 280 MB/s, i.e. 3 juta kali 100 bait. Tetapi seperti yang kita ingat, data yang kita perlukan adalah 2600 kali kurang daripada apa yang dibaca. Oleh itu, kita membahagikan 3 juta dengan 2600 dan mendapat 1100 rekod sesaat.

Menyedihkan, bukan? Itu fitrah Akses Rawak akses kepada data pada HDD - tanpa mengira saiz blok. Ini adalah had fizikal capaian rawak dan tiada pangkalan data boleh memerah lebih banyak dalam keadaan sedemikian.

Bagaimanakah pangkalan data mencapai kelajuan yang lebih tinggi? Untuk menjawab soalan ini, mari kita lihat apa yang berlaku dalam gambar berikut:

Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Di sini kita melihat bahawa untuk beberapa minit pertama kelajuannya adalah kira-kira seribu rekod sesaat. Walau bagaimanapun, selanjutnya, disebabkan fakta bahawa lebih banyak dibaca daripada yang diminta, data berakhir dalam buff/cache sistem pengendalian (linux) dan kelajuan meningkat kepada 60 ribu sesaat yang lebih baik.

Oleh itu, selanjutnya kita akan berurusan dengan mempercepatkan akses hanya kepada data yang ada dalam cache OS atau terletak dalam peranti storan SSD/NVMe dengan kelajuan akses yang setanding.

Dalam kes kami, kami akan menjalankan ujian pada bangku 4 pelayan, setiap satunya dicaj seperti berikut:

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

Dan di sini perkara utama ialah jumlah data dalam jadual yang perlu dibaca. Hakikatnya ialah jika anda membaca data daripada jadual yang diletakkan sepenuhnya dalam cache HBase, maka data itu tidak akan dapat dibaca daripada buff/cache sistem pengendalian. Kerana HBase secara lalai memperuntukkan 40% memori kepada struktur yang dipanggil BlockCache. Pada asasnya ini ialah ConcurrentHashMap, di mana kuncinya ialah nama fail + offset blok, dan nilainya ialah data sebenar pada offset ini.

Oleh itu, apabila membaca hanya dari struktur ini, kita kita lihat kelajuan yang sangat baik, seperti sejuta permintaan sesaat. Tetapi mari kita bayangkan bahawa kita tidak boleh memperuntukkan ratusan gigabait memori hanya untuk keperluan pangkalan data, kerana terdapat banyak perkara berguna lain yang dijalankan pada pelayan ini.

Sebagai contoh, dalam kes kami, jumlah BlockCache pada satu RS adalah kira-kira 12 GB. Kami mendaratkan dua RS pada satu nod, i.e. 96 GB diperuntukkan untuk BlockCache pada semua nod. Dan terdapat banyak kali lebih banyak data, sebagai contoh, biarkan ia menjadi 4 jadual, 130 wilayah setiap satu, di mana fail bersaiz 800 MB, dimampatkan oleh FAST_DIFF, i.e. sejumlah 410 GB (ini adalah data tulen, iaitu tanpa mengambil kira faktor replikasi).

Oleh itu, BlockCache hanyalah kira-kira 23% daripada jumlah volum data dan ini lebih dekat dengan keadaan sebenar yang dipanggil BigData. Dan di sinilah keseronokan bermula - kerana jelas sekali, semakin sedikit cache hits, semakin teruk prestasinya. Lagipun, jika anda terlepas, anda perlu melakukan banyak kerja - i.e. pergi ke memanggil fungsi sistem. Walau bagaimanapun, ini tidak boleh dielakkan, jadi mari kita lihat aspek yang sama sekali berbeza - apakah yang berlaku kepada data di dalam cache?

Mari kita permudahkan keadaan dan anggap bahawa kita mempunyai cache yang hanya muat 1 objek. Berikut ialah contoh perkara yang akan berlaku apabila kami cuba bekerja dengan volum data 3 kali lebih besar daripada cache, kami perlu:

1. Letakkan blok 1 dalam cache
2. Alih keluar blok 1 daripada cache
3. Letakkan blok 2 dalam cache
4. Alih keluar blok 2 daripada cache
5. Letakkan blok 3 dalam cache

5 tindakan selesai! Walau bagaimanapun, keadaan ini tidak boleh dipanggil biasa; sebenarnya, kami memaksa HBase untuk melakukan banyak kerja yang tidak berguna sama sekali. Ia sentiasa membaca data dari cache OS, meletakkannya dalam BlockCache, hanya untuk membuangnya dengan serta-merta kerana bahagian baru data telah tiba. Animasi pada permulaan siaran menunjukkan intipati masalah - Pemungut Sampah tidak berskala, suasana semakin panas, Greta kecil di Sweden yang jauh dan panas semakin kecewa. Dan kami orang IT sangat tidak suka apabila kanak-kanak sedih, jadi kami mula memikirkan apa yang boleh kami lakukan mengenainya.

Bagaimana jika anda tidak meletakkan semua blok dalam cache, tetapi hanya peratusan tertentu daripadanya, supaya cache tidak melimpah? Mari kita mulakan dengan hanya menambah beberapa baris kod pada permulaan fungsi untuk meletakkan 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 ialah yang berikut: offset ialah kedudukan blok dalam fail dan digit terakhirnya diagihkan secara rawak dan sama rata dari 00 hingga 99. Oleh itu, kami hanya akan melangkau mereka yang termasuk dalam julat yang kami perlukan.

Sebagai contoh, tetapkan cacheDataBlockPercent = 20 dan lihat apa yang berlaku:

Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Hasilnya adalah jelas. Dalam graf di bawah, menjadi jelas mengapa pecutan sedemikian berlaku - kami menjimatkan banyak sumber GC tanpa melakukan kerja Sisyphean untuk meletakkan data dalam cache hanya untuk segera membuangnya ke longkang anjing Marikh:

Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Pada masa yang sama, penggunaan CPU meningkat, tetapi jauh lebih rendah daripada produktiviti:

Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Perlu juga diperhatikan bahawa blok yang disimpan dalam BlockCache adalah berbeza. Kebanyakan, kira-kira 95%, adalah data itu sendiri. Dan selebihnya ialah metadata, seperti penapis Bloom atau LEAF_INDEX dan dan lain-lain. Data ini tidak mencukupi, tetapi ia sangat berguna, kerana sebelum mengakses data secara langsung, HBase beralih kepada meta untuk memahami sama ada perlu untuk mencari di sini dengan lebih lanjut dan, jika ya, di mana betul-betul blok minat berada.

Oleh itu, dalam kod kita melihat keadaan semak buf.getBlockType().isData() dan terima kasih kepada meta ini, kami akan meninggalkannya dalam cache dalam apa jua keadaan.

Sekarang mari kita tingkatkan beban dan ketatkan sedikit ciri sekali gus. Dalam ujian pertama kami membuat peratusan cutoff = 20 dan BlockCache sedikit kurang digunakan. Sekarang mari kita tetapkan kepada 23% dan tambah 100 utas setiap 5 minit untuk melihat pada titik ketepuan yang berlaku:

Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Di sini kita melihat bahawa versi asal hampir serta-merta mencecah siling pada kira-kira 100 ribu permintaan sesaat. Sedangkan patch memberikan pecutan sehingga 300 ribu. Pada masa yang sama, jelas bahawa pecutan selanjutnya tidak lagi begitu "percuma"; penggunaan CPU juga meningkat.

Walau bagaimanapun, ini bukan penyelesaian yang sangat elegan, kerana kita tidak tahu terlebih dahulu berapa peratusan blok yang perlu di-cache, ia bergantung pada profil beban. Oleh itu, mekanisme telah dilaksanakan untuk melaraskan parameter ini secara automatik bergantung pada aktiviti operasi membaca.

Tiga pilihan telah ditambah untuk mengawal ini:

hbase.lru.cache.heavy.eviction.count.limit β€” menetapkan berapa kali proses membuang data daripada cache harus dijalankan sebelum kita mula menggunakan pengoptimuman (iaitu melangkau blok). Secara lalai ia sama dengan MAX_INT = 2147483647 dan sebenarnya bermakna ciri itu tidak akan mula berfungsi dengan nilai ini. Kerana proses pengusiran bermula setiap 5 - 10 saat (ia bergantung kepada beban) dan 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 tahun. Walau bagaimanapun, kami boleh menetapkan parameter ini kepada 0 dan menjadikan ciri berfungsi serta-merta selepas pelancaran.

Walau bagaimanapun, terdapat juga muatan dalam parameter ini. Jika beban kami sedemikian rupa sehingga bacaan jangka pendek (katakan pada waktu siang) dan bacaan jangka panjang (pada waktu malam) sentiasa diselang-seli, maka kami boleh memastikan bahawa ciri itu dihidupkan hanya apabila operasi bacaan panjang sedang dijalankan.

Sebagai contoh, kita tahu bahawa bacaan jangka pendek biasanya berlangsung kira-kira 1 minit. Tidak perlu mula membuang blok, cache tidak akan mempunyai masa untuk menjadi ketinggalan zaman dan kemudian kita boleh menetapkan parameter ini sama dengan, sebagai contoh, 10. Ini akan membawa kepada fakta bahawa pengoptimuman akan mula berfungsi hanya apabila lama- istilah membaca aktif telah bermula, i.e. dalam 100 saat. Oleh itu, jika kita mempunyai bacaan jangka pendek, maka semua blok akan masuk ke dalam cache dan akan tersedia (kecuali yang akan diusir oleh algoritma standard). Dan apabila kami melakukan bacaan jangka panjang, ciri ini dihidupkan dan kami akan mempunyai prestasi yang lebih tinggi.

hbase.lru.cache.heavy.eviction.mb.size.limit β€” menetapkan berapa banyak megabait yang ingin kami letakkan dalam cache (dan, sudah tentu, buang) dalam 10 saat. Ciri ini akan cuba mencapai nilai ini dan mengekalkannya. Intinya ialah: jika kita memasukkan gigabait ke dalam cache, maka kita perlu mengusir gigabait, dan ini, seperti yang kita lihat di atas, adalah sangat mahal. Walau bagaimanapun, anda tidak boleh cuba menetapkannya terlalu kecil, kerana ini akan menyebabkan mod langkau blok keluar lebih awal. Untuk pelayan berkuasa (kira-kira 20-40 teras fizikal), adalah optimum untuk menetapkan kira-kira 300-400 MB. Untuk kelas pertengahan (~10 teras) 200-300 MB. Untuk sistem lemah (2-5 teras) 50-100 MB mungkin normal (tidak diuji pada ini).

Mari lihat cara ini berfungsi: katakan kita menetapkan hbase.lru.cache.heavy.eviction.mb.size.limit = 500, terdapat beberapa jenis beban (membaca) dan kemudian setiap ~10 saat kita mengira berapa banyak bait diusir menggunakan formula:

Overhed = Jumlah Bait Dibebaskan (MB) * 100 / Had (MB) - 100;

Jika sebenarnya 2000 MB telah diusir, maka Overhed adalah sama dengan:

2000 * 100 / 500 - 100 = 300%

Algoritma cuba mengekalkan tidak lebih daripada beberapa puluh peratus, jadi ciri ini akan mengurangkan peratusan blok cache, dengan itu melaksanakan mekanisme penalaan automatik.

Walau bagaimanapun, jika beban menurun, katakan hanya 200 MB dikeluarkan dan Overhed menjadi negatif (yang dipanggil overshoot):

200 * 100 / 500 - 100 = -60%

Sebaliknya, ciri ini akan meningkatkan peratusan blok cache sehingga Overhed menjadi positif.

Di bawah ialah contoh cara ini kelihatan pada data sebenar. Tak perlu nak cuba capai 0%, mustahil. Ia sangat baik apabila ia adalah kira-kira 30 - 100%, ini membantu untuk mengelakkan keluar pramatang daripada mod pengoptimuman semasa lonjakan jangka pendek.

hbase.lru.cache.heavy.eviction.overhead.coefficient β€” menetapkan seberapa cepat kita ingin mendapatkan hasilnya. Jika kita tahu dengan pasti bahawa bacaan kita kebanyakannya panjang dan tidak mahu menunggu, kita boleh meningkatkan nisbah ini dan mendapatkan prestasi tinggi dengan lebih cepat.

Sebagai contoh, kami menetapkan pekali ini = 0.01. Ini bermakna Overhed (lihat di atas) akan didarab dengan nombor ini dengan hasil yang terhasil dan peratusan blok cache akan dikurangkan. Mari kita andaikan bahawa Overhed = 300% dan pekali = 0.01, maka peratusan blok cache akan dikurangkan sebanyak 3%.

Logik "Tekanan Belakang" yang serupa juga dilaksanakan untuk nilai Overhed negatif (overshoot). Memandangkan turun naik jangka pendek dalam volum bacaan dan pengusiran sentiasa mungkin, mekanisme ini membolehkan anda mengelakkan keluar pramatang daripada mod pengoptimuman. Tekanan belakang mempunyai logik terbalik: lebih kuat penembusan, lebih banyak blok dicache.

Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Kod pelaksanaan

        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 menggunakan contoh sebenar. Kami mempunyai skrip ujian berikut:

  1. Mari mula melakukan Imbasan (25 utas, kelompok = 100)
  2. Selepas 5 minit, tambah multi-dapat (25 utas, kelompok = 100)
  3. Selepas 5 minit, matikan multi-dapat (hanya tinggal imbasan lagi)

Kami melakukan dua larian, pertama hbase.lru.cache.heavy.eviction.count.limit = 10000 (yang sebenarnya melumpuhkan ciri), dan kemudian menetapkan had = 0 (membolehkannya).

Dalam log di bawah kita melihat cara ciri dihidupkan dan menetapkan semula Overshooting kepada 14-71%. Dari semasa ke semasa beban berkurangan, yang menghidupkan Tekanan Belakang dan HBase menyimpan lebih banyak blok lagi.

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

Imbasan diperlukan untuk menunjukkan proses yang sama dalam bentuk graf hubungan antara dua bahagian cache - tunggal (di mana blok yang tidak pernah diminta sebelum ini) dan berbilang (data "diminta" sekurang-kurangnya sekali disimpan di sini):

Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Dan akhirnya, bagaimana rupa operasi parameter dalam bentuk graf. Sebagai perbandingan, cache telah dimatikan sepenuhnya pada permulaan, kemudian HBase dilancarkan dengan caching dan menangguhkan permulaan kerja pengoptimuman selama 5 minit (30 kitaran pengusiran).

Kod penuh boleh didapati dalam Permintaan Tarik HBASE 23887 pada github.

Walau bagaimanapun, 300 ribu bacaan sesaat bukanlah semua yang boleh dicapai pada perkakasan ini dalam keadaan ini. Hakikatnya ialah apabila anda perlu mengakses data melalui HDFS, mekanisme ShortCircuitCache (selepas ini dirujuk sebagai SSC) digunakan, yang membolehkan anda mengakses data secara langsung, mengelakkan interaksi rangkaian.

Pemprofilan menunjukkan bahawa walaupun mekanisme ini memberikan keuntungan yang besar, ia juga pada satu ketika menjadi hambatan, kerana hampir semua operasi berat berlaku di dalam kunci, yang membawa kepada penyekatan pada kebanyakan masa.

Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Setelah menyedari perkara ini, kami menyedari bahawa masalah itu boleh dielakkan dengan mencipta pelbagai SSC bebas:

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

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

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

Sekarang anda boleh mula menguji. Untuk melakukan ini, kami akan membaca fail dari HDFS dengan aplikasi berbilang benang yang mudah. 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 sahaja fail:

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);
}

Kod ini dilaksanakan dalam benang berasingan dan kami akan meningkatkan bilangan fail dibaca secara serentak (dari 10 hingga 200 - paksi mendatar) dan bilangan cache (dari 1 hingga 10 - grafik). Paksi menegak menunjukkan pecutan yang terhasil daripada peningkatan dalam SSC berbanding kes apabila hanya terdapat satu cache.

Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Cara membaca graf: Masa pelaksanaan untuk 100 ribu bacaan dalam blok 64 KB dengan satu cache memerlukan 78 saat. Manakala dengan 5 cache ia mengambil masa 16 saat. Itu. terdapat pecutan ~5 kali. Seperti yang dapat dilihat daripada graf, kesannya tidak begitu ketara untuk sebilangan kecil bacaan selari; ia mula memainkan peranan yang ketara apabila terdapat lebih daripada 50 bacaan benang. Ia juga ketara bahawa peningkatan bilangan SSC daripada 6 dan di atas memberikan peningkatan prestasi yang jauh lebih kecil.

Nota 1: memandangkan keputusan ujian agak tidak menentu (lihat di bawah), 3 larian telah dijalankan dan nilai yang terhasil dipuratakan.

Nota 2: Keuntungan prestasi daripada mengkonfigurasi akses rawak adalah sama, walaupun akses itu sendiri perlahan sedikit.

Walau bagaimanapun, adalah perlu untuk menjelaskan bahawa, tidak seperti kes dengan HBase, pecutan ini tidak selalunya percuma. Di sini kami "membuka kunci" keupayaan CPU untuk melakukan lebih banyak kerja, bukannya bergantung pada kunci.

Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Di sini anda boleh melihat bahawa, secara umum, peningkatan dalam bilangan cache memberikan peningkatan yang lebih kurang berkadar dalam penggunaan CPU. Walau bagaimanapun, terdapat lebih banyak kombinasi yang menang.

Sebagai contoh, mari kita lihat dengan lebih dekat tetapan SSC = 3. Peningkatan prestasi pada julat adalah kira-kira 3.3 kali ganda. Di bawah adalah keputusan daripada ketiga-tiga larian berasingan.

Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Manakala penggunaan CPU meningkat kira-kira 2.8 kali ganda. Perbezaannya tidak terlalu besar, tetapi Greta kecil sudah gembira dan mungkin mempunyai masa untuk menghadiri sekolah dan mengambil pelajaran.

Oleh itu, ini akan memberi kesan positif untuk mana-mana alat yang menggunakan akses pukal kepada HDFS (contohnya Spark, dsb.), dengan syarat kod aplikasi adalah ringan (iaitu palam berada pada bahagian klien HDFS) dan terdapat kuasa CPU percuma . Untuk menyemak, mari kita uji apakah kesan penggunaan gabungan pengoptimuman BlockCache dan penalaan SSC untuk membaca daripada HBase.

Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Ia boleh dilihat bahawa dalam keadaan sedemikian kesannya tidak sehebat dalam ujian halus (membaca tanpa sebarang pemprosesan), tetapi agak mungkin untuk memerah 80K tambahan di sini. Bersama-sama, kedua-dua pengoptimuman memberikan kelajuan sehingga 4x ganda.

PR juga dibuat untuk pengoptimuman ini [HDFS-15202], yang telah digabungkan dan fungsi ini akan tersedia dalam keluaran akan datang.

Dan akhirnya, adalah menarik untuk membandingkan prestasi bacaan pangkalan data lajur lebar yang serupa, Cassandra dan HBase.

Untuk melakukan ini, kami melancarkan contoh utiliti ujian beban YCSB standard daripada dua hos (jumlah 800 utas). Di bahagian pelayan - 4 contoh RegionServer dan Cassandra pada 4 hos (bukan yang dijalankan oleh pelanggan, untuk mengelakkan pengaruh mereka). Bacaan datang dari jadual saiz:

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

Cassandra - 250 GB (faktor replikasi = 3)

Itu. volumnya lebih kurang sama (dalam HBase lebih sedikit).

Parameter HBase:

dfs.client.short.circuit.num = 5 (pengoptimuman pelanggan HDFS)

hbase.lru.cache.heavy.eviction.count.limit = 30 - ini bermakna tampung akan mula berfungsi selepas 30 pengusiran (~5 minit)

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 β€” jumlah sasaran caching dan pengusiran

Log YCSB telah dihuraikan dan disusun ke dalam graf Excel:

Bagaimana untuk meningkatkan kelajuan baca dari HBase sehingga 3 kali dan dari HDFS sehingga 5 kali

Seperti yang anda lihat, pengoptimuman ini membolehkan anda membandingkan prestasi pangkalan data ini di bawah keadaan ini dan mencapai 450 ribu bacaan sesaat.

Kami berharap maklumat ini boleh berguna kepada seseorang semasa perjuangan menarik untuk produktiviti.

Sumber: www.habr.com

Tambah komen