วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

ประสิทธิภาพสูงเป็นหนึ่งในข้อกำหนดสำคัญเมื่อทำงานกับข้อมูลขนาดใหญ่ ในแผนกการโหลดข้อมูลที่ Sberbank เราอัดธุรกรรมเกือบทั้งหมดลงใน Data Cloud ที่ใช้ Hadoop ของเรา และดังนั้นจึงจัดการกับกระแสข้อมูลขนาดใหญ่มาก โดยปกติแล้ว เรามองหาวิธีปรับปรุงประสิทธิภาพอยู่เสมอ และตอนนี้เราต้องการบอกคุณว่าเราจัดการแพตช์ RegionServer HBase และไคลเอนต์ HDFS ได้อย่างไร ซึ่งช่วยให้เราเพิ่มความเร็วในการอ่านได้อย่างมาก
วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

อย่างไรก็ตาม ก่อนที่จะพูดถึงแก่นแท้ของการปรับปรุง เราควรพูดถึงข้อจำกัดที่ตามหลักการแล้วไม่สามารถหลีกเลี่ยงได้หากคุณใช้ HDD

เหตุใดการอ่าน HDD และการเข้าถึงแบบสุ่มอย่างรวดเร็วจึงเข้ากันไม่ได้
ดังที่คุณทราบ HBase และฐานข้อมูลอื่น ๆ จัดเก็บข้อมูลเป็นบล็อกขนาดหลายสิบกิโลไบต์ โดยค่าเริ่มต้นจะอยู่ที่ประมาณ 64 KB ทีนี้ลองจินตนาการว่าเราจำเป็นต้องได้รับเพียง 100 ไบต์และเราขอให้ HBase ให้ข้อมูลนี้แก่เราโดยใช้คีย์บางตัว เนื่องจากขนาดบล็อกใน HFiles คือ 64 KB คำขอจะมีขนาดใหญ่กว่าที่จำเป็นถึง 640 เท่า (เพียงหนึ่งนาที!)

ถัดไป เนื่องจากคำขอจะต้องผ่าน HDFS และกลไกการแคชข้อมูลเมตา ShortCircuitCache (ซึ่งอนุญาตให้เข้าถึงไฟล์ได้โดยตรง) สิ่งนี้นำไปสู่การอ่านจากดิสก์ 1 MB อย่างไรก็ตาม สามารถปรับได้ด้วยพารามิเตอร์ dfs.client.read.shortcircuit.buffer.size และในหลายกรณี ควรลดค่านี้ลง เช่น 126 KB

สมมติว่าเราทำสิ่งนี้ แต่นอกจากนี้ เมื่อเราเริ่มอ่านข้อมูลผ่าน java api เช่น ฟังก์ชันอย่าง FileChannel.read และขอให้ระบบปฏิบัติการอ่านข้อมูลตามจำนวนที่ระบุ ระบบจะอ่านว่า “เผื่อไว้” มากกว่า 2 เท่า , เช่น. 256 KB ในกรณีของเรา เนื่องจาก Java ไม่มีวิธีง่ายๆ ในการตั้งค่าสถานะ FADV_RANDOM เพื่อป้องกันพฤติกรรมนี้

เป็นผลให้เพื่อให้ได้ 100 ไบต์ของเราจะมีการอ่านเพิ่มขึ้น 2600 เท่าภายใต้ประทุน ดูเหมือนว่าวิธีแก้ปัญหาจะชัดเจน ลองลดขนาดบล็อกลงเหลือกิโลไบต์ ตั้งค่าสถานะที่กล่าวถึง และเร่งความเร็วการตรัสรู้ให้ดียิ่งขึ้น แต่ปัญหาก็คือการลดขนาดบล็อกลง 2 เท่า เรายังลดจำนวนไบต์ที่อ่านต่อหน่วยเวลาลง 2 เท่าด้วย

ประโยชน์บางส่วนจากการตั้งค่าสถานะ FADV_RANDOM สามารถรับได้ แต่เฉพาะกับมัลติเธรดที่สูงและมีขนาดบล็อก 128 KB แต่นี่เป็นค่าสูงสุดสองสามสิบเปอร์เซ็นต์:

วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

การทดสอบดำเนินการกับไฟล์ 100 ไฟล์ แต่ละไฟล์มีขนาด 1 GB และอยู่บน HDD 10 ตัว

โดยหลักการแล้ว มาคำนวณสิ่งที่เราสามารถวางใจได้ด้วยความเร็วนี้:
สมมติว่าเราอ่านจากดิสก์ 10 แผ่นด้วยความเร็ว 280 MB/วินาที เช่น 3 ล้านคูณ 100 ไบต์ แต่อย่างที่เราจำได้ ข้อมูลที่เราต้องการนั้นน้อยกว่าข้อมูลที่อ่านถึง 2600 เท่า ดังนั้นเราจึงหาร 3 ล้านด้วย 2600 แล้วได้ 1100 บันทึกต่อวินาที

น่าหดหู่ใช่ไหม? นั่นคือธรรมชาติ การเข้าถึงแบบสุ่ม เข้าถึงข้อมูลบน HDD - โดยไม่คำนึงถึงขนาดบล็อก นี่เป็นขีดจำกัดทางกายภาพของการเข้าถึงแบบสุ่ม และไม่มีฐานข้อมูลใดสามารถบีบอัดออกไปได้มากกว่านี้ภายใต้เงื่อนไขดังกล่าว

ฐานข้อมูลจะมีความเร็วที่สูงกว่ามากได้อย่างไร? เพื่อตอบคำถามนี้ ลองดูสิ่งที่เกิดขึ้นในภาพต่อไปนี้:

วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

ต่อไปนี้เราจะเห็นว่าในช่วงสองสามนาทีแรก ความเร็วจะอยู่ที่ประมาณหนึ่งพันบันทึกต่อวินาทีจริงๆ อย่างไรก็ตาม นอกจากนี้ เนื่องจากความจริงที่ว่ามีการอ่านมากกว่าที่ร้องขอ ข้อมูลจึงจบลงที่บัฟ/แคชของระบบปฏิบัติการ (ลินุกซ์) และความเร็วเพิ่มขึ้นเป็น 60 ต่อวินาทีที่เหมาะสมมากขึ้น

ดังนั้น เราจะจัดการกับการเร่งความเร็วการเข้าถึงเฉพาะข้อมูลที่อยู่ในแคชระบบปฏิบัติการหรืออยู่ในอุปกรณ์จัดเก็บข้อมูล SSD/NVMe ที่มีความเร็วการเข้าถึงที่เทียบเคียงได้

ในกรณีของเรา เราจะทำการทดสอบบนเซิร์ฟเวอร์ 4 เครื่อง ซึ่งแต่ละเซิร์ฟเวอร์จะมีค่าบริการดังนี้:

CPU: Xeon E5-2680 v4 @ 2.40GHz 64 เธรด
หน่วยความจำ: 730GB.
เวอร์ชันจาวา: 1.8.0_111

และจุดสำคัญคือปริมาณข้อมูลในตารางที่ต้องอ่าน ความจริงก็คือ หากคุณอ่านข้อมูลจากตารางที่วางอยู่ในแคช HBase ทั้งหมด ข้อมูลดังกล่าวจะไม่อ่านจากบัฟ/แคชของระบบปฏิบัติการด้วยซ้ำ เนื่องจากโดยค่าเริ่มต้น HBase จะจัดสรรหน่วยความจำ 40% ให้กับโครงสร้างที่เรียกว่า BlockCache โดยพื้นฐานแล้ว นี่คือ ConcurrentHashMap โดยที่คีย์คือชื่อไฟล์ + ออฟเซ็ตของบล็อก และค่าคือข้อมูลจริงที่ออฟเซ็ตนี้

ดังนั้นเมื่ออ่านจากโครงสร้างนี้เท่านั้นเรา เราเห็นความเร็วที่ยอดเยี่ยมเช่น หนึ่งล้านคำขอต่อวินาที แต่ลองจินตนาการว่าเราไม่สามารถจัดสรรหน่วยความจำหลายร้อยกิกะไบต์สำหรับความต้องการของฐานข้อมูลได้ เนื่องจากมีสิ่งต่างๆ ที่มีประโยชน์อื่นๆ อีกมากมายที่ทำงานบนเซิร์ฟเวอร์เหล่านี้

ตัวอย่างเช่น ในกรณีของเรา ปริมาณ BlockCache บน RS หนึ่งตัวจะอยู่ที่ประมาณ 12 GB เราลงจอด RS สองตัวบนโหนดเดียวนั่นคือ 96 GB ได้รับการจัดสรรสำหรับ BlockCache บนโหนดทั้งหมด และมีข้อมูลมากกว่าเดิมหลายเท่า เช่น ปล่อยให้เป็น 4 ตาราง แต่ละตารางมี 130 ภูมิภาค โดยไฟล์มีขนาด 800 MB บีบอัดด้วย FAST_DIFF เช่น รวม 410 GB (นี่คือข้อมูลล้วนๆ กล่าวคือ โดยไม่คำนึงถึงปัจจัยการจำลองแบบ)

ดังนั้น BlockCache จึงเป็นเพียงประมาณ 23% ของปริมาณข้อมูลทั้งหมด และใกล้เคียงกับเงื่อนไขที่แท้จริงของสิ่งที่เรียกว่า BigData มาก และนี่คือจุดเริ่มต้นของความสนุก - เพราะเห็นได้ชัดว่ายิ่งมีการเข้าถึงแคชน้อยลง ประสิทธิภาพก็ยิ่งแย่ลงเท่านั้น ท้ายที่สุด หากคุณพลาด คุณจะต้องทำงานมากมาย เช่น ลงไปที่การเรียกใช้ฟังก์ชันระบบ อย่างไรก็ตาม สิ่งนี้ไม่สามารถหลีกเลี่ยงได้ ดังนั้นเรามาดูแง่มุมที่แตกต่างไปจากเดิมอย่างสิ้นเชิง - จะเกิดอะไรขึ้นกับข้อมูลภายในแคช

มาทำให้สถานการณ์ง่ายขึ้นและสมมติว่าเรามีแคชที่เหมาะกับ 1 อ็อบเจ็กต์เท่านั้น นี่คือตัวอย่างสิ่งที่จะเกิดขึ้นเมื่อเราพยายามทำงานกับปริมาณข้อมูลที่มีขนาดใหญ่กว่าแคช 3 เท่า เราจะต้อง:

1. วางบล็อก 1 ไว้ในแคช
2. ลบบล็อก 1 ออกจากแคช
3. วางบล็อก 2 ไว้ในแคช
4. ลบบล็อก 2 ออกจากแคช
5. วางบล็อก 3 ไว้ในแคช

5 การกระทำเสร็จสิ้นแล้ว! อย่างไรก็ตาม สถานการณ์นี้ไม่สามารถเรียกว่าปกติได้ อันที่จริง เรากำลังบังคับให้ HBase ทำงานที่ไร้ประโยชน์โดยสิ้นเชิง มันจะอ่านข้อมูลจากแคชของระบบปฏิบัติการอย่างต่อเนื่อง และวางไว้ใน BlockCache เพียงเพื่อจะโยนมันออกไปเกือบจะในทันทีเนื่องจากมีข้อมูลส่วนใหม่เข้ามาแล้ว แอนิเมชั่นในตอนต้นของโพสต์แสดงให้เห็นถึงแก่นแท้ของปัญหา - คนเก็บขยะกำลังจะลดขนาดลง บรรยากาศกำลังร้อนขึ้น เกรตาตัวน้อยในสวีเดนที่ห่างไกลและร้อนอบอ้าวกำลังอารมณ์เสีย และพวกเราชาวไอทีไม่ชอบเวลาที่เด็กๆ รู้สึกเศร้า ดังนั้นเราจึงเริ่มคิดว่าเราจะทำอะไรกับเรื่องนี้ได้บ้าง

จะเกิดอะไรขึ้นถ้าคุณไม่ใส่บล็อกทั้งหมดลงในแคช แต่มีเพียงเปอร์เซ็นต์ที่แน่นอนเท่านั้นเพื่อไม่ให้แคชล้น เริ่มต้นด้วยการเพิ่มโค้ดเพียงไม่กี่บรรทัดที่จุดเริ่มต้นของฟังก์ชันเพื่อใส่ข้อมูลลงใน BlockCache:

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

ประเด็นมีดังนี้: offset คือตำแหน่งของบล็อกในไฟล์และตัวเลขสุดท้ายจะถูกกระจายแบบสุ่มและสม่ำเสมอตั้งแต่ 00 ถึง 99 ดังนั้นเราจะข้ามเฉพาะส่วนที่อยู่ในช่วงที่เราต้องการเท่านั้น

ตัวอย่างเช่น ตั้งค่า cacheDataBlockPercent = 20 และดูว่าเกิดอะไรขึ้น:

วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

ผลลัพธ์ก็ชัดเจน ในกราฟด้านล่าง เห็นได้ชัดว่าเหตุใดการเร่งความเร็วจึงเกิดขึ้น - เราประหยัดทรัพยากร GC จำนวนมากโดยไม่ต้องทำงาน Sisyphean ในการวางข้อมูลในแคชเพียงเพื่อโยนมันลงท่อระบายน้ำของสุนัขดาวอังคารทันที:

วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

ในขณะเดียวกัน การใช้งาน CPU จะเพิ่มขึ้น แต่น้อยกว่าประสิทธิภาพการทำงานมาก:

วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

นอกจากนี้ยังเป็นที่น่าสังเกตว่าบล็อกที่จัดเก็บไว้ใน BlockCache นั้นแตกต่างกัน ส่วนใหญ่ประมาณ 95% เป็นข้อมูลนั่นเอง และที่เหลือเป็นข้อมูลเมตา เช่น Bloom filter หรือ LEAF_INDEX และ т.д.. ข้อมูลนี้ไม่เพียงพอ แต่มีประโยชน์มาก เนื่องจากก่อนที่จะเข้าถึงข้อมูลโดยตรง HBase จะหันไปหาเมตาดาต้าเพื่อทำความเข้าใจว่าจำเป็นต้องค้นหาเพิ่มเติมที่นี่หรือไม่ และหากเป็นเช่นนั้น บล็อกที่น่าสนใจนั้นอยู่ที่ใด

ดังนั้นในโค้ดเราจะเห็นเงื่อนไขการตรวจสอบ buf.getBlockType().isData() และด้วยเมตานี้ เราจะทิ้งมันไว้ในแคชไม่ว่าในกรณีใด

ทีนี้มาเพิ่มภาระและกระชับฟีเจอร์เล็กน้อยในคราวเดียว ในการทดสอบครั้งแรก เราได้ตัดเปอร์เซ็นต์ = 20 และ BlockCache มีการใช้งานน้อยเกินไปเล็กน้อย ตอนนี้เรามาตั้งค่าเป็น 23% และเพิ่ม 100 เธรดทุกๆ 5 นาทีเพื่อดูว่าจุดอิ่มตัวเกิดขึ้นที่ใด:

วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

ที่นี่เราจะเห็นว่าเวอร์ชันดั้งเดิมเกือบจะถึงเพดานในทันทีด้วยจำนวนคำขอประมาณ 100 คำขอต่อวินาที ในขณะที่แพทช์ให้อัตราเร่งสูงถึง 300 ในขณะเดียวกัน เป็นที่ชัดเจนว่าการเร่งความเร็วเพิ่มเติมจะไม่ "ฟรี" อีกต่อไป การใช้งาน CPU ก็เพิ่มขึ้นเช่นกัน

อย่างไรก็ตาม นี่ไม่ใช่วิธีแก้ปัญหาที่หรูหรานัก เนื่องจากเราไม่ทราบล่วงหน้าว่าต้องแคชบล็อกกี่เปอร์เซ็นต์ ขึ้นอยู่กับโปรไฟล์โหลด ดังนั้นจึงมีการใช้กลไกเพื่อปรับพารามิเตอร์นี้โดยอัตโนมัติโดยขึ้นอยู่กับกิจกรรมการดำเนินการอ่าน

มีการเพิ่มสามตัวเลือกเพื่อควบคุมสิ่งนี้:

hbase.lru.cache.heavy.eviction.count.limit — กำหนดจำนวนครั้งที่ควรรันกระบวนการลบข้อมูลจากแคชก่อนที่เราจะเริ่มใช้การปรับให้เหมาะสม (เช่น การข้ามบล็อก) โดยค่าเริ่มต้นจะเท่ากับ MAX_INT = 2147483647 และในความเป็นจริงหมายความว่าคุณลักษณะนี้จะไม่เริ่มทำงานกับค่านี้ เพราะกระบวนการขับไล่เริ่มทุกๆ 5 - 10 วินาที (ขึ้นอยู่กับภาระ) และ 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 ปี อย่างไรก็ตาม เราสามารถตั้งค่าพารามิเตอร์นี้เป็น 0 และทำให้ฟีเจอร์นี้ใช้งานได้ทันทีหลังจากเปิดตัว

อย่างไรก็ตาม ยังมีเพย์โหลดในพารามิเตอร์นี้ด้วย หากภาระของเราทำให้การอ่านระยะสั้น (เช่น ในระหว่างวัน) และการอ่านระยะยาว (ในเวลากลางคืน) สลับกันอย่างต่อเนื่อง เราจะมั่นใจได้ว่าคุณลักษณะนี้จะเปิดเฉพาะเมื่อดำเนินการอ่านเป็นเวลานานเท่านั้น

ตัวอย่างเช่น เรารู้ว่าการอ่านค่าระยะสั้นมักจะใช้เวลาประมาณ 1 นาที ไม่จำเป็นต้องเริ่มทิ้งบล็อกแคชจะไม่มีเวลาล้าสมัยและจากนั้นเราสามารถตั้งค่าพารามิเตอร์นี้เท่ากับเช่น 10 สิ่งนี้จะนำไปสู่ความจริงที่ว่าการปรับให้เหมาะสมจะเริ่มทำงานเฉพาะเมื่อนานเท่านั้น การอ่านเชิงรุกได้เริ่มขึ้นแล้ว เช่น ใน 100 วินาที ดังนั้นหากเรามีการอ่านในระยะสั้น บล็อกทั้งหมดจะเข้าไปในแคชและจะพร้อมใช้งาน (ยกเว้นบล็อกที่จะถูกไล่ออกโดยอัลกอริธึมมาตรฐาน) และเมื่อเราอ่านระยะยาว คุณลักษณะนี้จะถูกเปิดใช้งาน และเราจะมีประสิทธิภาพที่สูงขึ้นมาก

hbase.lru.cache.heavy.eviction.mb.size.limit — กำหนดจำนวนเมกะไบต์ที่เราต้องการวางในแคช (และแน่นอนว่าต้องลบออก) ภายใน 10 วินาที คุณลักษณะนี้จะพยายามเข้าถึงค่านี้และรักษาไว้ ประเด็นก็คือ: ถ้าเรายัดกิกะไบต์ลงในแคช เราจะต้องกำจัดกิกะไบต์ออกไป และตามที่เราเห็นข้างต้นมีราคาแพงมาก อย่างไรก็ตาม คุณไม่ควรพยายามตั้งค่าให้เล็กเกินไป เนื่องจากจะทำให้โหมด Block Skip ออกจากระบบก่อนเวลาอันควร สำหรับเซิร์ฟเวอร์ที่มีประสิทธิภาพ (ประมาณ 20-40 ฟิสิคัลคอร์) วิธีที่ดีที่สุดคือตั้งค่าประมาณ 300-400 MB สำหรับชนชั้นกลาง (~ 10 คอร์) 200-300 MB สำหรับระบบที่อ่อนแอ (2-5 คอร์) 50-100 MB อาจเป็นเรื่องปกติ (ไม่ได้ทดสอบกับสิ่งเหล่านี้)

มาดูวิธีการทำงานกัน: สมมติว่าเราตั้งค่า hbase.lru.cache.heavy.eviction.mb.size.limit = 500 มีการโหลดบางประเภท (การอ่าน) จากนั้นทุกๆ ~10 วินาที เราจะคำนวณว่ามีกี่ไบต์ ไล่ออกโดยใช้สูตร:

ค่าโสหุ้ย = ผลรวมไบต์ที่ว่าง (MB) * 100 / ขีดจำกัด (MB) - 100;

หากในความเป็นจริง 2000 MB ถูกไล่ออก ค่าโสหุ้ยจะเท่ากับ:

2000 * 100 / 500 - 100 = 300%

อัลกอริธึมพยายามรักษาไว้ไม่เกินสองสามสิบเปอร์เซ็นต์ ดังนั้นคุณลักษณะนี้จะลดเปอร์เซ็นต์ของบล็อกแคช ดังนั้นจึงใช้กลไกการปรับอัตโนมัติ

อย่างไรก็ตาม หากโหลดลดลง สมมติว่ามีเพียง 200 MB เท่านั้นที่ถูกขับออก และ Overhead กลายเป็นลบ (หรือที่เรียกว่า Overshooting):

200 * 100/500 - 100 = -60%

ในทางตรงกันข้าม คุณลักษณะนี้จะเพิ่มเปอร์เซ็นต์ของบล็อกแคชจนกว่าค่าโสหุ้ยจะกลายเป็นค่าบวก

ด้านล่างนี้คือตัวอย่างลักษณะที่ปรากฏของข้อมูลจริง ไม่จำเป็นต้องพยายามให้ถึง 0% มันเป็นไปไม่ได้ จะดีมากเมื่ออยู่ที่ประมาณ 30 - 100% ซึ่งจะช่วยหลีกเลี่ยงการออกจากโหมดการปรับให้เหมาะสมก่อนเวลาอันควรในช่วงไฟกระชากระยะสั้น

hbase.lru.cache.heavy.eviction.overhead.coefficient — กำหนดความเร็วที่เราต้องการได้ผลลัพธ์ หากเรารู้แน่ว่าการอ่านของเราส่วนใหญ่ยาวและไม่ต้องการรอ เราก็สามารถเพิ่มอัตราส่วนนี้และทำให้ประสิทธิภาพสูงเร็วขึ้นได้

ตัวอย่างเช่น เราตั้งค่าสัมประสิทธิ์นี้ = 0.01 ซึ่งหมายความว่าค่าโสหุ้ย (ดูด้านบน) จะถูกคูณด้วยตัวเลขนี้ด้วยผลลัพธ์ที่ได้ และเปอร์เซ็นต์ของบล็อกที่แคชไว้จะลดลง สมมติว่าค่าโสหุ้ย = 300% และค่าสัมประสิทธิ์ = 0.01 จากนั้นเปอร์เซ็นต์ของบล็อกที่แคชไว้จะลดลง 3%

ตรรกะ "Backpressure" ที่คล้ายกันยังถูกนำมาใช้สำหรับค่าโสหุ้ยที่เป็นลบ (การถ่ายภาพเกิน) เนื่องจากความผันผวนของปริมาณการอ่านและการไล่ออกในระยะสั้นมักเกิดขึ้นได้เสมอ กลไกนี้ช่วยให้คุณหลีกเลี่ยงการออกจากโหมดการปรับให้เหมาะสมก่อนเวลาอันควรได้ Backpressure มีตรรกะแบบกลับด้าน: ยิ่งมีการแก้ไขมากเกินไป บล็อกก็ยิ่งถูกแคชมากขึ้น

วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

รหัสการใช้งาน

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

ตอนนี้เรามาดูทั้งหมดนี้โดยใช้ตัวอย่างจริง เรามีสคริปต์ทดสอบดังต่อไปนี้:

  1. มาเริ่มสแกนกันเถอะ (25 เธรด, แบตช์ = 100)
  2. หลังจากผ่านไป 5 นาที ให้เพิ่ม multi-gets (25 เธรด, แบทช์ = 100)
  3. หลังจากผ่านไป 5 นาที ให้ปิด multi-get (เหลือเพียงการสแกนอีกครั้ง)

เราดำเนินการสองครั้ง ครั้งแรก hbase.lru.cache.heavy.eviction.count.limit = 10000 (ซึ่งจริงๆ แล้วปิดใช้งานคุณลักษณะนี้) จากนั้นตั้งค่าขีดจำกัด = 0 (เปิดใช้งาน)

ในบันทึกด้านล่าง เราจะเห็นว่าฟีเจอร์นี้เปิดอย่างไรและรีเซ็ต Overshooting เป็น 14-71% ในบางครั้งภาระจะลดลง ซึ่งจะเปิด Backpressure และ HBase จะแคชบล็อกเพิ่มเติมอีกครั้ง

บันทึกภูมิภาคเซิร์ฟเวอร์
ขับไล่ (MB): 0, อัตราส่วน 0.0, โอเวอร์เฮด (%): -100, ตัวนับการขับไล่อย่างหนัก: 0, การแคช DataBlock ปัจจุบัน (%): 100
ขับไล่ (MB): 0, อัตราส่วน 0.0, โอเวอร์เฮด (%): -100, ตัวนับการขับไล่อย่างหนัก: 0, การแคช DataBlock ปัจจุบัน (%): 100
ขับไล่ (MB): 2170, อัตราส่วน 1.09, โอเวอร์เฮด (%): 985, ตัวนับการขับไล่อย่างหนัก: 1, DataBlock แคชปัจจุบัน (%): 91 < เริ่มต้น
ขับไล่ (MB): 3763, อัตราส่วน 1.08, โอเวอร์เฮด (%): 1781, ตัวนับการขับไล่อย่างหนัก: 2, DataBlock แคชปัจจุบัน (%): 76
ขับไล่ (MB): 3306, อัตราส่วน 1.07, โอเวอร์เฮด (%): 1553, ตัวนับการขับไล่อย่างหนัก: 3, DataBlock แคชปัจจุบัน (%): 61
ขับไล่ (MB): 2508, อัตราส่วน 1.06, โอเวอร์เฮด (%): 1154, ตัวนับการขับไล่อย่างหนัก: 4, DataBlock แคชปัจจุบัน (%): 50
ขับไล่ (MB): 1824, อัตราส่วน 1.04, โอเวอร์เฮด (%): 812, ตัวนับการขับไล่อย่างหนัก: 5, DataBlock แคชปัจจุบัน (%): 42
ขับไล่ (MB): 1482, อัตราส่วน 1.03, โอเวอร์เฮด (%): 641, ตัวนับการขับไล่อย่างหนัก: 6, DataBlock แคชปัจจุบัน (%): 36
ขับไล่ (MB): 1140, อัตราส่วน 1.01, โอเวอร์เฮด (%): 470, ตัวนับการขับไล่อย่างหนัก: 7, DataBlock แคชปัจจุบัน (%): 32
ขับไล่ (MB): 913, อัตราส่วน 1.0, โอเวอร์เฮด (%): 356, ตัวนับการขับไล่อย่างหนัก: 8, DataBlock แคชปัจจุบัน (%): 29
ขับไล่ (MB): 912, อัตราส่วน 0.89, โอเวอร์เฮด (%): 356, ตัวนับการขับไล่อย่างหนัก: 9, DataBlock แคชปัจจุบัน (%): 26
ขับไล่ (MB): 684, อัตราส่วน 0.76, โอเวอร์เฮด (%): 242, ตัวนับการขับไล่อย่างหนัก: 10, DataBlock แคชปัจจุบัน (%): 24
ขับไล่ (MB): 684, อัตราส่วน 0.61, โอเวอร์เฮด (%): 242, ตัวนับการขับไล่อย่างหนัก: 11, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 456, อัตราส่วน 0.51, โอเวอร์เฮด (%): 128, ตัวนับการขับไล่อย่างหนัก: 12, DataBlock แคชปัจจุบัน (%): 21
ขับไล่ (MB): 456, อัตราส่วน 0.42, โอเวอร์เฮด (%): 128, ตัวนับการขับไล่อย่างหนัก: 13, DataBlock แคชปัจจุบัน (%): 20
ขับไล่ (MB): 456, อัตราส่วน 0.33, โอเวอร์เฮด (%): 128, ตัวนับการขับไล่อย่างหนัก: 14, DataBlock แคชปัจจุบัน (%): 19
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 15, DataBlock แคชปัจจุบัน (%): 19
ขับไล่ (MB): 342, อัตราส่วน 0.32, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 16, DataBlock แคชปัจจุบัน (%): 19
ขับไล่ (MB): 342, อัตราส่วน 0.31, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 17, DataBlock แคชปัจจุบัน (%): 19
ขับไล่ (MB): 228, อัตราส่วน 0.3, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 18, DataBlock แคชปัจจุบัน (%): 19
ขับไล่ (MB): 228, อัตราส่วน 0.29, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 19, DataBlock แคชปัจจุบัน (%): 19
ขับไล่ (MB): 228, อัตราส่วน 0.27, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 20, DataBlock แคชปัจจุบัน (%): 19
ขับไล่ (MB): 228, อัตราส่วน 0.25, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 21, DataBlock แคชปัจจุบัน (%): 19
ขับไล่ (MB): 228, อัตราส่วน 0.24, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 22, DataBlock แคชปัจจุบัน (%): 19
ขับไล่ (MB): 228, อัตราส่วน 0.22, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 23, DataBlock แคชปัจจุบัน (%): 19
ขับไล่ (MB): 228, อัตราส่วน 0.21, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 24, DataBlock แคชปัจจุบัน (%): 19
ขับไล่ (MB): 228, อัตราส่วน 0.2, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 25, DataBlock แคชปัจจุบัน (%): 19
ขับไล่ (MB): 228, อัตราส่วน 0.17, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 26, DataBlock แคชปัจจุบัน (%): 19
ขับไล่ (MB): 456, อัตราส่วน 0.17, โอเวอร์เฮด (%): 128, ตัวนับการขับไล่อย่างหนัก: 27, DataBlock แคชปัจจุบัน (%): 18 < เพิ่มรับ (แต่ตารางเหมือนกัน)
ขับไล่ (MB): 456, อัตราส่วน 0.15, โอเวอร์เฮด (%): 128, ตัวนับการขับไล่อย่างหนัก: 28, DataBlock แคชปัจจุบัน (%): 17
ขับไล่ (MB): 342, อัตราส่วน 0.13, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 29, DataBlock แคชปัจจุบัน (%): 17
ขับไล่ (MB): 342, อัตราส่วน 0.11, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 30, DataBlock แคชปัจจุบัน (%): 17
ขับไล่ (MB): 342, อัตราส่วน 0.09, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 31, DataBlock แคชปัจจุบัน (%): 17
ขับไล่ (MB): 228, อัตราส่วน 0.08, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 32, DataBlock แคชปัจจุบัน (%): 17
ขับไล่ (MB): 228, อัตราส่วน 0.07, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 33, DataBlock แคชปัจจุบัน (%): 17
ขับไล่ (MB): 228, อัตราส่วน 0.06, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 34, DataBlock แคชปัจจุบัน (%): 17
ขับไล่ (MB): 228, อัตราส่วน 0.05, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 35, DataBlock แคชปัจจุบัน (%): 17
ขับไล่ (MB): 228, อัตราส่วน 0.05, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 36, DataBlock แคชปัจจุบัน (%): 17
ขับไล่ (MB): 228, อัตราส่วน 0.04, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 37, DataBlock แคชปัจจุบัน (%): 17
ขับไล่ (MB): 109, อัตราส่วน 0.04, โอเวอร์เฮด (%): -46, ตัวนับการขับไล่หนัก: 37, DataBlock แคชปัจจุบัน (%): 22 < แรงดันย้อนกลับ
ขับไล่ (MB): 798, อัตราส่วน 0.24, โอเวอร์เฮด (%): 299, ตัวนับการขับไล่อย่างหนัก: 38, DataBlock แคชปัจจุบัน (%): 20
ขับไล่ (MB): 798, อัตราส่วน 0.29, โอเวอร์เฮด (%): 299, ตัวนับการขับไล่อย่างหนัก: 39, DataBlock แคชปัจจุบัน (%): 18
ขับไล่ (MB): 570, อัตราส่วน 0.27, โอเวอร์เฮด (%): 185, ตัวนับการขับไล่อย่างหนัก: 40, DataBlock แคชปัจจุบัน (%): 17
ขับไล่ (MB): 456, อัตราส่วน 0.22, โอเวอร์เฮด (%): 128, ตัวนับการขับไล่อย่างหนัก: 41, DataBlock แคชปัจจุบัน (%): 16
ขับไล่ (MB): 342, อัตราส่วน 0.16, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 42, DataBlock แคชปัจจุบัน (%): 16
ขับไล่ (MB): 342, อัตราส่วน 0.11, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 43, DataBlock แคชปัจจุบัน (%): 16
ขับไล่ (MB): 228, อัตราส่วน 0.09, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 44, DataBlock แคชปัจจุบัน (%): 16
ขับไล่ (MB): 228, อัตราส่วน 0.07, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 45, DataBlock แคชปัจจุบัน (%): 16
ขับไล่ (MB): 228, อัตราส่วน 0.05, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 46, DataBlock แคชปัจจุบัน (%): 16
ขับไล่ (MB): 222, อัตราส่วน 0.04, โอเวอร์เฮด (%): 11, ตัวนับการขับไล่อย่างหนัก: 47, DataBlock แคชปัจจุบัน (%): 16
ขับไล่ (MB): 104, อัตราส่วน 0.03, โอเวอร์เฮด (%): -48, ตัวนับการขับไล่อย่างหนัก: 47, การแคช DataBlock ปัจจุบัน (%): 21 < ได้รับการขัดจังหวะ
ขับไล่ (MB): 684, อัตราส่วน 0.2, โอเวอร์เฮด (%): 242, ตัวนับการขับไล่อย่างหนัก: 48, DataBlock แคชปัจจุบัน (%): 19
ขับไล่ (MB): 570, อัตราส่วน 0.23, โอเวอร์เฮด (%): 185, ตัวนับการขับไล่อย่างหนัก: 49, DataBlock แคชปัจจุบัน (%): 18
ขับไล่ (MB): 342, อัตราส่วน 0.22, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 50, DataBlock แคชปัจจุบัน (%): 18
ขับไล่ (MB): 228, อัตราส่วน 0.21, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 51, DataBlock แคชปัจจุบัน (%): 18
ขับไล่ (MB): 228, อัตราส่วน 0.2, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 52, DataBlock แคชปัจจุบัน (%): 18
ขับไล่ (MB): 228, อัตราส่วน 0.18, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 53, DataBlock แคชปัจจุบัน (%): 18
ขับไล่ (MB): 228, อัตราส่วน 0.16, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 54, DataBlock แคชปัจจุบัน (%): 18
ขับไล่ (MB): 228, อัตราส่วน 0.14, โอเวอร์เฮด (%): 14, ตัวนับการขับไล่อย่างหนัก: 55, DataBlock แคชปัจจุบัน (%): 18
ขับไล่ (MB): 112, อัตราส่วน 0.14, โอเวอร์เฮด (%): -44, ตัวนับการขับไล่หนัก: 55, DataBlock แคชปัจจุบัน (%): 23 < แรงดันย้อนกลับ
ขับไล่ (MB): 456, อัตราส่วน 0.26, โอเวอร์เฮด (%): 128, ตัวนับการขับไล่อย่างหนัก: 56, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.31, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 57, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 58, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 59, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 60, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 61, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 62, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 63, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.32, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 64, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 65, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 66, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.32, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 67, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 68, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.32, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 69, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.32, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 70, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 71, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 72, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 73, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 74, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 75, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 342, อัตราส่วน 0.33, โอเวอร์เฮด (%): 71, ตัวนับการขับไล่อย่างหนัก: 76, DataBlock แคชปัจจุบัน (%): 22
ขับไล่ (MB): 21, อัตราส่วน 0.33, โอเวอร์เฮด (%): -90, ตัวนับการขับไล่อย่างหนัก: 76, การแคช DataBlock ปัจจุบัน (%): 32
ขับไล่ (MB): 0, อัตราส่วน 0.0, โอเวอร์เฮด (%): -100, ตัวนับการขับไล่อย่างหนัก: 0, การแคช DataBlock ปัจจุบัน (%): 100
ขับไล่ (MB): 0, อัตราส่วน 0.0, โอเวอร์เฮด (%): -100, ตัวนับการขับไล่อย่างหนัก: 0, การแคช DataBlock ปัจจุบัน (%): 100

จำเป็นต้องมีการสแกนเพื่อแสดงกระบวนการเดียวกันในรูปแบบของกราฟความสัมพันธ์ระหว่างส่วนแคชสองส่วน - ส่วนเดียว (โดยที่บล็อกที่ไม่เคยได้รับการร้องขอมาก่อน) และหลายส่วน (ข้อมูลที่ "ร้องขอ" อย่างน้อยหนึ่งครั้งถูกเก็บไว้ที่นี่):

วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

และสุดท้าย การทำงานของพารามิเตอร์มีลักษณะอย่างไรในรูปของกราฟ สำหรับการเปรียบเทียบ แคชถูกปิดโดยสมบูรณ์ตั้งแต่เริ่มต้น จากนั้น HBase เริ่มทำงานด้วยแคชและชะลอการเริ่มทำงานการปรับให้เหมาะสมเป็นเวลา 5 นาที (รอบการกำจัด 30 รอบ)

รหัสเต็มสามารถพบได้ใน Pull Request HBASE 23887 บน GitHub

อย่างไรก็ตาม การอ่าน 300 ครั้งต่อวินาทีไม่ใช่ทั้งหมดที่สามารถทำได้บนฮาร์ดแวร์นี้ภายใต้เงื่อนไขเหล่านี้ ความจริงก็คือเมื่อคุณต้องการเข้าถึงข้อมูลผ่าน HDFS จะใช้กลไก ShortCircuitCache (ต่อไปนี้จะเรียกว่า SSC) ซึ่งช่วยให้คุณเข้าถึงข้อมูลได้โดยตรง โดยหลีกเลี่ยงการโต้ตอบของเครือข่าย

การทำโปรไฟล์แสดงให้เห็นว่าแม้ว่ากลไกนี้จะให้ประโยชน์อย่างมาก แต่ในบางจุดก็กลายเป็นปัญหาคอขวดด้วย เนื่องจากการทำงานหนักเกือบทั้งหมดเกิดขึ้นภายในล็อค ซึ่งนำไปสู่การบล็อกเกือบตลอดเวลา

วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

เมื่อตระหนักถึงสิ่งนี้ เราก็ตระหนักว่าปัญหาสามารถหลีกเลี่ยงได้โดยการสร้างอาร์เรย์ของ SSC อิสระ:

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

จากนั้นทำงานกับพวกมัน โดยไม่รวมทางแยกที่เลขออฟเซ็ตสุดท้ายด้วย:

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

ตอนนี้คุณสามารถเริ่มการทดสอบได้แล้ว ในการดำเนินการนี้ เราจะอ่านไฟล์จาก HDFS ด้วยแอปพลิเคชันแบบมัลติเธรดที่เรียบง่าย ตั้งค่าพารามิเตอร์:

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

และเพียงอ่านไฟล์:

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

รหัสนี้ดำเนินการในเธรดแยกกันและเราจะเพิ่มจำนวนไฟล์ที่อ่านพร้อมกัน (จาก 10 เป็น 200 - แกนแนวนอน) และจำนวนแคช (จาก 1 ถึง 10 - กราฟิก) แกนแนวตั้งแสดงความเร่งที่เป็นผลมาจากการเพิ่มขึ้นของ SSC ที่สัมพันธ์กับเคสเมื่อมีแคชเพียงอันเดียว

วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

วิธีอ่านกราฟ: เวลาดำเนินการสำหรับการอ่าน 100 ครั้งในบล็อก 64 KB ที่มีแคชเดียวต้องใช้เวลา 78 วินาที ในขณะที่แคช 5 อันใช้เวลา 16 วินาที เหล่านั้น. มีความเร่งประมาณ ~5 เท่า ดังที่เห็นได้จากกราฟ เอฟเฟกต์จะไม่สังเกตเห็นได้ชัดเจนนักสำหรับการอ่านแบบขนานจำนวนเล็กน้อย โดยจะเริ่มมีบทบาทที่เห็นได้ชัดเจนเมื่อมีการอ่านเธรดมากกว่า 50 เธรด นอกจากนี้ยังสังเกตเห็นได้ด้วยว่าการเพิ่มจำนวน SSC จาก 6 และสูงกว่าจะทำให้ประสิทธิภาพเพิ่มขึ้นน้อยลงอย่างมาก

หมายเหตุ 1: เนื่องจากผลการทดสอบค่อนข้างผันผวน (ดูด้านล่าง) จึงดำเนินการ 3 รันและนำค่าผลลัพธ์มาเฉลี่ย

หมายเหตุ 2: ประสิทธิภาพที่ได้รับจากการกำหนดค่าการเข้าถึงแบบสุ่มจะเท่ากัน แม้ว่าการเข้าถึงจะช้ากว่าเล็กน้อยก็ตาม

อย่างไรก็ตาม มีความจำเป็นต้องชี้แจงว่า การเร่งความเร็วนี้ไม่ได้ฟรีเสมอไป ซึ่งต่างจากกรณีของ HBase ที่นี่เรา "ปลดล็อก" ความสามารถของ CPU ในการทำงานมากขึ้น แทนที่จะค้างอยู่กับการล็อค

วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

ที่นี่ คุณจะสังเกตได้ว่า โดยทั่วไป การเพิ่มจำนวนแคชจะทำให้การใช้งาน CPU เพิ่มขึ้นตามสัดส่วนโดยประมาณ อย่างไรก็ตาม มีชุดค่าผสมที่ชนะมากกว่าเล็กน้อย

ตัวอย่างเช่น มาดูการตั้งค่า SSC = 3 กันดีกว่า ประสิทธิภาพที่เพิ่มขึ้นในช่วงนี้คือประมาณ 3.3 เท่า ด้านล่างนี้คือผลลัพธ์จากการวิ่งทั้ง XNUMX แบบแยกกัน

วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

ในขณะที่ปริมาณการใช้ CPU เพิ่มขึ้นประมาณ 2.8 เท่า ความแตกต่างไม่ได้ใหญ่มาก แต่เกรตาตัวน้อยมีความสุขอยู่แล้วและอาจมีเวลาไปโรงเรียนและเรียนบทเรียน

ดังนั้น สิ่งนี้จะมีผลในเชิงบวกสำหรับเครื่องมือใดๆ ที่ใช้การเข้าถึง HDFS จำนวนมาก (เช่น Spark เป็นต้น) โดยมีเงื่อนไขว่าโค้ดของแอปพลิเคชันนั้นมีน้ำหนักเบา (เช่น ปลั๊กอยู่ที่ฝั่งไคลเอ็นต์ HDFS) และมีพลังงาน CPU ว่าง . ในการตรวจสอบ เราจะทดสอบว่าการใช้การเพิ่มประสิทธิภาพ BlockCache ร่วมกันและการปรับแต่ง SSC สำหรับการอ่านจาก HBase จะมีผลกระทบอย่างไร

วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

จะเห็นได้ว่าภายใต้เงื่อนไขดังกล่าวเอฟเฟกต์จะไม่ดีเท่ากับในการทดสอบแบบละเอียด (อ่านโดยไม่ต้องประมวลผลใด ๆ ) แต่ค่อนข้างเป็นไปได้ที่จะบีบเพิ่มอีก 80K ที่นี่ การเพิ่มประสิทธิภาพทั้งสองแบบร่วมกันช่วยเพิ่มความเร็วได้ถึง 4 เท่า

มีการประชาสัมพันธ์เพื่อการเพิ่มประสิทธิภาพนี้ด้วย [HDFS-15202]ซึ่งได้รับการรวมเข้าด้วยกันแล้วและฟังก์ชันนี้จะพร้อมใช้งานในรุ่นต่อๆ ไป

และสุดท้าย การเปรียบเทียบประสิทธิภาพการอ่านของฐานข้อมูลคอลัมน์กว้างที่คล้ายกัน Cassandra และ HBase ก็น่าสนใจ

ในการดำเนินการนี้ เราได้เปิดตัวอินสแตนซ์ของยูทิลิตีการทดสอบโหลด YCSB มาตรฐานจากสองโฮสต์ (รวม 800 เธรด) ทางฝั่งเซิร์ฟเวอร์ - 4 อินสแตนซ์ของ RegionServer และ Cassandra บน 4 โฮสต์ (ไม่ใช่ที่ไคลเอ็นต์กำลังทำงานอยู่ เพื่อหลีกเลี่ยงอิทธิพลของพวกเขา) การอ่านมาจากตารางขนาด:

HBase – 300 GB บน HDFS (ข้อมูลบริสุทธิ์ 100 GB)

Cassandra - 250 GB (ปัจจัยการจำลอง = 3)

เหล่านั้น. ปริมาตรก็ใกล้เคียงกัน (ใน HBase มากกว่าเล็กน้อย)

พารามิเตอร์ HBase:

dfs.client.short.circuit.num = 5 (การเพิ่มประสิทธิภาพไคลเอ็นต์ HDFS)

hbase.lru.cache.heavy.eviction.count.limit = 30 - หมายความว่าแพตช์จะเริ่มทำงานหลังจากการขับไล่ 30 ครั้ง (~ 5 นาที)

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 — ปริมาณเป้าหมายของการแคชและการขับไล่

บันทึก YCSB ถูกแยกวิเคราะห์และรวบรวมเป็นกราฟ Excel:

วิธีเพิ่มความเร็วในการอ่านจาก HBase สูงสุด 3 เท่า และจาก HDFS สูงสุด 5 เท่า

อย่างที่คุณเห็น การปรับให้เหมาะสมเหล่านี้ทำให้สามารถเปรียบเทียบประสิทธิภาพของฐานข้อมูลเหล่านี้ภายใต้เงื่อนไขเหล่านี้ และบรรลุการอ่าน 450 ครั้งต่อวินาที

เราหวังว่าข้อมูลนี้จะเป็นประโยชน์กับใครบางคนในระหว่างการต่อสู้ที่น่าตื่นเต้นเพื่อประสิทธิภาพการทำงาน

ที่มา: will.com

เพิ่มความคิดเห็น