1. 程式人生 > >Elasticsearch儲存目錄結構深入詳解

Elasticsearch儲存目錄結構深入詳解

在本文中,我們將研究Elasticsearch的各個部分寫入資料目錄的檔案。我們將檢視節點,索引和分片級檔案,並簡要說明其內容,以便了解Elasticsearch寫入磁碟的資料。

1、從Elasticsearch路徑說起

Elasticsearch配置了多個路徑:

    path.home:執行Elasticsearch程序的使用者的主目錄。預設為Java系統屬性user.dir,它是程序所有者的預設主目錄。

    path.conf:包含配置檔案的目錄。這通常通過設定Java系統屬性es.config來設定,因為在找到配置檔案之前它必然會被解析。

    path.plugins:子資料夾為Elasticsearch外掛的目錄。這裡支援Sym-links,當從同一個可執行檔案執行多個Elasticsearch例項時,可以使用它來有選擇地啟用/禁用某個Elasticsearch例項的一組外掛。

    path.logs:儲存生成的日誌的位置。如果其中一個卷的磁碟空間不足,則將它放在與資料目錄不同的捲上可能是有意義的。path.data:包含Elasticsearch儲存的資料的資料夾的路徑。

在本文中,我們將仔細研究資料目錄(path.data)的實際內容,並嘗試瞭解所有檔案的用途。
2、檔案從哪裡來?

由於Elasticsearch使用Lucene來處理分片級別的索引和查詢,因此資料目錄中的檔案由Elasticsearch和Lucene寫入。兩者的職責都非常明確:

    Lucene負責寫和維護Lucene索引檔案,

    而Elasticsearch在Lucene之上寫與功能相關的元資料,例如欄位對映,索引設定和其他叢集元資料。 終端使用者和支援功能

    在低階Lucene中不存在,由Elasticsearch提供。

在深入研究並最終找到Lucene索引檔案之前,讓我們看看Elasticsearch編寫的外部級別資料。
3、節點資料

只需從空資料目錄啟動Elasticsearch即可生成以下目錄樹:

    node.lock檔案用於確保一次只能從一個數據目錄讀取/寫入一個Elasticsearch相關安裝資訊。

    有趣的是global-0.st檔案。 global-字首表示這是一個全域性狀態檔案,

    而.st擴充套件名錶示這是一個包含元資料的狀態檔案。您可能已經猜到,此二進位制檔案包含有關您的叢集的全域性元資料,字首後的數字表示叢集元資料版本,遵循跟隨您的叢集嚴格增加的版本控制方案。

    注意:雖然在緊急情況下使用十六進位制編輯器在技術上可以編輯這些檔案,但強烈建議不要這樣做,因為它很快就會導致資料丟失。

4、索引資料

讓我們建立一個分片索引並檢視Elasticsearch更改的檔案。

我們看到已經建立了與索引名稱對應的新目錄。 此目錄有兩個子資料夾:_state和0.

    前者state包含所謂的索引狀態檔案(indices / {index-name} / state / state- {version} .st),其中包含有關索引的元資料,例如 它的建立時間戳。 它還包含唯一識別符號以及索引的設定和對映。

    後者0包含與索引的第一個(也是唯一的)分片相關的資料(分片0)。

接下來,我們將仔細研究一下。
5、分片資料

分片資料目錄包含分片的狀態檔案,其中包括版本控制以及有關分片是主分片還是副本的資訊。

在早期的Elasticsearch版本中,還在分片資料目錄中找到了單獨的{shard_id} / index / _checksums-檔案(和.cks-files)。在當前版本中,這些校驗和現在可以在Lucene檔案的頁尾中找到,因為Lucene已經為其所有索引檔案添加了端到端校驗和。

{shard_id} / index目錄包含Lucene擁有的檔案。 Elasticsearch通常不直接寫入此資料夾(除了早期版本中的舊校驗和實現)。這些目錄中的檔案構成了任何Elasticsearch資料目錄的大小。

在我們進入Lucene的世界之前,我們將看一下Elasticsearch 事務日誌,這在每個分片translog目錄中的字首translog-中存在。Translog日誌對於Elasticsearch的功能和效能非常重要,因此我們將在下一節中更詳細地解釋它的用法。
6、每個分片的 事務日誌(Transaction Log)

Elasticsearch事務日誌確保可以安全地將資料索引到Elasticsearch,而無需為每個文件執行低階Lucene提交。提交Lucene索引會在Lucene級別建立一個新的segment,即執行fsync(),會導致大量磁碟I / O影響效能。

為了接受索引文件並使其可搜尋而不需要完整的Lucene提交,Elasticsearch將其新增到Lucene IndexWriter並將其附加到事務日誌中。在每個refresh_interval之後,它將在Lucene索引上呼叫reopen(),這將使資料可以在不需要提交的情況下進行搜尋。這是Lucene Near Real Time API的一部分。當IndexWriter最終由於自動重新整理事務日誌或由於顯式重新整理操作而提交時,先前的事務日誌將被丟棄並且新的事務日誌將取代它。

如果需要恢復,將首先恢復在Lucene中寫入磁碟的segments,然後重放事務日誌,以防止丟失尚未完全提交到磁碟的操作。
7、Lucene索引檔案

Lucene在記錄Lucene索引目錄中的檔案方面做得很好,為了方便起見,這裡重現了這些檔案(Lucene中的連結文件也詳細介紹了這些檔案從Lucene 2.1返回後所經歷的變化,所以檢查一下 出來):

 
Lucene Index Files
Name     Extension     Brief Description
Segments File     segments_N     Stores information about a commit point
Lock File     write.lock     The Write lock prevents multiple IndexWriters from writing to the same file.
Segment Info     .si     Stores metadata about a segment
Compound File     .cfs, .cfe     An optional “virtual” file consisting of all the other index files for systems that frequently run out of file handles.
Fields     .fnm     Stores information about the fields
Field Index     .fdx     Contains pointers to field data
Field Data     .fdt     The stored fields for documents
Term Dictionary     .tim     The term dictionary, stores term info
Term Index     .tip     The index into the Term Dictionary
Frequencies     .doc     Contains the list of docs which contain each term along with frequency
Positions     .pos     Stores position information about where a term occurs in the index
Payloads     .pay     Stores additional per-position metadata information such as character offsets and user payloads
Norms     .nvd, .nvm     Encodes length and boost factors for docs and fields
Per-Document Values     .dvd, .dvm     Encodes additional scoring factors or other per-document information.
Term Vector Index     .tvx     Stores offset into the document data file
Term Vector Documents     .tvd     Contains information about each document that has term vectors
Term Vector Fields     .tvf     The field level info about term vectors
Live Documents     .liv     Info about what files are live

通常,您還會在Lucene索引目錄中看到一個segments.gen檔案,該檔案是一個幫助檔案,其中包含有關當前/最新segments_N檔案的資訊,並用於可能無法通過目錄列表返回足夠資訊的檔案系統,以確定 最新一代段檔案。在較舊的Lucene版本中,您還可以找到帶有.del字尾的檔案。 它們與Live Documents(.liv)檔案的用途相同 - 換句話說,這些是刪除列表。
8、修復有問題的碎片

由於Elasticsearch分片包含Lucene索引,我們可以使用Lucene的強大的CheckIndex工具(http://t.cn/Rs0gKjCl),這使我們能夠掃描和修復有問題的段,通常只需要很少的資料丟失。 我們通常會建議Elasticsearch使用者簡單地重新索引資料(re-index),但如果由於某種原因這是不可能的並且資料非常重要,那麼這是一條可以採取的路線,即使它需要相當多的手工工作和時間, 取決於碎片的數量和它們的大小。

    Lucene CheckIndex工具包含在預設的Elasticsearch發行版中,無需額外下載。

如果CheckIndex檢測到問題並且其建議修復它看起來很合理,您可以通過新增-fix命令列引數告訴CheckIndex應用修復程式。
9、儲存快照

您可能想知道所有這些檔案如何轉換為快照儲存庫使用的儲存。 不用再想了:拿這個叢集,將它作為我的快照快照到基於檔案系統的閘道器,並檢查儲存庫中的檔案,我們會找到這些檔案(為簡潔起見省略了一些檔案):

在根目錄下,我們有一個索引檔案,其中包含有關此儲存庫中所有快照的資訊,每個快照都有一個關聯的快照和元資料檔案。

根目錄下的快照檔案包含有關快照狀態,快照包含的索引等資訊。 根目錄下的元資料檔案包含快照時的群集元資料。

    當設定compress:true時,使用LZF壓縮元資料和快照檔案,LZF專注於壓縮和解壓縮速度,這使其非常適合Elasticsearch。

    資料儲存有標題:ZV + 1位元組,指示資料是否被壓縮。 在標題之後,格式上將存在一個或多個壓縮的64K塊:2位元組塊長度+2位元組未壓縮大小+壓縮資料。  使用此資訊,您可以使用任何相容LibLZF的解壓縮程式。

在索引級別,還有另一個檔案indices / {index_name} / snapshot- {snapshot_name},其中包含索引元資料,例如快照時索引的設定和對映。

在分片級別,您將找到兩種檔案:重新命名的Lucene索引檔案和分片快照檔案:indices / {index_name} / {shard_id} / snapshot- {snapshot_name}。 此檔案包含有關快照中使用的分片目錄中的哪些檔案的資訊,以及從快照中的邏輯檔名到具體檔名的對映,這些檔名在還原時應儲存為磁碟。 它還包含可用於檢測和防止資料損壞的所有相關檔案的校驗和,Lucene版本控制和大小資訊。.

    您可能想知道為什麼這些檔案已被重新命名而不是僅保留其原始檔名,這可能更容易直接在磁碟上使用。

    原因很簡單:可以在再次快照之前對索引進行快照,刪除並重新建立它。 在這種情況下,幾個檔案最終會有相同的名稱,但內容不同。

10、小結

    在本文中,我們查看了各種級別的Elasticsearch寫入資料目錄的檔案:節點,索引和分片級別。我們已經看到了Lucene索引儲存在磁碟上的位置,並簡要描述瞭如何使用Lucene CheckIndex工具來驗證和修復有問題的碎片。

    希望您不需要對Elasticsearch資料目錄的內容執行任何操作,但是瞭解您最喜歡的基於搜尋的資料庫將哪種資料寫入檔案系統總是有幫助的!

    不需要完整記住每個檔案的確切含義,關鍵的時候知道去哪裡更快的查詢最重要。

11、補充認知

一份資料寫入es會產生多份資料用於不同查詢方式,會比原資料佔用更多磁碟空間。而索引setting裡"codec": "best_compression"是針對_source進行壓縮的,壓縮演算法是deflate壓縮比為6。

對照上面的lucene表進行如下的關聯。

    儲存原文_source 的檔案.fdt .fdm .fdx;

    儲存倒排索引 的檔案.tim .tip .doc;

    用於聚合排序 的列存檔案.dvd .dvm;

    全文檢索檔案.pos .pay .nvd .nvm等。

    載入到記憶體中的檔案有.fdx .tip .dvm,

其中.tip佔用記憶體最大,而.fdt . tim .dvd檔案佔用磁碟最大,例如


 

11.5M    _ap9_1w3.liv 25.0G    _ap9.fdt 31.9M    _ap9.fdx 444K     _ap9.fnm 53.1G    _ap9_Lucene50_0.doc 64.2G    _ap9_Lucene50_0.tim 781M     _ap9_Lucene50_0.tip 87.7G    _ap9_Lucene54_0.dvd 920K     _ap9_Lucene54_0.dvm104.0K    _ap9.si

另外segment較小時檔案內容是儲存在.cfs檔案中,.cfe檔案儲存Lucene各檔案在.cfs檔案的位置資訊,這是為了減少Lucene開啟的檔案控制代碼數。

es節點上shard過多了會導致記憶體不夠,可以對靜態的索引進行

    POST {indexName}/_forcemerge?max_num_segments=1

 
定義

在Lucene中基本的概念包括:index、document、field和term。一個index包含一個documents的序列

    一個document是一個fields的序列
    一個field是一個命名的terms序列
    一個term是一個bytes的序列

在兩個不同fields中的相同bytes序列被認為是不同的term。因此,term表示為一對:命名field的字串,以及field內的bytes。

倒排索引

談到倒排索引,那麼首先看看正排是什麼樣子的呢?假設文件1包含【中文、英文、日文】,文件2包含【英文、日文、韓文】,文件3包含【韓文,中文】那麼根據文件去查詢內容的話

    文件1->【中文、英文、日文】
    文件2->【英文、日文、韓文】
    文件3->【韓文,中文】

反過來,根據內容去查詢文件

    中文->【文件1、文件3】
    英文->【文件1、文件2】
    日文->【文件1、文件2】
    韓文->【文件2、文件3】

就是倒排索引,而Lucene擅長的也正在於此。

Fields的型別

    TextField:索引並分詞,不包含詞向量,多用於文字的正文
    StringField:索引但不分詞,整個String作為單個標記索引,例如可用於“國家名稱”或“ID”等,或者其它任何你想要用來排序的欄位
    IntPoint:int型用於精確/範圍查詢的索引
    LongPoint:long型用於精確/範圍查詢的索引
    FloatPoint:float型用於精確/範圍查詢的索引
    DoublePoint:double型用於精確/範圍查詢的索引
    SortedDocValuesField:儲存每個文件BytesRef值的欄位,索引用於排序,如果需要儲存值,需要再用StoredField例項
    SortedSetDocValuesField:儲存每個文件一組BytesRef值的欄位,索引用於faceting/grouping/joining,如果需要儲存值,需要再用StoredField例項
    NumericDocValuesField:儲存每個文件long值的欄位,用於scoring/sorting/值檢索
    SortedNumericDocValuesField:儲存每個文件一組long值的欄位,用於scoring/sorting/值檢索
    StoredField:用於在彙總結果中檢索的僅儲存值

段(Segments)

Lucene的索引可能是由多個子索引或Segments組成。每個Segment是一個完全獨立地索引,可以單獨用於搜尋。索引涉及

    為新新增的documents建立新的segments
    合併已經存在的segments

搜尋可能涉及多個segments或/和多個索引,每個索引可能由一組segments組成。

文件編號

Lucene通過一個整型的文件編號指向每個文件,第一個被加入索引的文件編號為0,後續加入的文件編號依次遞增。注意文件編號是可能發生變化的,所以在Lucene外部儲存這些值時需要格外小心。
索引結構概述

每個segment索引包括資訊

    Segment info:包含有關segment的元資料,例如文件編號,使用的檔案
    Field names:包含索引中使用的欄位名稱集合
    Stored Field values:對於每個document,它包含屬性-值對的列表,其中屬性是欄位名稱。這些用於儲存有關文件的輔助資訊,例如其標題、url或訪問資料庫的識別符號
    Term dictionary:包含所有文件的所有索引欄位中使用的所有terms的字典。字典還包括包含term的文件編號,以及指向term的頻率和接近度的指標
    Term Frequency data:對於字典中的每個term,包含該term的所有文件的數量以及該term在該文件中的頻率,除非省略頻率(IndexOptions.DOCS)
    Term Proximity data:對於字典中的每個term,term在每個文件中出現的位置。注意,如果所有文件中的所有欄位都省略位置資料,則不會存在
    Normalization factors:對於每個文件中的每個欄位,儲存一個值,該值將乘以該欄位上的匹配的分數
    Term Vectors:對於每個文件中的每個欄位,可以儲存term vector,term vector由term文字和term頻率組成
    Per-document values:與儲存的值類似,這些也以文件編號作為key,但通常旨在被載入到主儲存器中以用於快速訪問。儲存的值通常用於彙總來自搜尋的結果,而每個文件值對於諸如評分因子是有用的
    Live documents:一個可選檔案,指示哪些文件是活動的
    Point values:可選的檔案對,記錄索引欄位尺寸,以實現快速數字範圍過濾和大數值(例如BigInteger、BigDecimal(1D)、地理形狀交集(2D,3D))

檔案命名

屬於一個段的所有檔案具有相同的名稱和不同的副檔名。當使用複合索引檔案,這些檔案(除了段資訊檔案、鎖檔案和已刪除的文件檔案)將壓縮成單個.cfs檔案。當任何索引檔案被儲存到目錄時,它被賦予一個從未被使用過的檔名字。
副檔名摘要
名稱     副檔名     簡短描述
Segments File     segments_N     儲存了一個提交點(a commit point)的資訊
Lock File     write.lock     防止多個IndexWriter同時寫到一份索引檔案中
Segment Info     .si     儲存了索引段的元資料資訊
Compound File     .cfs,.cfe     一個可選的虛擬檔案,把所有索引資訊都儲存到複合索引檔案中
Fields     .fnm     儲存fields的相關資訊
Field Index     .fdx     儲存指向field data的指標
Field Data     .fdt     文件儲存的欄位的值
Term Dictionary     .tim     term詞典,儲存term資訊
Term Index     .tip     到Term Dictionary的索引
Frequencies     .doc     由包含每個term以及頻率的docs列表組成
Positions     .pos     儲存出現在索引中的term的位置資訊
Payloads     .pay     儲存額外的per-position元資料資訊,例如字元偏移和使用者payloads
Norms     .nvd,.nvm     .nvm檔案儲存索引欄位加權因子的元資料,.nvd檔案儲存索引欄位加權資料
Per-Document Values     .dvd,.dvm     .dvm檔案儲存索引文件評分因子的元資料,.dvd檔案儲存索引文件評分資料
Term Vector Index     .tvx     將偏移儲存到文件資料檔案中
Term Vector Documents     .tvd     包含有term vectors的每個文件資訊
Term Vector Fields     .tvf     欄位級別有關term vectors的資訊
Live Documents     .liv     哪些是有效檔案的資訊
Point values     .dii,.dim     保留索引點,如果有的話

鎖檔案

預設情況下,儲存在索引目錄中的鎖檔名為“write.lock”。如果鎖目錄與索引目錄不同,則鎖檔案將命名為“XXXX-write.lock”,其中XXXX是從索引目錄的完整路徑匯出的唯一字首。此鎖檔案確保每次只有一個寫入程式在修改索引。

    翻譯參考:http://t.cn/RsOPjTS

    補充參考:http://t.cn/RsWQAiV

 

    Lucene的索引結構是有層次結構的,主要分以下幾個層次:
     
     
    索引(Index):
    在Lucene中一個索引是放在一個資料夾中的。
    如上圖,同一資料夾中的所有的檔案構成一個Lucene索引。
    段(Segment):
    一個索引可以包含多個段,段與段之間是獨立的,新增新文件可以生成新的段,不同的段可以合併。
    如上圖,具有相同字首檔案的屬同一個段,圖中共三個段 "_0" 和 "_1"和“_2”。
    segments.gen和segments_X是段的元資料檔案,也即它們儲存了段的屬性資訊。
    文件(Document):
    文件是我們建索引的基本單位,不同的文件是儲存在不同的段中的,一個段可以包含多篇文件。
    新新增的文件是單獨儲存在一個新生成的段中,隨著段的合併,不同的文件合併到同一個段中。
    域(Field):
    一篇文件包含不同型別的資訊,可以分開索引,比如標題,時間,正文,作者等,都可以儲存在不同的域裡。
    不同域的索引方式可以不同,在真正解析域的儲存的時候,我們會詳細解讀。
    詞(Term):
    詞是索引的最小單位,是經過詞法分析和語言處理後的字串。
     更多對應的檔案字尾
     
    名稱
    檔案拓展名
    描述
    段檔案
    segments_N    儲存了索引包含的多少段,每個段包含多少文件。
    段元資料
    .si    儲存了索引段的元資料資訊
    鎖檔案
    write.lock    防止多個IndexWriter同時寫到一份索引檔案中。
    複合索引檔案
    .cfs, .cfe    把所有索引資訊都儲存到複合索引檔案中。
    索引段的域資訊
    .fnm
    儲存此段包含的域,以及域的名稱和域的索引型別。
    索引段的文件資訊
    .fdx, .fdt
    儲存此段包含的文件,每篇文件中包含的域以及每個域的資訊。
     
    索引段Term資訊
    .tim, .tip
    .tim檔案中儲存著每個域中Term的統計資訊且儲存著指向.doc, .pos, and .pay 索引檔案的指標。
     
    .tip檔案儲存著Term 字典的索引資訊,可支援隨機訪問。
     
    文件中Term詞頻和跳錶資訊
    .doc
    儲存此段中每個文件對應的Term頻率資訊。
    文件中Term的位置資訊
    .pos
    儲存此段中每個文件對應的Term位置資訊。
    文件的有效載荷和部分位置資訊
    .pay
    儲存此段中每個文件的有效載體(payload) 和 Term的位置資訊(offsets)。 其中有一部分的Term位置資訊儲存在.pos檔案中。
    索引欄位加權因子
    .nvd, .nvm    
    .nvm 檔案儲存索引欄位加權因子的元資料
     
    .nvd 檔案儲存索引欄位加權資料
     
    索引文件加權因子
    .dvd, .dvm
    .dvm 檔案儲存索引文件加權因子的元資料
     
    .dvd 檔案儲存索引文件加權資料
     
    索引向量資料
    .tvx, .tvd, .tvf
    .tvd 儲存此段文件的Term、Term頻率、位置資訊、有效載荷等資訊。
     
    .tvx 索引檔案,用於把特定的文件載入到記憶體。
     
    .tvf 儲存索引欄位的向量資訊。
     
    有效文件
    .liv
    儲存有效文件的索引檔案資訊
    Lucene的索引結構中,即儲存了正向資訊,也儲存了反向資訊。
     
    所謂正向資訊:
     
    按層次儲存了從索引,一直到詞的包含關係:索引(Index) –> 段(segment) –> 文件(Document) –> 域(Field) –> 詞(Term)
    也即此索引包含了那些段,每個段包含了那些文件,每個文件包含了那些域,每個域包含了那些詞。
    既然是層次結構,則每個層次都儲存了本層次的資訊以及下一層次的元資訊,也即屬性資訊,比如一本介紹中國地理的書,應該首先介紹中國地理的概況, 以及中國包含多少個省,每個省介紹本省的基本概況及包含多少個市,每個市介紹本市的基本概況及包含多少個縣,每個縣具體介紹每個縣的具體情況。
    如上圖,包含正向資訊的檔案有:
    segments_N儲存了此索引包含多少個段,每個段包含多少篇文件。
    XXX.fnm儲存了此段包含了多少個域,每個域的名稱及索引方式。
    XXX.fdx,XXX.fdt儲存了此段包含的所有文件,每篇文件包含了多少域,每個域儲存了那些資訊。
    XXX.tvx,XXX.tvd,XXX.tvf儲存了此段包含多少文件,每篇文件包含了多少域,每個域包含了多少詞,每個詞的字串,位置等資訊。
    所謂反向資訊:
     
    儲存了詞典到倒排表的對映:詞(Term) –> 文件(Document)
    如上圖,包含反向資訊的檔案有:
    XXX.tis,XXX.tii儲存了詞典(Term Dictionary),也即此段包含的所有的詞按字典順序的排序。
    XXX.frq儲存了倒排表,也即包含每個詞的文件ID列表。
    XXX.prx儲存了倒排表中每個詞在包含此詞的文件中的位置。
    在瞭解Lucene索引的詳細結構之前,先看看Lucene索引中的基本資料型別。
     
     
    二、基本型別
     
    Lucene索引檔案中,用以下基本型別來儲存資訊:
     
    Byte:是最基本的型別,長8位(bit)。
    UInt32:由4個Byte組成。
    UInt64:由8個Byte組成。
    VInt:
    變長的整數型別,它可能包含多個Byte,對於每個Byte的8位,其中後7位表示數值,最高1位表示是否還有另一個Byte,0表示沒有,1表示有。
    越前面的Byte表示數值的低位,越後面的Byte表示數值的高位。
    例如130化為二進位制為 1000, 0010,總共需要8位,一個Byte表示不了,因而需要兩個Byte來表示,第一個Byte表示後7位,並且在最高位置1來表示後面還有一個Byte, 所以為(1) 0000010,第二個Byte表示第8位,並且最高位置0來表示後面沒有其他的Byte了,所以為(0) 0000001。
     
     
     
     
    Chars:是UTF-8編碼的一系列Byte。
    String:一個字串首先是一個VInt來表示此字串包含的字元的個數,接著便是UTF-8編碼的字元序列Chars。
     
    三、基本規則
     
    Lucene為了使的資訊的儲存佔用的空間更小,訪問速度更快,採取了一些特殊的技巧,然而在看Lucene檔案格式的時候,這些技巧卻容易使我們感到困惑,所以有必要把這些特殊的技巧規則提取出來介紹一下。
     
    在下不才,胡亂給這些規則起了一些名字,是為了方便後面應用這些規則的時候能夠簡單,不妥之處請大家諒解。
     
    1. 字首字尾規則(PREFIX+SUFFIX)
     
    Lucene在反向索引中,要儲存詞典(Term Dictionary)的資訊,所有的詞(Term)在詞典中是按照字典順序進行排列的,然而詞典中包含了文件中的幾乎所有的詞,並且有的詞還是非常的長 的,這樣索引檔案會非常的大,所謂字首字尾規則,即當某個詞和前一個詞有共同的字首的時候,後面的詞僅僅儲存字首在詞中的偏移(offset),以及除前 綴以外的字串(稱為字尾)。
     
    [圖]字首字尾規則
     
     
     
    比如要儲存如下詞:term,termagancy,termagant,terminal,
     
    如果按照正常方式來儲存,需要的空間如下:
     
    [VInt = 4] [t][e][r][m],[VInt = 10][t][e][r][m][a][g][a][n][c][y],[VInt = 9][t][e][r][m][a][g][a][n][t],[VInt = 8][t][e][r][m][i][n][a][l]
     
    共需要35個Byte.
     
    如果應用字首字尾規則,需要的空間如下:
     
    [VInt = 4] [t][e][r][m],[VInt = 4 (offset)][VInt = 6][a][g][a][n][c][y],[VInt = 8 (offset)][VInt = 1][t],[VInt = 4(offset)][VInt = 4][i][n][a][l]
     
    共需要22個Byte。
     
    大大縮小了儲存空間,尤其是在按字典順序排序的情況下,字首的重合率大大提高。
     
    2. 差值規則(DELTA)
     
    在Lucene的反向索引中,需要儲存很多整型數字的資訊,比如文件ID號,比如詞(Term)在文件中的位置等等。
     
    由上面介紹,我們知道,整型數字是以VInt的格式儲存的。隨著數值的增大,每個數字佔用的Byte的個數也逐漸的增多。所謂差值規則(Delta)就是先後儲存兩個整數的時候,後面的整數僅僅儲存和前面整數的差即可。
     
    [圖]差值規則
     
     
     
    比如要儲存如下整數:16386,16387,16388,16389
     
    如果按照正常方式來儲存,需要的空間如下:
     
    [(1) 000, 0010][(1) 000, 0000][(0) 000, 0001],[(1) 000, 0011][(1) 000, 0000][(0) 000, 0001],[(1) 000, 0100][(1) 000, 0000][(0) 000, 0001],[(1) 000, 0101][(1) 000, 0000][(0) 000, 0001]
     
    供需12個Byte。
     
    如果應用差值規則來儲存,需要的空間如下:
     
    [(1) 000, 0010][(1) 000, 0000][(0) 000, 0001],[(0) 000, 0001],[(0) 000, 0001],[(0) 000, 0001]
     
    共需6個Byte。
     
    大大縮小了儲存空間,而且無論是文件ID,還是詞在文件中的位置,都是按從小到大的順序,逐漸增大的。
     
    3. 或然跟隨規則(A, B?)
     
    Lucene的索引結構中存在這樣的情況,某個值A後面可能存在某個值B,也可能不存在,需要一個標誌來表示後面是否跟隨著B。
     
    一般的情況下,在A後面放置一個Byte,為0則後面不存在B,為1則後面存在B,或者0則後面存在B,1則後面不存在B。
     
    但這樣要浪費一個Byte的空間,其實一個Bit就可以了。
     
    在Lucene中,採取以下的方式:A的值左移一位,空出最後一位,作為標誌位,來表示後面是否跟隨B,所以在這種情況下,A/2是真正的A原來的值。
     
     
    如果去讀Apache Lucene - Index File Formats這篇文章,會發現很多符合這種規則的:
     
    .frq檔案中的DocDelta[, Freq?],DocSkip,PayloadLength?
    .prx檔案中的PositionDelta,Payload? (但不完全是,如下表分析)
    當然還有一些帶?的但不屬於此規則的:
     
    .frq檔案中的SkipChildLevelPointer?,是多層跳躍表中,指向下一層表的指標,當然如果是最後一層,此值就不存在,也不需要標誌。
    .tvf檔案中的Positions?, Offsets?。
    在此類情況下,帶?的值是否存在,並不取決於前面的值的最後一位。
    而是取決於Lucene的某項配置,當然這些配置也是儲存在Lucene索引檔案中的。
    如Position和Offset是否儲存,取決於.fnm檔案中對於每個域的配置(TermVector.WITH_POSITIONS和TermVector.WITH_OFFSETS)
    為什麼會存在以上兩種情況,其實是可以理解的:
     
    對於符合或然跟隨規則的,是因為對於每一個A,B是否存在都不相同,當這種情況大量存在的時候,從一個Byte到一個Bit如此8倍的空間節約還是很值得的。
    對於不符合或然跟隨規則的,是因為某個值的是否存在的配置對於整個域(Field)甚至整個索引都是有效的,而非每次的情況都不相同,因而可以統一存放一個標誌。
    文章中對如下格式的描述令人困惑:
    Positions -->  Freq
    Payload -->
    PositionDelta和Payload是否適用或然跟隨規則呢?如何標識PayloadLength是否存在呢?
    其實PositionDelta和Payload並不符合或然跟隨規則,Payload是否存在,是由.fnm檔案中對於每個域的配置中有關Payload的配置決定的(FieldOption.STORES_PAYLOADS) 。
    當Payload不存在時,PayloadDelta本身不遵從或然跟隨原則。
    當Payload存在時,格式應該變成如下:Positions -->  Freq
    從而PositionDelta和PayloadLength一起適用或然跟隨規則。
     
    4. 跳躍表規則(SKIP LIST)  
     
    為了提高查詢的效能,Lucene在很多地方採取的跳躍表的資料結構。
     
    跳躍表(Skip List)是如圖的一種資料結構,有以下幾個基本特徵:
     
    元素是按順序排列的,在Lucene中,或是按字典順序排列,或是按從小到大順序排列。
    跳躍是有間隔的(Interval),也即每次跳躍的元素數,間隔是事先配置好的,如圖跳躍表的間隔為3。
    跳躍表是由層次的(level),每一層的每隔指定間隔的元素構成上一層,如圖跳躍表共有2層。
     
     
    需要注意一點的是,在很多資料結構或演算法書中都會有跳躍表的描述,原理都是大致相同的,但是定義稍有差別:
     
    對間隔(Interval)的定義: 如圖中,有的認為間隔為2,即兩個上層元素之間的元素數,不包括兩個上層元素;有的認為是3,即兩個上層元素之間的差,包括後面上層元素,不包括前面的上 層元素;有的認為是4,即除兩個上層元素之間的元素外,既包括前面,也包括後面的上層元素。Lucene是採取的第二種定義。
    對層次(Level)的定義:如圖中,有的認為應該包括原連結串列層,並從1開始計數,則總層次為3,為1,2,3層;有的認為應該包括原連結串列層,並 從0計數,為0,1,2層;有的認為不應該包括原連結串列層,且從1開始計數,則為1,2層;有的認為不應該包括連結串列層,且從0開始計數,則為0,1層。 Lucene採取的是最後一種定義。
    跳躍表比順序查詢,大大提高了查詢速度,如查詢元素72,原來要訪問2,3,7,12,23,37,39,44,50,72總共10個元素,應用跳 躍表後,只要首先訪問第1層的50,發現72大於50,而第1層無下一個節點,然後訪問第2層的94,發現94大於72,然後訪問原連結串列的72,找到元 素,共需要訪問3個元素即可。
     
    然而Lucene在具體實現上,與理論又有所不同,在具體的格式中,會詳細說明。

推薦閱讀: