分區索引過濾器

  隨着DB /內存比變大,過濾器/索引塊的內存佔用變得不重要。
  雖然cache_index_and_filter_blocks允許只將其中的一部分存儲在塊緩存中,但是它們相對較大的尺寸通過以下方式對性能產生負面影響:
  i)佔用可用於緩存數據的塊緩存空間。

  ii)通過將磁盤存儲裝載到錯過之後的緩存。這裏我們更詳細地說明這些問題,並解釋如何劃分索引/過濾器來減輕開銷。


索引/過濾塊有多大?

  RocksDB默認每個SST文件有一個索引/過濾塊。
  索引/過濾器的大小因配置而異,但對於大小爲256MB的SST,大小爲0.5 / 5MB的索引/過濾器塊是典型的,比典型的數據塊大小爲4-32KB要大得多。

  當所有的索引/過濾器完全符合內存,因此在每SST生命週期內讀取一次,而不是與塊緩存空間的數據塊競爭,而且也可能從磁盤多次重新讀取時,這是很好的。


大型索引/過濾器塊有什麼大不了的?

  當索引/過濾塊被存儲在塊緩存中時,它們在這個稀缺資源上有效地與數據塊(以及相互之間)競爭。

  一個大小爲5MB的過濾器佔用了可以用來緩存1000個數據塊(大小爲4KB)的空間。這會導致更多的數據塊緩存未命中。較大的索引/過濾器也會更頻繁地從塊緩存中相互踢出,並且也加劇了它們自己的緩存缺失率。這只是索引/過濾器塊的一小部分可能在緩存中的生命週期中實際使用過。

  索引/過濾器的緩存未命中後,必須從磁盤重新加載,而其大小不利於降低IO成本。雖然簡單的點查找可能最多需要從LSM的每一層讀取一對數據塊讀取(大小爲4KB),但最終也可能會加載多兆字節的索引/過濾塊。如果這種情況經常發生,那麼磁盤會花費更多的時間來服務索引/過濾器,而不是實際的數據塊。


什麼是分區索引/過濾器?

  通過分區,SST文件的索引/過濾器被分區爲更小的塊,並在其上有一個額外的頂級索引。
  當讀取索引/過濾器時,只有頂級索引被加載到內存中。
  分區索引/過濾器然後使用頂級索引按需加載塊緩存執行索引/過濾器查詢所需的分區。

  根據設置,內存佔用空間更小的頂級索引可以存儲在堆或塊緩存中cache_index_and_filter_blocks。

優點:

  更高的緩存命中率:不是用大的索引/塊來污染緩存空間,而是通過分區允許加載更精細的索引/過濾器,從而有效地使用緩存空間。
  減少IO util:在索引/過濾器分區的緩存未命中時,只需要從磁盤加載一個分區,與讀取SST文件的整個索引/過濾器相比,這會導致磁盤負載更輕。
  在索引/過濾器上不妥協:如果不分區,減少索引/過濾器的內存佔用量的替代方法就是犧牲它們的準確性,例如較大的數據塊或較少的bloom位分別具有較小的索引和過濾器。

缺點:

  頂級索引的額外空間:索引/過濾器大小的0.1-1%非常小。
  更多的磁盤IO:如果頂級索引不在緩存中,則會導致另外一個IO。爲了避免它們可以存儲在堆中,或者優先存儲在緩存中(TODO工作)

  丟失空間局部性:如果一個工作負載需要頻繁的,但是從同一個SST文件中隨機讀取,則每次讀取時都會導致加載一個單獨的索引/過濾器分區,這比一次讀取整個索引/過濾器效率低。儘管我們沒有在我們的基準測試中觀察到這種模式,但它只可能發生在LSM的L0 / L1層,可以禁用分區(TODO工作)


如何使用它?

index_type = IndexType::kTwoLevelIndexSearch
這是爲了啓用分區索引
NewBloomFilterPolicy(BITS, false)
使用完整的過濾器
partition_filters = true
這是爲了啓用分區過濾器
metadata_block_size = 4096
這是索引分區的塊大小。
cache_index_and_filter_blocks = false
無論如何,分區都存儲在緩存中。這可以控制頂級索引的位置,這很容易放入內存。將它們存儲在塊緩存中的嘗試較少。
cache_index_and_filter_blocks_with_high_priority = true
推薦設置
pin_l0_filter_and_index_blocks_in_cache = true
建議將此屬性的設置擴展到索引/篩選器分區。
只有在壓實風格是基於級別的情況下才使用它
注意:將塊鎖定到塊緩存中時,如果不設置strict_capacity_limit(這是默認情況),它可能會超出容量。

阻止緩存大小:如果您曾經將過濾器/索引存儲到堆中,請不要忘記使用您從堆中保存的內存量來增加塊緩存大小。

目前的限制

  如果未啓用分區索引,則無法啓用分區過濾器。
  我們有相同數量的過濾器和索引分區。換句話說,每當索引塊被切割時,過濾塊也被切割。如果顯示導致缺陷,我們可能在將來改變它。

  過濾器塊的大小取決於索引塊何時被切割。我們將很快擴展metadata_block_size到在過濾器和索引塊上執行最大尺寸,即,當索引塊被切割或者其大小將要超過metadata_block_size(TODO)時,過濾塊被切割。

BlockBasedTable格式

BlockBasedTable格式與分區的差異是索引塊
[index block]
被存儲爲
[index block - partition 1]
[index block - partition 2]
...
[index block - partition N]
[index block - top-level index]
  SST的頁腳指向頂層索引塊(索引塊本身就是索引分區塊的索引)。每個單獨的索引塊分區符合與kBinarySearch相同的格式。頂級索引格式也符合kBinarySearch的格式,因此可以使用正常的數據塊讀取器讀取。
  類似的結構被用於劃分過濾塊。每個濾波器塊的格式與kFullFilter的格式一致。頂級索引格式與kBinarySearch格式一致,類似於索引塊的頂層索引。

  請注意,通過對SST檢查工具進行分區,sst_dump會報告索引/過濾器上頂級索引的大小,而不是索引/過濾器塊的集體大小。


生成器

   分區索引和過濾器分別由PartitionedIndexBuilder和PartitionedFilterBlockBuilder構建。
  PartitionedIndexBuilder維護sub_index_builder_一個指針來ShortenedIndexBuilder構建當前的索引分區。當確定時flush_policy_,構建器會將指針和索引塊中的最後一個鍵一起保存,並創建一個新的活動ShortenedIndexBuilder。在::Finish構建器上調用::Finish時,它會調用最早的子索引構建器並返回生成的分區塊。接下來的調用PartitionedIndexBuilder::Finish還將包括先前在SST上返回的分區的偏移量,該分區用作頂級索引的值。最後一次調用PartitionedIndexBuilder::Finish將完成頂級索引並返回。在SST上存儲頂層索引之後,其偏移量將被用作索引塊的偏移量。
  PartitionedFilterBlockBuilder從FullFilterBlockBuilder其中繼承了一個FilterBitsBuilder用於構建bloom濾鏡。它還有一個指針PartitionedIndexBuilder並調用ShouldCutFilterBlock它來確定何時應該剪切過濾塊(緊隨在剪切索引塊之後)。要剪切過濾器塊,它將完成FilterBitsBuilder並存儲結果塊以及由其提供的分區鍵PartitionedIndexBuilder::GetPartitionKey(),並重置FilterBitsBuilder下一個分區。每次PartitionedFilterBlockBuilder::Finish調用結束時都會返回一個分區,並且還使用前一個分區的偏移量來構建頂級索引。最後一次調用::Finish將返回頂級索引塊。

  PartitionedFilterBlockBuilder取決於的原因PartitionedIndexBuilder是爲SST文件上的索引/篩選器分區交叉啓用優化。我們可能會在未來減少這種依賴。


讀取

  通過PartitionIndexReader它在頂層索引塊上進行操作來讀取分區索引。當在頂層索引塊上NewIterator調用a TwoLevelIterator時。這個簡單的實現是可行的,因爲每個索引分區都有與數據塊相同格式的kBinarySearch格式,因此可以很容易地將其插入爲較低級別的迭代器。如果pin_l0_filter_and_index_blocks_in_cache設置了較低級別的迭代器,則PartitionIndexReader只要活動,它們的對應索引分區將被鎖定在塊緩存中PartitionIndexReader。BlockEntryIteratorState使用一組固定的分區偏移來避免兩次拆分索引分區。
  PartitionedFilterBlockReader使用頂級索引來查找過濾器分區的偏移量。然後它調用GetFilter上BlockBasedTable對象加載FilterBlockReader上從塊高速緩存過濾器分區對象(或者如果它已不存在加載到所述高速緩存),然後釋放該FilterBlockReader對象。要擴展table_options.pin_l0_filter_and_index_blocks_in_cache到過濾器分區,PartitionedFilterBlockReader不會釋放這些塊的高速緩存句柄(即將它們固定在塊高速緩存中)。而是維護filter_cache_一個固定的map,這個mapFilterBlockReader也被用來在PartitionedFilterBlockReader被破壞時釋放緩存條目。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章