日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

ElasticSearch探索之路(四)索引原理:倒排索引、列式存储、Fielddata、索引压缩、联合索引

發布時間:2024/4/11 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ElasticSearch探索之路(四)索引原理:倒排索引、列式存储、Fielddata、索引压缩、联合索引 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 倒排索引
  • Term dictionary與Term index
  • 列式存儲——Doc Values
  • Fielddata
  • 索引壓縮
    • FOR編碼
    • Roaring Bitmaps
  • 聯合索引


倒排索引

例如,假設我們有兩個文檔,每個文檔的 content 域包含如下內容:

  • The quick brown fox jumped over the lazy dog
  • Quick brown foxes leap over lazy dogs in summer
  • 為了創建倒排索引,首先我們需要借助分詞器,將每個文檔的 content 域拆分成單獨的詞(我們稱它為 詞條 或 tokens、term ),創建一個包含所有不重復詞條的排序列表,然后列出每個詞條出現在哪個文檔。

    Term Doc_1 Doc_2 ------------------------- Quick | | X The | X | brown | X | X dog | X | dogs | | X fox | X | foxes | | X in | | X jumped | X | lazy | X | X leap | | X over | X | X quick | X | summer | | X the | X | ------------------------

    如果我們想搜索 quick brown ,我們只需要查找包含每個詞條的文檔:

    Term Doc_1 Doc_2 ------------------------- brown | X | X quick | X | ------------------------ Total | 2 | 1

    這里我們匹配到了兩個文檔(為了節省空間,這里返回的只是文檔ID,最后再通過文檔id去查詢到具體文檔。)。對于搜索引擎來說,用戶總希望能夠先看到相關度更高的結果,因此實際使用時我們通過一些算法來進行權重計算,將查詢的結果按照權重降序返回。


    Term dictionary與Term index

    Elasticsearch為了能夠快速的在倒排索引中找到某個term,他會按照字典序對所有的term進行排序,再通過二分查找來找到term,這就是Term Dictionary,但即使有了Term Dictionary,O(logN)的磁盤讀寫仍然是影響性能的一大問題。

    B-Tree通過減少磁盤尋道次數來提高查詢性能,Elasticsearch也是采用同樣的思路,直接通過內存查找term,不讀磁盤,但是如果term太多,term dictionary也會很大,放內存不現實,于是有了Term Index,從下圖可以看出,Term Index其實就是一個Trie樹(前綴樹)

    Term index

    這棵樹不會包含所有的term,它包含的是term的一些前綴。通過term index可以快速地定位到term dictionary的某個offset,然后從這個位置再往后順序查找。

    查找流程

    為什么Elasticsearch/Lucene檢索比mysql快呢?

    Mysql只有term dictionary這一層,是以b-tree排序的方式存儲在磁盤上的。檢索一個term需要若干次的random access的磁盤操作。而Lucene在term dictionary的基礎上添加了term index來加速檢索,term index以樹的形式緩存在內存中。從term index查到對應的term dictionary的block位置之后,再去磁盤上找term,大大減少了磁盤的random access次數。


    列式存儲——Doc Values

    Doc values的存在是因為倒排索引只對某些操作是高效的。 倒排索引的優勢在于查找包含某個項的文檔,而對于從另外一個方向的相反操作并不高效,即:確定哪些項是否存在單個文檔里,聚合需要這種次級的訪問模式。

    以排序來舉例——雖然倒排索引的檢索性能非常快,但是在字段值排序時卻不是理想的結構。

    • 在搜索的時候,我們能通過搜索關鍵詞快速得到結果集。
    • 當排序的時候,我們需要倒排索引里面某個字段值的集合。換句話說,我們需要轉置倒排索引。

    轉置 結構經常被稱作 列式存儲 。它將所有單字段的值存儲在單數據列中,這使得對其進行操作是十分高效的,例如排序、聚合等操作。

    在Elasticsearch中,Doc Values就是一種列式存儲結構,在索引時與倒排索引同時生成。也就是說Doc Values和倒排索引一樣,基于 Segement生成并且是不可變的。同時Doc Values和倒排索引一樣序列化到磁盤。

    Doc Values常被應用到以下場景:

    • 對一個字段進行排序
    • 對一個字段進行聚合
    • 某些過濾,比如地理位置過濾
    • 某些與字段相關的腳本計算

    下面舉一個例子,來講講它是如何運作的

    假設存在以下倒排索引

    Term Doc_1 Doc_2 Doc_3 ------------------------------------ brown | X | X | dog | X | | X dogs | | X | X ------------------------------------

    那么其生成的DocValues如下(實際存儲時不會存儲doc_id,值所在的順位即為doc_id)

    Doc_id Values ------------------ Doc_1 | brown | Doc_1 | dog | Doc_2 | brown | Doc_2 | dogs | Doc_3 | dog | Doc_3 | dogs | ------------------

    假設我們需要計算出brown出現的次數

    GET /my_index/_search {"query":{"match":{"body":"brown"}},"aggs":{"popular_terms":{"terms":{"field":"body"}}},"size" : 0 }

    下面來分析上述請求在ES中是如何來進行查詢的:

  • 定位數據范圍。通過倒排索引,來找到所有包含brown的doc_id。
  • 進行聚合計算。借助doc_id在doc_values中定位到為brown的字段,此時進行聚合累加得到計算結果。browm的count=2。

  • 但是doc_values仍存在一些問題,其不支持analyzed類型的字段,因為這些字段在進行文本分析時可能會被分詞處理,從而導致doc_values將其存儲為多行記錄。但是在我們實際使用時,為什么仍然能對analyzed的字段進行聚合操作呢?這時就需要介紹一下Fielddata


    Fielddata

    doc values不生成分析的字符串,那為什么這些字段仍然可以使用聚合呢?是因為使用了fielddata的數據結構。與doc values不同,fielddata構建和管理100%在內存中,常駐于JVM內存堆。

    從歷史上看,fielddata 是所有字段的默認設置。但是Elasticsearch已遷移到doc values以減少 OOM 的幾率。分析的字符串是仍然使用fielddata的最后一塊陣地。 最終目標是建立一個序列化的數據結構類似于doc values ,可以處理高維度的分析字符串,逐步淘汰 fielddata。

    它的一些特性如下

    • 延遲加載。如果你從來沒有聚合一個分析字符串,就不會加載fielddata到內存中,其是在查詢時候構建的。
    • 基于字段加載。 只有很活躍地使用字段才會增加fielddata的負擔。
    • 會加載索引中(針對該特定字段的) 所有的文檔,而不管查詢是否命中。邏輯是這樣:如果查詢會訪問文檔 X、Y 和 Z,那很有可能會在下一個查詢中訪問其他文檔。
    • 如果空間不足,使用最久未使用(LRU)算法移除fielddata。

    因此,在聚合字符串字段之前,請評估情況:

    • 這是一個not_analyzed字段嗎?如果是,可以通過doc values節省內存 。
    • 否則,這是一個analyzed字段,它將使用fielddata并加載到內存中。這個字段因為N-grams有一個非常大的基數?如果是,這對于內存來說極度不友好。


    索引壓縮

    FOR編碼

    在Elasticsearch中,為了能夠更方便的計算交集和并集,它要求倒排索引是有序的,而這個特點同時也帶來了一個額外的好處,我們可以使用增量編碼來壓縮倒排索引,也就是FOR(Frame of Reference)編碼

    增量編碼壓縮,將大數變小數,按字節存儲

    FOR編碼分為三個步驟

    • 增量編碼
    • 增量分區
    • 位壓縮

    如下圖所示,如果我們的倒排索引中存儲的文檔id為[73, 300, 302, 332, 343, 372],那么經過增量編碼后的結果則為[73, 227, 2, 30, 11, 29]。這種壓縮的好處在哪里呢?我們通過增量將原本的大數變成了小數,使得所有的增量都在0~255之間,因此每一個值就只需要使用一個字節就可以存儲,而不會使用int或者bigint,大大的節約了空間。

    接著,第二步我們將這些增量分到不同的區塊中(Lucene底層用了256個區塊,下面為了方便展示之用了兩個)。

    第三步,我們計算出每組數據中最大的那個數所占用的bit位數,例如下圖中區塊1最大的為227,所以只占用8個bit位,所以三個數總共占用3 * 8bits即3字節。而區塊2最大為29,只占用5個bit位,因此這三個數總共占用3 * 5bits即差不多2字節。通過這種方法,將原本6個整數從24字節壓縮到了5字節,效果十分出色。

    ROF編碼實例


    Roaring Bitmaps

    FOR編碼對于倒排索引來說效果很好,但對于需要存儲在內存中的過濾器緩存等不太合適,兩者之間有很多不同之處:

    • 由于我們僅僅緩存那些經常使用的過濾器,因此它的壓縮率并不需要像倒排索引那么高(倒排索引需要對每個詞都進行編碼)。
    • 緩存過濾器的目的就是為了加速處理效率,因此它必須要比重新執行過濾器要快,因此使用一個好的數據結構和算法非常重要。
    • 緩存的過濾器存儲在內存之中,而倒排索引通常存儲在磁盤中。

    基于以上的不同,對于緩存來說FOR編碼并不適用,因此我們還需要考慮其他的一些選擇。

    • 整數數組:數組可能是我們馬上能想到的最簡單的實現方式,我們將文檔id存儲在數組中,這樣就使得我們的迭代變得非常簡單,但是這種方法的內存利用率又十分低下,因為每個文檔都需要4個字節。
    • Bitmaps:在數據分布密集的下,位圖是一個很好的選擇。它本質上就是一個數組,其中每一個文檔id占據一個位,用0和1來標記文檔是否存在。這種方法大大節約了內存,將一個文檔從4字節降低到了一個位,但是一旦數據分布稀疏,此時的位圖性能將大打折扣,因為無論數據量多少,位圖的大小都是由數據的上下區間來決定的。
    • Roaring Bitmaps:Roaring Bitmaps即是對位圖的一種優化,它會根據16位最高位將倒排索引劃分為多塊,如第一個塊將對0到65535之間的值進行編碼,第二個塊將在65536和131071之間進行編碼。在每一個塊中,我們再對低16位進行編碼,如果它的值小于4096在使用數組,否則就使用位圖。由于我們編碼的時候只會對低16位進行編碼,因此在這里數組每個元素只需要2個字節

    Roaring Bitmaps原理

    為什么要使用4096作為數組和位圖選取的閾值呢?

    下面是官方給出的數據報告,在一個塊中只有文檔數量超過4096,位圖的內存優勢才會凸顯出來

    位圖與數組內存利用對比

    這就是Roaring Bitmaps高效率的原因,它基于兩種完全不同的方案來進行編碼,并根據內存效率來動態決定使用哪一種方案。

    官方也給出了幾種方案的性能測試

    迭代性能

    跳過性能——稀疏集

    跳過性能——密集集

    內存占用

    構造時間

    從上述對比可以看出,沒有一種方法是完美的,但是以下兩種方法的巨大劣勢使得它們不會被選擇

    • 數組:性能很好,但是內存占用巨大。
    • Bitmaps:數據稀疏分布的時候內存和性能都會大打折扣。

    因此在綜合考量下,Elasticsearch還是選擇使用Roaring Bitmaps,并且在很多大家了解的開源大數據框架中,也都使用了這一結構,如Hive、Spark、Kylin、Druid等。


    聯合索引

    如果多個字段索引的聯合查詢,倒排索引如何滿足快速查詢的要求呢?

    • 跳表:同時遍歷多個字段的倒排索引,互相skip。
    • 位圖:對多個過濾器分別求出位圖,對這幾個位圖做AND操作。

    Elasticsearch支持以上兩種的聯合索引方式,如果查詢的過濾器緩存到了內存中(以位圖的形式),那么合并就是兩個位圖的AND。如果查詢的過濾器沒有緩存,那么就用跳表的方式去遍歷兩個硬盤中的倒排索引。

    假設有下面三個倒排索引需要聯合索引:

    假設有三個倒排索引
    • 如果使用跳表,則對最短的倒排索引中的每一個id,逐個在另外兩個倒排索引中查看是否存在,來判斷是否存在交集。
    • 如果使用位圖,則直接將幾個位圖按位與運算,最終得到的結果就是最后的交集。

    總結

    以上是生活随笔為你收集整理的ElasticSearch探索之路(四)索引原理:倒排索引、列式存储、Fielddata、索引压缩、联合索引的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。