Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

Büyük verilerle çalışırken yüksek performans temel gereksinimlerden biridir. Sberbank'ın veri yükleme bölümünde neredeyse tüm işlemleri Hadoop tabanlı Veri Bulutumuza aktarıyoruz ve bu nedenle gerçekten büyük bilgi akışlarıyla ilgileniyoruz. Doğal olarak, her zaman performansı iyileştirmenin yollarını arıyoruz ve şimdi size RegionServer HBase ve HDFS istemcisine yama yapmayı nasıl başardığımızı anlatmak istiyoruz, bu sayede okuma işlemlerinin hızını önemli ölçüde artırabildik.
Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

Ancak, iyileştirmelerin özüne geçmeden önce, prensip olarak bir HDD'ye oturursanız aşılamayan kısıtlamalardan bahsetmeye değer.

HDD ve hızlı Rastgele Erişim okumaları neden uyumsuz?
Bildiğiniz gibi HBase ve diğer birçok veritabanı, verileri onlarca kilobayt boyutunda bloklar halinde saklar. Varsayılan olarak yaklaşık 64 KB'dir. Şimdi sadece 100 byte almamız gerektiğini düşünelim ve HBase'den bu veriyi belirli bir anahtar kullanarak bize vermesini istiyoruz. HFiles'taki blok boyutu 64 KB olduğundan, istek gerekenden 640 kat (sadece bir dakika!) daha büyük olacaktır.

Daha sonra, istek HDFS'den ve onun meta veri önbelleğe alma mekanizmasından geçeceği için Kısa Devre Önbelleği (dosyalara doğrudan erişime izin verir), bu, diskten zaten 1 MB okumaya yol açar. Ancak bu parametre ile ayarlanabilir. dfs.client.read.shortcircuit.buffer.size ve çoğu durumda bu değeri örneğin 126 KB'a düşürmek mantıklıdır.

Diyelim ki bunu yapıyoruz ama ek olarak Java api üzerinden FileChannel.read gibi fonksiyonlar üzerinden veri okumaya başladığımızda ve işletim sisteminden belirtilen miktardaki veriyi okumasını istediğimizde 2 kat daha “her ihtimale karşı” yazıyor. yani Bizim durumumuzda 256 KB. Bunun nedeni, Java'nın bu davranışı önlemek için FADV_RANDOM bayrağını ayarlamanın kolay bir yolunun bulunmamasıdır.

Sonuç olarak 100 baytımızı elde etmek için kaputun altında 2600 kat daha fazla okunur. Görünüşe göre çözüm açık, blok boyutunu bir kilobyte'a düşürelim, bahsedilen bayrağı ayarlayalım ve büyük bir aydınlanma ivmesi kazanalım. Ancak sorun şu ki, blok boyutunu 2 kat azaltarak birim zaman başına okunan bayt sayısını da 2 kat azaltıyoruz.

FADV_RANDOM bayrağının ayarlanmasıyla bir miktar kazanç elde edilebilir, ancak yalnızca yüksek çoklu iş parçacığı ve 128 KB blok boyutuyla elde edilebilir, ancak bu maksimum yüzde birkaç onluk bir değerdir:

Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

Testler, her biri 100 GB boyutunda ve 1 HDD üzerinde yer alan 10 dosya üzerinde gerçekleştirildi.

Prensip olarak bu hızda neye güvenebileceğimizi hesaplayalım:
Diyelim ki 10 diskten 280 MB/sn hızında okuduk. 3 milyon çarpı 100 bayt. Ama hatırladığımız gibi ihtiyacımız olan veri, okunandan 2600 kat daha az. Böylece 3 milyonu 2600'e böleriz ve şunu elde ederiz: Saniyede 1100 kayıt.

Bunaltıcı, değil mi? Bu doğa Rasgele erişim Blok boyutundan bağımsız olarak HDD'deki verilere erişim. Bu, rastgele erişimin fiziksel sınırıdır ve bu koşullar altında hiçbir veritabanı bundan daha fazlasını sıkıştıramaz.

Peki veritabanları nasıl çok daha yüksek hızlara ulaşıyor? Bu soruyu cevaplamak için aşağıdaki resimde neler olduğuna bakalım:

Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

Burada ilk birkaç dakikadaki hızın aslında saniyede bin kayıt civarında olduğunu görüyoruz. Ancak ayrıca istenenden çok daha fazlası okunduğu için veriler işletim sisteminin (linux) buff/cache'inde kalıyor ve hız saniyede 60 bin gibi daha makul bir seviyeye çıkıyor.

Bu nedenle, ayrıca yalnızca işletim sistemi önbelleğinde bulunan veya karşılaştırılabilir erişim hızına sahip SSD/NVMe depolama cihazlarında bulunan verilere erişimin hızlandırılmasıyla ilgileneceğiz.

Bizim durumumuzda, her biri aşağıdaki şekilde ücretlendirilen 4 sunucudan oluşan bir tezgah üzerinde testler yapacağız:

CPU: Xeon E5-2680 v4 @ 2.40 GHz 64 iş parçacığı.
Bellek: 730 GB.
Java sürümü: 1.8.0_111

Ve burada kilit nokta tablolardaki okunması gereken veri miktarıdır. Gerçek şu ki, eğer tamamen HBase önbelleğine yerleştirilmiş bir tablodan veri okursanız, bu veri işletim sisteminin buff/önbelleğinden okumaya bile gelmeyecektir. Çünkü HBase varsayılan olarak belleğin %40'ını BlockCache adı verilen bir yapıya ayırır. Temel olarak bu, anahtarın dosya adı + bloğun ofseti olduğu ve değerin bu ofsetteki gerçek veriler olduğu bir ConcurrentHashMap'tir.

Böylece sadece bu yapıdan okurken, mükemmel hız görüyoruzsaniyede bir milyon istek gibi. Ancak yüzlerce gigabaytlık belleği yalnızca veritabanı ihtiyaçları için ayıramayacağımızı hayal edelim, çünkü bu sunucularda çalışan birçok başka yararlı şey var.

Örneğin bizim durumumuzda, bir RS'deki BlockCache'in hacmi yaklaşık 12 GB'dir. Bir düğüme iki RS yerleştirdik, yani. Tüm düğümlerde BlockCache için 96 GB ayrılmıştır. Ve birçok kat daha fazla veri var, örneğin, FAST_DIFF tarafından sıkıştırılmış, dosyaların boyutu 4 MB olan, her biri 130 bölge olmak üzere 800 tablo olsun, yani. toplam 410 GB (bu saf veridir, yani çoğaltma faktörü dikkate alınmadan).

Dolayısıyla BlockCache, toplam veri hacminin yalnızca %23'ünü oluşturur ve bu, BigData olarak adlandırılan şeyin gerçek koşullarına çok daha yakındır. Eğlencenin başladığı yer burasıdır; çünkü açıkçası ne kadar az önbellek isabeti olursa performans o kadar kötü olur. Sonuçta, eğer kaçırırsanız, çok fazla iş yapmanız gerekecek - yani. sistem işlevlerini çağırmaya gidin. Ancak bu kaçınılmazdır, o halde tamamen farklı bir konuya bakalım: Önbellekteki verilere ne olur?

Durumu basitleştirelim ve yalnızca 1 nesneye sığacak bir önbelleğimiz olduğunu varsayalım. Önbellekten 3 kat daha büyük bir veri hacmiyle çalışmaya çalıştığımızda ne olacağına dair bir örnek:

1. Blok 1'i önbelleğe yerleştirin
2. Blok 1'i önbellekten kaldırın
3. Blok 2'i önbelleğe yerleştirin
4. Blok 2'i önbellekten kaldırın
5. Blok 3'i önbelleğe yerleştirin

5 eylem tamamlandı! Ancak bu duruma normal denemez, aslında HBase'i tamamen faydasız bir sürü iş yapmaya zorluyoruz. Sürekli olarak işletim sistemi önbelleğindeki verileri okur, BlockCache'e yerleştirir, ancak yeni bir veri bölümü geldiğinde neredeyse anında atılır. Gönderinin başındaki animasyon sorunun özünü gösteriyor - Çöp Toplayıcı ölçeğini aşıyor, atmosfer ısınıyor, uzak ve sıcak İsveç'teki küçük Greta üzülüyor. Ve biz BT çalışanları, çocukların üzgün olmasından gerçekten hoşlanmıyoruz, bu yüzden bu konuda ne yapabileceğimizi düşünmeye başlıyoruz.

Önbelleğin taşmaması için tüm blokları değil, yalnızca belirli bir yüzdesini önbelleğe koyarsanız ne olur? BlockCache'e veri koymak için fonksiyonun başlangıcına sadece birkaç satır kod ekleyerek başlayalım:

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

Burada amaç şudur: ofset, bloğun dosyadaki konumudur ve son rakamları 00'dan 99'a kadar rastgele ve eşit bir şekilde dağıtılmıştır. Bu nedenle yalnızca ihtiyacımız olan aralığa girenleri atlayacağız.

Örneğin, CacheDataBlockPercent = 20 değerini ayarlayın ve ne olacağını görün:

Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

Sonuç ortada. Aşağıdaki grafiklerde, böyle bir hızlanmanın neden meydana geldiği açıkça görülüyor - Sisifos'un önbelleğe veri yerleştirme işini yapmadan ve verileri hemen Marslı köpeklerin kanalizasyonuna atmadan çok sayıda GC kaynağından tasarruf ediyoruz:

Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

Aynı zamanda CPU kullanımı artar ancak üretkenlikten çok daha azdır:

Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

BlockCache'te saklanan blokların farklı olduğunu da belirtmekte fayda var. Çoğu, yaklaşık %95'i verinin kendisidir. Gerisi Bloom filtreleri veya LEAF_INDEX gibi meta verilerdir ve т.д.. Bu veri yeterli değil ama çok faydalıdır, çünkü HBase verilere doğrudan erişmeden önce burada daha fazla arama yapmanın gerekli olup olmadığını ve eğer öyleyse ilgilenilen bloğun tam olarak nerede bulunduğunu anlamak için metaya döner.

Bu nedenle kodda bir kontrol koşulu görüyoruz buf.getBlockType().isData() ve bu meta sayesinde her durumda onu önbellekte bırakacağız.

Şimdi yükü arttıralım ve özelliği tek seferde biraz sıkılaştıralım. İlk testte kesme yüzdesini = 20 yaptık ve BlockCache biraz az kullanıldı. Şimdi bunu %23'e ayarlayalım ve doygunluğun hangi noktada oluştuğunu görmek için her 100 dakikada bir 5 iş parçacığı ekleyelim:

Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

Burada orijinal versiyonun neredeyse anında saniyede yaklaşık 100 bin istekle tavana vurduğunu görüyoruz. Oysa yama 300 bine kadar hızlanma sağlıyor. Aynı zamanda, daha fazla hızlanmanın artık o kadar da "bedava" olmadığı açık; CPU kullanımı da artıyor.

Ancak bu çok zarif bir çözüm değil, çünkü blokların yüzde kaçının önbelleğe alınması gerektiğini önceden bilmiyoruz, yük profiline bağlı. Bu nedenle okuma işlemlerinin etkinliğine bağlı olarak bu parametrenin otomatik olarak ayarlanmasını sağlayacak bir mekanizma hayata geçirilmiştir.

Bunu kontrol etmek için üç seçenek eklendi:

hbase.lru.cache.heavy.eviction.count.limit — optimizasyonu kullanmaya başlamadan önce (yani blokları atlayarak) önbellekten veri çıkarma işleminin kaç kez çalışması gerektiğini ayarlar. Varsayılan olarak MAX_INT = 2147483647'ye eşittir ve aslında özelliğin hiçbir zaman bu değerle çalışmaya başlamayacağı anlamına gelir. Çünkü tahliye işlemi her 5 - 10 saniyede bir başlar (yüke bağlıdır) ve 2147483647*10/60/60/24/365=680 yıl. Ancak bu parametreyi 0 olarak ayarlayıp özelliğin lansmandan hemen sonra çalışmasını sağlayabiliriz.

Ancak bu parametrede de bir payload bulunmaktadır. Eğer yükümüz kısa süreli okumalar (örneğin gün içinde) ve uzun süreli okumalar (gece) sürekli olarak serpiştirilecek şekilde ise, bu durumda özelliğin yalnızca uzun süreli okuma işlemleri yapılırken açıldığından emin olabiliriz.

Örneğin kısa süreli okumaların genellikle 1 dakika kadar sürdüğünü biliyoruz. Blokları atmaya başlamanıza gerek yok, önbelleğin güncelliğini yitirmesi için zamanımız olmayacak ve ardından bu parametreyi örneğin 10'a eşitleyebiliriz. Bu, optimizasyonun yalnızca uzun süre çalışmaya başlayacağı gerçeğine yol açacaktır. Dönem aktif okuma başladı, yani. 100 saniye içinde. Böylece, kısa süreli bir okumamız varsa, tüm bloklar önbelleğe girecek ve kullanılabilir olacaktır (standart algoritma tarafından çıkarılacak olanlar hariç). Ve uzun süreli okumalar yaptığımızda özellik açılıyor ve çok daha yüksek performans elde ediyoruz.

hbase.lru.cache.heavy.eviction.mb.size.limit - 10 saniye içinde önbelleğe kaç megabayt yerleştirmek (ve elbette çıkarmak) istediğimizi ayarlar. Özellik bu değere ulaşmaya ve onu korumaya çalışacaktır. Mesele şu ki: Eğer gigabaytları önbelleğe koyarsak, o zaman gigabaytları çıkarmak zorunda kalacağız ve bu, yukarıda gördüğümüz gibi, çok pahalıdır. Ancak bunu çok küçük ayarlamaya çalışmamalısınız çünkü bu, blok atlama modunun zamanından önce çıkmasına neden olacaktır. Güçlü sunucular için (yaklaşık 20-40 fiziksel çekirdek), yaklaşık 300-400 MB ayarlamak en uygunudur. Orta sınıf için (~10 çekirdek) 200-300 MB. Zayıf sistemler için (2-5 çekirdekli) 50-100 MB normal olabilir (bunlarda test edilmemiştir).

Bunun nasıl çalıştığına bir bakalım: diyelim ki hbase.lru.cache.heavy.eviction.mb.size.limit = 500 ayarladık, bir tür yük (okuma) var ve ardından her ~10 saniyede bir kaç baytın olduğunu hesaplıyoruz formül kullanılarak tahliye edildi:

Genel Yük = Serbest Bayt Toplamı (MB) * 100 / Sınır (MB) - 100;

Aslında 2000 MB tahliye edilmişse, Genel Yük şuna eşittir:

2000 * 100 / 500 - 100 = %300

Algoritmalar yüzde birkaç ondan fazlasını korumamaya çalışır, bu nedenle özellik önbelleğe alınan blokların yüzdesini azaltacak ve böylece bir otomatik ayarlama mekanizması uygulayacaktır.

Ancak yük düşerse, diyelim ki sadece 200 MB boşaltılır ve Overhead negatif olur (sözde aşım):

200 * 100 / 500 - 100 = -%60

Aksine, özellik, Overhead pozitif hale gelene kadar önbelleğe alınan blokların yüzdesini artıracaktır.

Aşağıda bunun gerçek verilerde nasıl göründüğüne dair bir örnek verilmiştir. %0'a ulaşmaya çalışmanıza gerek yok, imkansızdır. Yaklaşık %30 - 100 olması çok iyidir, bu, kısa vadeli dalgalanmalar sırasında optimizasyon modundan erken çıkmanın önlenmesine yardımcı olur.

hbase.lru.cache.heavy.eviction.overhead.katsayısı — sonuca ne kadar çabuk ulaşmak istediğimizi ayarlar. Okumalarımızın çoğunlukla uzun olduğundan emin olursak ve beklemek istemezsek bu oranı arttırıp daha hızlı yüksek performans elde edebiliriz.

Örneğin bu katsayıyı = 0.01 olarak belirledik. Bu, Ek Yükün (yukarı bakın) bu sayı ile elde edilen sonuçla çarpılacağı ve önbelleğe alınan blokların yüzdesinin azaltılacağı anlamına gelir. Overhead = %300 ve katsayı = 0.01 olduğunu varsayalım, bu durumda önbelleğe alınan blokların yüzdesi %3 azalacaktır.

Benzer bir "Geri basınç" mantığı, negatif Tepe (aşırı atış) değerleri için de uygulanır. Okuma ve çıkarma hacminde kısa vadeli dalgalanmalar her zaman mümkün olduğundan, bu mekanizma optimizasyon modundan zamanından önce çıkmanızı önlemenize olanak tanır. Karşı basıncın ters bir mantığı vardır: Aşma ne kadar güçlü olursa, o kadar fazla blok önbelleğe alınır.

Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

Uygulama kodu

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

Şimdi tüm bunlara gerçek bir örnek kullanarak bakalım. Aşağıdaki test komut dosyasına sahibiz:

  1. Tarama yapmaya başlayalım (25 iş parçacığı, toplu iş = 100)
  2. 5 dakika sonra çoklu alımlar ekleyin (25 iş parçacığı, toplu iş = 100)
  3. 5 dakika sonra çoklu almaları kapatın (tekrar yalnızca tarama kalır)

İki çalıştırma yapıyoruz, önce hbase.lru.cache.heavy.eviction.count.limit = 10000 (bu aslında özelliği devre dışı bırakır) ve ardından limit = 0'ı belirler (etkinleştirir).

Aşağıdaki günlüklerde özelliğin nasıl etkinleştirildiğini ve Aşım'ı %14-71'e nasıl sıfırladığını görüyoruz. Zaman zaman yük azalır, bu da Geri Basınç'ı etkinleştirir ve HBase yeniden daha fazla bloğu önbelleğe alır.

Günlük Bölge Sunucusu
tahliye edildi (MB): 0, oran 0.0, ek yük (%): -100, ağır tahliye sayacı: 0, geçerli önbelleğe alma DataBlock (%): 100
tahliye edildi (MB): 0, oran 0.0, ek yük (%): -100, ağır tahliye sayacı: 0, geçerli önbelleğe alma DataBlock (%): 100
tahliye edildi (MB): 2170, oran 1.09, ek yük (%): 985, ağır tahliye sayacı: 1, geçerli önbelleğe alma DataBlock (%): 91 < başlangıç
tahliye edildi (MB): 3763, oran 1.08, genel gider (%): 1781, ağır tahliye sayacı: 2, geçerli önbelleğe alma DataBlock (%): 76
tahliye edildi (MB): 3306, oran 1.07, genel gider (%): 1553, ağır tahliye sayacı: 3, geçerli önbelleğe alma DataBlock (%): 61
tahliye edildi (MB): 2508, oran 1.06, genel gider (%): 1154, ağır tahliye sayacı: 4, geçerli önbelleğe alma DataBlock (%): 50
tahliye edildi (MB): 1824, oran 1.04, genel gider (%): 812, ağır tahliye sayacı: 5, geçerli önbelleğe alma DataBlock (%): 42
tahliye edildi (MB): 1482, oran 1.03, genel gider (%): 641, ağır tahliye sayacı: 6, geçerli önbelleğe alma DataBlock (%): 36
tahliye edildi (MB): 1140, oran 1.01, genel gider (%): 470, ağır tahliye sayacı: 7, geçerli önbelleğe alma DataBlock (%): 32
tahliye edildi (MB): 913, oran 1.0, genel gider (%): 356, ağır tahliye sayacı: 8, geçerli önbelleğe alma DataBlock (%): 29
tahliye edildi (MB): 912, oran 0.89, genel gider (%): 356, ağır tahliye sayacı: 9, geçerli önbelleğe alma DataBlock (%): 26
tahliye edildi (MB): 684, oran 0.76, genel gider (%): 242, ağır tahliye sayacı: 10, geçerli önbelleğe alma DataBlock (%): 24
tahliye edildi (MB): 684, oran 0.61, genel gider (%): 242, ağır tahliye sayacı: 11, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 456, oran 0.51, genel gider (%): 128, ağır tahliye sayacı: 12, geçerli önbelleğe alma DataBlock (%): 21
tahliye edildi (MB): 456, oran 0.42, genel gider (%): 128, ağır tahliye sayacı: 13, geçerli önbelleğe alma DataBlock (%): 20
tahliye edildi (MB): 456, oran 0.33, genel gider (%): 128, ağır tahliye sayacı: 14, geçerli önbelleğe alma DataBlock (%): 19
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 15, geçerli önbelleğe alma DataBlock (%): 19
tahliye edildi (MB): 342, oran 0.32, genel gider (%): 71, ağır tahliye sayacı: 16, geçerli önbelleğe alma DataBlock (%): 19
tahliye edildi (MB): 342, oran 0.31, genel gider (%): 71, ağır tahliye sayacı: 17, geçerli önbelleğe alma DataBlock (%): 19
tahliye edildi (MB): 228, oran 0.3, genel gider (%): 14, ağır tahliye sayacı: 18, geçerli önbelleğe alma DataBlock (%): 19
tahliye edildi (MB): 228, oran 0.29, genel gider (%): 14, ağır tahliye sayacı: 19, geçerli önbelleğe alma DataBlock (%): 19
tahliye edildi (MB): 228, oran 0.27, genel gider (%): 14, ağır tahliye sayacı: 20, geçerli önbelleğe alma DataBlock (%): 19
tahliye edildi (MB): 228, oran 0.25, genel gider (%): 14, ağır tahliye sayacı: 21, geçerli önbelleğe alma DataBlock (%): 19
tahliye edildi (MB): 228, oran 0.24, genel gider (%): 14, ağır tahliye sayacı: 22, geçerli önbelleğe alma DataBlock (%): 19
tahliye edildi (MB): 228, oran 0.22, genel gider (%): 14, ağır tahliye sayacı: 23, geçerli önbelleğe alma DataBlock (%): 19
tahliye edildi (MB): 228, oran 0.21, genel gider (%): 14, ağır tahliye sayacı: 24, geçerli önbelleğe alma DataBlock (%): 19
tahliye edildi (MB): 228, oran 0.2, genel gider (%): 14, ağır tahliye sayacı: 25, geçerli önbelleğe alma DataBlock (%): 19
tahliye edildi (MB): 228, oran 0.17, genel gider (%): 14, ağır tahliye sayacı: 26, geçerli önbelleğe alma DataBlock (%): 19
tahliye edildi (MB): 456, oran 0.17, ek yük (%): 128, ağır tahliye sayacı: 27, geçerli önbelleğe alma DataBlock (%): 18 < eklenenler (ancak tablo aynı)
tahliye edildi (MB): 456, oran 0.15, genel gider (%): 128, ağır tahliye sayacı: 28, geçerli önbelleğe alma DataBlock (%): 17
tahliye edildi (MB): 342, oran 0.13, genel gider (%): 71, ağır tahliye sayacı: 29, geçerli önbelleğe alma DataBlock (%): 17
tahliye edildi (MB): 342, oran 0.11, genel gider (%): 71, ağır tahliye sayacı: 30, geçerli önbelleğe alma DataBlock (%): 17
tahliye edildi (MB): 342, oran 0.09, genel gider (%): 71, ağır tahliye sayacı: 31, geçerli önbelleğe alma DataBlock (%): 17
tahliye edildi (MB): 228, oran 0.08, genel gider (%): 14, ağır tahliye sayacı: 32, geçerli önbelleğe alma DataBlock (%): 17
tahliye edildi (MB): 228, oran 0.07, genel gider (%): 14, ağır tahliye sayacı: 33, geçerli önbelleğe alma DataBlock (%): 17
tahliye edildi (MB): 228, oran 0.06, genel gider (%): 14, ağır tahliye sayacı: 34, geçerli önbelleğe alma DataBlock (%): 17
tahliye edildi (MB): 228, oran 0.05, genel gider (%): 14, ağır tahliye sayacı: 35, geçerli önbelleğe alma DataBlock (%): 17
tahliye edildi (MB): 228, oran 0.05, genel gider (%): 14, ağır tahliye sayacı: 36, geçerli önbelleğe alma DataBlock (%): 17
tahliye edildi (MB): 228, oran 0.04, genel gider (%): 14, ağır tahliye sayacı: 37, geçerli önbelleğe alma DataBlock (%): 17
tahliye edildi (MB): 109, oran 0.04, ek yük (%): -46, ağır tahliye sayacı: 37, geçerli önbelleğe alma DataBlock (%): 22 < karşı basınç
tahliye edildi (MB): 798, oran 0.24, genel gider (%): 299, ağır tahliye sayacı: 38, geçerli önbelleğe alma DataBlock (%): 20
tahliye edildi (MB): 798, oran 0.29, genel gider (%): 299, ağır tahliye sayacı: 39, geçerli önbelleğe alma DataBlock (%): 18
tahliye edildi (MB): 570, oran 0.27, genel gider (%): 185, ağır tahliye sayacı: 40, geçerli önbelleğe alma DataBlock (%): 17
tahliye edildi (MB): 456, oran 0.22, genel gider (%): 128, ağır tahliye sayacı: 41, geçerli önbelleğe alma DataBlock (%): 16
tahliye edildi (MB): 342, oran 0.16, genel gider (%): 71, ağır tahliye sayacı: 42, geçerli önbelleğe alma DataBlock (%): 16
tahliye edildi (MB): 342, oran 0.11, genel gider (%): 71, ağır tahliye sayacı: 43, geçerli önbelleğe alma DataBlock (%): 16
tahliye edildi (MB): 228, oran 0.09, genel gider (%): 14, ağır tahliye sayacı: 44, geçerli önbelleğe alma DataBlock (%): 16
tahliye edildi (MB): 228, oran 0.07, genel gider (%): 14, ağır tahliye sayacı: 45, geçerli önbelleğe alma DataBlock (%): 16
tahliye edildi (MB): 228, oran 0.05, genel gider (%): 14, ağır tahliye sayacı: 46, geçerli önbelleğe alma DataBlock (%): 16
tahliye edildi (MB): 222, oran 0.04, genel gider (%): 11, ağır tahliye sayacı: 47, geçerli önbelleğe alma DataBlock (%): 16
tahliye edildi (MB): 104, oran 0.03, ek yük (%): -48, ağır tahliye sayacı: 47, geçerli önbelleğe alma DataBlock (%): 21 < kesme alır
tahliye edildi (MB): 684, oran 0.2, genel gider (%): 242, ağır tahliye sayacı: 48, geçerli önbelleğe alma DataBlock (%): 19
tahliye edildi (MB): 570, oran 0.23, genel gider (%): 185, ağır tahliye sayacı: 49, geçerli önbelleğe alma DataBlock (%): 18
tahliye edildi (MB): 342, oran 0.22, genel gider (%): 71, ağır tahliye sayacı: 50, geçerli önbelleğe alma DataBlock (%): 18
tahliye edildi (MB): 228, oran 0.21, genel gider (%): 14, ağır tahliye sayacı: 51, geçerli önbelleğe alma DataBlock (%): 18
tahliye edildi (MB): 228, oran 0.2, genel gider (%): 14, ağır tahliye sayacı: 52, geçerli önbelleğe alma DataBlock (%): 18
tahliye edildi (MB): 228, oran 0.18, genel gider (%): 14, ağır tahliye sayacı: 53, geçerli önbelleğe alma DataBlock (%): 18
tahliye edildi (MB): 228, oran 0.16, genel gider (%): 14, ağır tahliye sayacı: 54, geçerli önbelleğe alma DataBlock (%): 18
tahliye edildi (MB): 228, oran 0.14, genel gider (%): 14, ağır tahliye sayacı: 55, geçerli önbelleğe alma DataBlock (%): 18
tahliye edildi (MB): 112, oran 0.14, ek yük (%): -44, ağır tahliye sayacı: 55, geçerli önbelleğe alma DataBlock (%): 23 < karşı basınç
tahliye edildi (MB): 456, oran 0.26, genel gider (%): 128, ağır tahliye sayacı: 56, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.31, genel gider (%): 71, ağır tahliye sayacı: 57, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 58, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 59, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 60, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 61, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 62, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 63, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.32, genel gider (%): 71, ağır tahliye sayacı: 64, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 65, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 66, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.32, genel gider (%): 71, ağır tahliye sayacı: 67, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 68, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.32, genel gider (%): 71, ağır tahliye sayacı: 69, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.32, genel gider (%): 71, ağır tahliye sayacı: 70, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 71, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 72, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 73, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 74, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 75, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 342, oran 0.33, genel gider (%): 71, ağır tahliye sayacı: 76, geçerli önbelleğe alma DataBlock (%): 22
tahliye edildi (MB): 21, oran 0.33, ek yük (%): -90, ağır tahliye sayacı: 76, geçerli önbelleğe alma DataBlock (%): 32
tahliye edildi (MB): 0, oran 0.0, ek yük (%): -100, ağır tahliye sayacı: 0, geçerli önbelleğe alma DataBlock (%): 100
tahliye edildi (MB): 0, oran 0.0, ek yük (%): -100, ağır tahliye sayacı: 0, geçerli önbelleğe alma DataBlock (%): 100

Taramaların, aynı süreci iki önbellek bölümü arasındaki ilişkinin grafiği biçiminde göstermesi gerekiyordu: tekli (daha önce hiç talep edilmemiş bloklar) ve çoklu (en az bir kez "istenen" veriler burada saklanır):

Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

Ve son olarak parametrelerin işleyişi grafik şeklinde nasıl görünüyor? Karşılaştırma için, başlangıçta önbellek tamamen kapatıldı, ardından önbelleğe alma ile HBase başlatıldı ve optimizasyon çalışmasının başlangıcı 5 dakika (30 tahliye döngüsü) geciktirildi.

Kodun tamamını Çekme İsteğinde bulabilirsiniz HBASE 23887 github'da.

Ancak bu koşullar altında bu donanım üzerinde saniyede 300 bin okuma elde edilebilecek tek şey değil. Gerçek şu ki, HDFS aracılığıyla verilere erişmeniz gerektiğinde, ağ etkileşimlerinden kaçınarak verilere doğrudan erişmenizi sağlayan ShortCircuitCache (bundan sonra SSC olarak anılacaktır) mekanizması kullanılır.

Profil oluşturma, bu mekanizmanın büyük bir kazanç sağlamasına rağmen, aynı zamanda bir noktada darboğaz haline geldiğini gösterdi; çünkü neredeyse tüm ağır işlemler bir kilidin içinde gerçekleşir ve bu da çoğu zaman kilitlenmeye yol açar.

Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

Bunu fark ettikten sonra, bir dizi bağımsız SSC oluşturarak sorunun çözülebileceğini fark ettik:

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

Daha sonra son ofset basamağında da kesişimleri hariç tutarak onlarla çalışın:

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

Artık teste başlayabilirsiniz. Bunu yapmak için basit bir çok iş parçacıklı uygulamayla HDFS'den dosyaları okuyacağız. Parametreleri ayarlayın:

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

Ve sadece dosyaları okuyun:

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

Bu kod ayrı iş parçacıklarında yürütülür ve aynı anda okunan dosyaların sayısını (10'dan 200'e - yatay eksen) ve önbellek sayısını (1'den 10'a - grafikler) artıracağız. Dikey eksen, yalnızca bir önbellek olduğu duruma göre SSC'deki artıştan kaynaklanan ivmeyi gösterir.

Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

Grafik nasıl okunur: Tek önbellek ile 100 KB'lık bloklarda 64 bin okumanın yürütme süresi 78 saniye gerektirir. Oysa 5 önbellekle 16 saniye sürer. Onlar. ~5 kat ivme vardır. Grafikten de görülebileceği gibi az sayıdaki paralel okumalarda etki çok fark edilmiyor, 50'den fazla thread okuma olduğunda fark edilebilir bir rol oynamaya başlıyor.SSC sayısının 6'dan artması da dikkat çekiyor. ve üzeri önemli ölçüde daha küçük bir performans artışı sağlar.

Not 1: Test sonuçları oldukça değişken olduğundan (aşağıya bakın), 3 çalışma gerçekleştirildi ve elde edilen değerlerin ortalaması alındı.

Not 2: Erişimin kendisi biraz daha yavaş olsa da, rastgele erişimin yapılandırılmasından elde edilen performans kazancı aynıdır.

Ancak şunu da açıklığa kavuşturmak gerekir ki, HBase'deki durumun aksine bu hızlandırma her zaman bedava değildir. Burada CPU'nun kilitlere bağlı kalmak yerine daha fazla iş yapma yeteneğinin "kilidini açıyoruz".

Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

Burada genel olarak önbellek sayısındaki artışın CPU kullanımında yaklaşık olarak orantılı bir artış sağladığını gözlemleyebilirsiniz. Ancak biraz daha fazla kazanan kombinasyon var.

Örneğin SSC=3 ayarına daha yakından bakalım. Aralıktaki performans artışı yaklaşık 3.3 kat oluyor. Aşağıda üç ayrı çalışmanın sonuçları yer almaktadır.

Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

CPU tüketimi yaklaşık 2.8 kat artarken. Aradaki fark çok büyük değil ama küçük Greta şimdiden mutlu ve okula gitmek ve ders almak için zamanı olabilir.

Bu nedenle, uygulama kodunun hafif olması (yani fişin HDFS istemci tarafında olması) ve boş CPU gücünün olması koşuluyla, HDFS'ye toplu erişim kullanan herhangi bir araç (örneğin Spark vb.) için bu olumlu bir etkiye sahip olacaktır. . Kontrol etmek için, HBase'den okumak için BlockCache optimizasyonu ve SSC ayarının birlikte kullanımının ne gibi bir etkiye sahip olacağını test edelim.

Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

Bu koşullar altında etkinin rafine testlerdeki (herhangi bir işlem yapmadan okuma) kadar büyük olmadığı görülebilir, ancak burada 80K daha sıkıştırmak oldukça mümkündür. Her iki optimizasyon birlikte 4 kata kadar hızlanma sağlar.

Bu optimizasyon için PR de yapıldı [HDFS-15202], birleştirildi ve bu işlevsellik gelecek sürümlerde mevcut olacak.

Ve son olarak, benzer geniş sütunlu bir veritabanı olan Cassandra ve HBase'in okuma performansını karşılaştırmak ilginçti.

Bunu yapmak için, standart YCSB yük testi yardımcı programının örneklerini iki ana bilgisayardan (toplamda 800 iş parçacığı) başlattık. Sunucu tarafında - 4 ana bilgisayarda RegionServer ve Cassandra'nın 4 örneği (etkilerinden kaçınmak için istemcilerin çalıştığı yerler değil). Okumalar boyut tablolarından geldi:

HBase – HDFS'de 300 GB (100 GB saf veri)

Cassandra - 250 GB (çoğaltma faktörü = 3)

Onlar. hacim yaklaşık olarak aynıydı (HBase'de biraz daha fazla).

HBase parametreleri:

dfs.client.short.circuit.num = 5 (HDFS istemci optimizasyonu)

hbase.lru.cache.heavy.eviction.count.limit = 30 - bu, yamanın 30 tahliyeden sonra (~5 dakika) çalışmaya başlayacağı anlamına gelir

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 — hedef önbelleğe alma ve çıkarma hacmi

YCSB günlükleri ayrıştırıldı ve Excel grafikleri halinde derlendi:

Okuma hızı HBase'den 3 kata kadar ve HDFS'den 5 kata kadar nasıl artırılır?

Gördüğünüz gibi bu optimizasyonlar, bu veritabanlarının performansını bu koşullar altında karşılaştırmayı ve saniyede 450 bin okumaya ulaşmayı mümkün kılıyor.

Bu bilgilerin heyecan verici üretkenlik mücadelesi sırasında birilerine faydalı olabileceğini umuyoruz.

Kaynak: habr.com

Yorum ekle