Elasticsearch中數據是如何存儲的

前言

不少使用Elasticsearch的同窗會關心數據存儲在ES中的存儲容量,會有這樣的疑問:xxTB的數據入到ES會使用多少存儲空間。這個問題其實很難直接回答的,只有數據寫入ES後,才能觀察到實際的存儲空間。好比一樣是1TB的數據,寫入ES的存儲空間可能差距會很是大,可能小到只有300~400GB,也可能多到6-7TB,爲何會形成這麼大的差距呢?究其緣由,咱們來探究下Elasticsearch中的數據是如何存儲。文章中我以Elasticsearch 2.3版本爲示例,對應的lucene版本是5.5,Elasticsearch如今已經來到了6.5版本,數字類型、列存等存儲結構有些變化,但基本的概念變化很少,文章中的內容依然適用。html

Elasticsearch索引結構

Elasticsearch對外提供的是index的概念,能夠類比爲DB,用戶查詢是在index上完成的,每一個index由若干個shard組成,以此來達到分佈式可擴展的能力。好比下圖是一個由10個shard組成的index。git


elasticsearch_store_arc.png


shard是Elasticsearch數據存儲的最小單位,index的存儲容量爲全部shard的存儲容量之和。Elasticsearch集羣的存儲容量則爲全部index存儲容量之和。github

一個shard就對應了一個lucene的library。對於一個shard,Elasticsearch增長了translog的功能,相似於HBase WAL,是數據寫入過程當中的中間數據,其他的數據都在lucene庫中管理的。算法

因此Elasticsearch索引使用的存儲內容主要取決於lucene中的數據存儲。apache

lucene數據存儲

下面咱們主要看下lucene的文件內容,在瞭解lucene文件內容前,你們先了解些lucene的基本概念。json

lucene基本概念

  • segment : lucene內部的數據是由一個個segment組成的,寫入lucene的數據並不直接落盤,而是先寫在內存中,通過了refresh間隔,lucene纔將該時間段寫入的所有數據refresh成一個segment,segment多了以後會進行merge成更大的segment。lucene查詢時會遍歷每一個segment完成。因爲lucene* 寫入的數據是在內存中完成,因此寫入效率很是高。可是也存在丟失數據的風險,因此Elasticsearch基於此現象實現了translog,只有在segment數據落盤後,Elasticsearch纔會刪除對應的translog。數組

  • doc : doc表示lucene中的一條記錄app

  • field :field表示記錄中的字段概念,一個doc由若干個field組成。less

  • term :term是lucene中索引的最小單位,某個field對應的內容若是是全文檢索類型,會將內容進行分詞,分詞的結果就是由term組成的。若是是不分詞的字段,那麼該字段的內容就是一個term。dom

  • 倒排索引(inverted index): lucene索引的通用叫法,即實現了term到doc list的映射。

  • 正排數據:搜索引擎的通用叫法,即原始數據,能夠理解爲一個doc list。

  • docvalues :Elasticsearch中的列式存儲的名稱,Elasticsearch除了存儲原始存儲、倒排索引,還存儲了一份docvalues,用做分析和排序。

lucene文件內容

lucene包的文件是由不少segment文件組成的,segments_xxx文件記錄了lucene包下面的segment文件數量。每一個segment會包含以下的文件。

Name Extension Brief Description
Segment Info .si segment的元數據文件
Compound File .cfs, .cfe 一個segment包含了以下表的各個文件,爲減小打開文件的數量,在segment小的時候,segment的全部文件內容都保存在cfs文件中,cfe文件保存了lucene各文件在cfs文件的位置信息
Fields .fnm 保存了fields的相關信息
Field Index .fdx 正排存儲文件的元數據信息
Field Data .fdt 存儲了正排存儲數據,寫入的原文存儲在這
Term Dictionary .tim 倒排索引的元數據信息
Term Index .tip 倒排索引文件,存儲了全部的倒排索引數據
Frequencies .doc 保存了每一個term的doc id列表和term在doc中的詞頻
Positions .pos Stores position information about where a term occurs in the index
全文索引的字段,會有該文件,保存了term在doc中的位置
Payloads .pay Stores additional per-position metadata information such as character offsets and user payloads
全文索引的字段,使用了一些像payloads的高級特性會有該文件,保存了term在doc中的一些高級特性
Norms .nvd, .nvm 文件保存索引字段加權數據
Per-Document Values .dvd, .dvm lucene的docvalues文件,即數據的列式存儲,用做聚合和排序
Term Vector Data .tvx, .tvd, .tvf Stores offset into the document data file
保存索引字段的矢量信息,用在對term進行高亮,計算文本相關性中使用
Live Documents .liv 記錄了segment中刪除的doc

測試數據示例

下面咱們以真實的數據做爲示例,看看lucene中各種型數據的容量佔比。

寫100w數據,有一個uuid字段,寫入的是長度爲36位的uuid,字符串總爲3600w字節,約爲35M。

數據使用一個shard,不帶副本,使用默認的壓縮算法,寫入完成後merge成一個segment方便觀察。

使用線上默認的配置,uuid存爲不分詞的字符串類型。建立以下索引:

PUT test_field
{
  "settings": {
    "index": {
      "number_of_shards": "1",
      "number_of_replicas": "0",
      "refresh_interval": "30s"
    }
  },
  "mappings": {
    "type": {
      "_all": {
        "enabled": false
      }, 
      "properties": {
        "uuid": {
          "type": "string",
          "index": "not_analyzed"
        }
      }
    }
  }
}

首先寫入100w不一樣的uuid,使用磁盤容量細節以下:

health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0    122.7mb        122.7mb 

-rw-r--r--  1 weizijun  staff    41M Aug 19 21:23 _8.fdt
-rw-r--r--  1 weizijun  staff    17K Aug 19 21:23 _8.fdx
-rw-r--r--  1 weizijun  staff   688B Aug 19 21:23 _8.fnm
-rw-r--r--  1 weizijun  staff   494B Aug 19 21:23 _8.si
-rw-r--r--  1 weizijun  staff   265K Aug 19 21:23 _8_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff    44M Aug 19 21:23 _8_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff   340K Aug 19 21:23 _8_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff    37M Aug 19 21:23 _8_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   254B Aug 19 21:23 _8_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   195B Aug 19 21:23 segments_2
-rw-r--r--  1 weizijun  staff     0B Aug 19 21:20 write.lock

能夠看到正排數據、倒排索引數據,列存數據容量佔比幾乎相同,正排數據和倒排數據還會存儲Elasticsearch的惟一id字段,因此容量會比列存多一些。

35M的uuid存入Elasticsearch後,數據膨脹了3倍,達到了122.7mb。Elasticsearch居然這麼消耗資源,不要着急下結論,接下來看另外一個測試結果。

咱們寫入100w同樣的uuid,而後看看Elasticsearch使用的容量。

health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0     13.2mb         13.2mb 

-rw-r--r--  1 weizijun  staff   5.5M Aug 19 21:29 _6.fdt
-rw-r--r--  1 weizijun  staff    15K Aug 19 21:29 _6.fdx
-rw-r--r--  1 weizijun  staff   688B Aug 19 21:29 _6.fnm
-rw-r--r--  1 weizijun  staff   494B Aug 19 21:29 _6.si
-rw-r--r--  1 weizijun  staff   309K Aug 19 21:29 _6_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff   7.0M Aug 19 21:29 _6_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff   195K Aug 19 21:29 _6_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff   244K Aug 19 21:29 _6_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   252B Aug 19 21:29 _6_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   195B Aug 19 21:29 segments_2
-rw-r--r--  1 weizijun  staff     0B Aug 19 21:26 write.lock

這回35M的數據Elasticsearch容量只有13.2mb,其中還有主要的佔比仍是Elasticsearch的惟一id,100w的uuid幾乎不佔存儲容積。

因此在Elasticsearch中創建索引的字段若是基數越大(count distinct),越佔用磁盤空間。

咱們再看看存100w個不同的整型會是如何。

health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0     13.6mb         13.6mb 

-rw-r--r--  1 weizijun  staff   6.1M Aug 28 10:19 _42.fdt
-rw-r--r--  1 weizijun  staff    22K Aug 28 10:19 _42.fdx
-rw-r--r--  1 weizijun  staff   688B Aug 28 10:19 _42.fnm
-rw-r--r--  1 weizijun  staff   503B Aug 28 10:19 _42.si
-rw-r--r--  1 weizijun  staff   2.8M Aug 28 10:19 _42_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff   2.2M Aug 28 10:19 _42_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff    83K Aug 28 10:19 _42_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff   2.5M Aug 28 10:19 _42_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   228B Aug 28 10:19 _42_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   196B Aug 28 10:19 segments_2
-rw-r--r--  1 weizijun  staff     0B Aug 28 10:16 write.lock

從結果能夠看到,100w整型數據,Elasticsearch的存儲開銷爲13.6mb。若是以int型計算100w數據的長度的話,爲400w字節,大概是3.8mb數據。忽略Elasticsearch惟一id字段的影響,Elasticsearch實際存儲容量跟整型數據長度差很少。

咱們再看一下開啓最佳壓縮參數對存儲空間的影響:

health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0    107.2mb        107.2mb 

-rw-r--r--  1 weizijun  staff    25M Aug 20 12:30 _5.fdt
-rw-r--r--  1 weizijun  staff   6.0K Aug 20 12:30 _5.fdx
-rw-r--r--  1 weizijun  staff   688B Aug 20 12:31 _5.fnm
-rw-r--r--  1 weizijun  staff   500B Aug 20 12:31 _5.si
-rw-r--r--  1 weizijun  staff   265K Aug 20 12:31 _5_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff    44M Aug 20 12:31 _5_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff   322K Aug 20 12:31 _5_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff    37M Aug 20 12:31 _5_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   254B Aug 20 12:31 _5_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   224B Aug 20 12:31 segments_4
-rw-r--r--  1 weizijun  staff     0B Aug 20 12:00 write.lock

結果中能夠發現,只有正排數據會啓動壓縮,壓縮能力確實強勁,不考慮惟一id字段,存儲容量大概壓縮到接近50%。

咱們還作了一些實驗,Elasticsearch默認是開啓_all參數的,_all可讓用戶傳入的總體json數據做爲全文檢索的字段,能夠更方便的檢索,但在現實場景中已經使用的很少,相反會增長不少存儲容量的開銷,能夠看下開啓_all的磁盤空間使用狀況:

health status index      pri rep docs.count docs.deleted store.size pri.store.size 
green  open   test_field   1   0    1000000            0    162.4mb        162.4mb 

-rw-r--r--  1 weizijun  staff    41M Aug 18 22:59 _20.fdt
-rw-r--r--  1 weizijun  staff    18K Aug 18 22:59 _20.fdx
-rw-r--r--  1 weizijun  staff   777B Aug 18 22:59 _20.fnm
-rw-r--r--  1 weizijun  staff    59B Aug 18 22:59 _20.nvd
-rw-r--r--  1 weizijun  staff    78B Aug 18 22:59 _20.nvm
-rw-r--r--  1 weizijun  staff   539B Aug 18 22:59 _20.si
-rw-r--r--  1 weizijun  staff   7.2M Aug 18 22:59 _20_Lucene50_0.doc
-rw-r--r--  1 weizijun  staff   4.2M Aug 18 22:59 _20_Lucene50_0.pos
-rw-r--r--  1 weizijun  staff    73M Aug 18 22:59 _20_Lucene50_0.tim
-rw-r--r--  1 weizijun  staff   832K Aug 18 22:59 _20_Lucene50_0.tip
-rw-r--r--  1 weizijun  staff    37M Aug 18 22:59 _20_Lucene54_0.dvd
-rw-r--r--  1 weizijun  staff   254B Aug 18 22:59 _20_Lucene54_0.dvm
-rw-r--r--  1 weizijun  staff   196B Aug 18 22:59 segments_2
-rw-r--r--  1 weizijun  staff     0B Aug 18 22:53 write.lock

開啓_all比不開啓多了40mb的存儲空間,多的數據都在倒排索引上,大約會增長30%多的存儲開銷。因此線上都直接禁用。

而後我還作了其餘幾個嘗試,爲了驗證存儲容量是否和數據量成正比,寫入1000w數據的uuid,發現存儲容量基本爲100w數據的10倍。我還驗證了數據長度是否和數據量成正比,發現把uuid增加2倍、4倍,存儲容量也響應的增長了2倍和4倍。在此就不一一列出數據了。

lucene各文件具體內容和實現

lucene數據元信息文件

文件名爲:segments_xxx

該文件爲lucene數據文件的元信息文件,記錄全部segment的元數據信息。

該文件主要記錄了目前有多少segment,每一個segment有一些基本信息,更新這些信息定位到每一個segment的元信息文件。

lucene元信息文件還支持記錄userData,Elasticsearch能夠在此記錄translog的一些相關信息。

文件示例


elasticsearch_store_segments.png


具體實現類

public final class SegmentInfos implements Cloneable, Iterable<SegmentCommitInfo> {
  // generation是segment的版本的概念,從文件名中提取出來,實例中爲:2t/101
  private long generation;     // generation of the "segments_N" for the next commit

  private long lastGeneration; // generation of the "segments_N" file we last successfully read
                               // or wrote; this is normally the same as generation except if
                               // there was an IOException that had interrupted a commit

  /** Id for this commit; only written starting with Lucene 5.0 */
  private byte[] id;

  /** Which Lucene version wrote this commit, or null if this commit is pre-5.3. */
  private Version luceneVersion;

  /** Counts how often the index has been changed.  */
  public long version;

  /** Used to name new segments. */
  // TODO: should this be a long ...?
  public int counter;

  /** Version of the oldest segment in the index, or null if there are no segments. */
  private Version minSegmentLuceneVersion;

  private List<SegmentCommitInfo> segments = new ArrayList<>();

  /** Opaque Map&lt;String, String&gt; that user can specify during IndexWriter.commit */
  public Map<String,String> userData = Collections.emptyMap();
}

/** Embeds a [read-only] SegmentInfo and adds per-commit
 *  fields.
 *
 *  @lucene.experimental */
public class SegmentCommitInfo {

  /** The {@link SegmentInfo} that we wrap. */
  public final SegmentInfo info;

  // How many deleted docs in the segment:
  private int delCount;

  // Generation number of the live docs file (-1 if there
  // are no deletes yet):
  private long delGen;

  // Normally 1+delGen, unless an exception was hit on last
  // attempt to write:
  private long nextWriteDelGen;

  // Generation number of the FieldInfos (-1 if there are no updates)
  private long fieldInfosGen;

  // Normally 1+fieldInfosGen, unless an exception was hit on last attempt to
  // write
  private long nextWriteFieldInfosGen; //fieldInfosGen == -1 ? 1 : fieldInfosGen + 1;

  // Generation number of the DocValues (-1 if there are no updates)
  private long docValuesGen;

  // Normally 1+dvGen, unless an exception was hit on last attempt to
  // write
  private long nextWriteDocValuesGen; //docValuesGen == -1 ? 1 : docValuesGen + 1;

  // TODO should we add .files() to FieldInfosFormat, like we have on
  // LiveDocsFormat?
  // track the fieldInfos update files
  private final Set<String> fieldInfosFiles = new HashSet<>();

  // Track the per-field DocValues update files
  private final Map<Integer,Set<String>> dvUpdatesFiles = new HashMap<>();

  // Track the per-generation updates files
  @Deprecated
  private final Map<Long,Set<String>> genUpdatesFiles = new HashMap<>();

  private volatile long sizeInBytes = -1;
}

segment的元信息文件

文件後綴:.si

每一個segment都有一個.si文件,記錄了該segment的元信息。

segment元信息文件中記錄了segment的文檔數量,segment對應的文件列表等信息。

文件示例


elasticsearch_store_si.png


具體實現類

/**
 * Information about a segment such as its name, directory, and files related
 * to the segment.
 *
 * @lucene.experimental
 */
public final class SegmentInfo {

  // _bl
  public final String name;

  /** Where this segment resides. */
  public final Directory dir;

  /** Id that uniquely identifies this segment. */
  private final byte[] id;

  private Codec codec;

  // Tracks the Lucene version this segment was created with, since 3.1. Null
  // indicates an older than 3.0 index, and it's used to detect a too old index.
  // The format expected is "x.y" - "2.x" for pre-3.0 indexes (or null), and
  // specific versions afterwards ("3.0.0", "3.1.0" etc.).
  // see o.a.l.util.Version.
  private Version version;

  private int maxDoc;         // number of docs in seg

  private boolean isCompoundFile;

  private Map<String,String> diagnostics;

  private Set<String> setFiles;

  private final Map<String,String> attributes;
}

fields信息文件

文件後綴:.fnm

該文件存儲了fields的基本信息。

fields信息中包括field的數量,field的類型,以及IndexOpetions,包括是否存儲、是否索引,是否分詞,是否須要列存等等。

文件示例


elasticsearch_store_fnm.png


具體實現類

/**
 *  Access to the Field Info file that describes document fields and whether or
 *  not they are indexed. Each segment has a separate Field Info file. Objects
 *  of this class are thread-safe for multiple readers, but only one thread can
 *  be adding documents at a time, with no other reader or writer threads
 *  accessing this object.
 **/
public final class FieldInfo {
  /** Field's name */
  public final String name;

  /** Internal field number */
  //field在內部的編號
  public final int number;

  //field docvalues的類型
  private DocValuesType docValuesType = DocValuesType.NONE;

  // True if any document indexed term vectors
  private boolean storeTermVector;

  private boolean omitNorms; // omit norms associated with indexed fields 

  //index的配置項
  private IndexOptions indexOptions = IndexOptions.NONE;

  private boolean storePayloads; // whether this field stores payloads together with term positions 

  private final Map<String,String> attributes;

  // docvalues的generation
  private long dvGen;
}

數據存儲文件

文件後綴:.fdx, .fdt

索引文件爲.fdx,數據文件爲.fdt,數據存儲文件功能爲根據自動的文檔id,獲得文檔的內容,搜索引擎的術語習慣稱之爲正排數據,即doc_id -> content,es的_source數據就存在這

索引文件記錄了快速定位文檔數據的索引信息,數據文件記錄了全部文檔id的具體內容。

文件示例


elasticsearch_store_fdt.png


具體實現類

/**
 * Random-access reader for {@link CompressingStoredFieldsIndexWriter}.
 * @lucene.internal
 */
public final class CompressingStoredFieldsIndexReader implements Cloneable, Accountable {
  private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(CompressingStoredFieldsIndexReader.class);

  final int maxDoc;

  //docid索引,快速定位某個docid的數組座標
  final int[] docBases;

  //快速定位某個docid所在的文件offset的startPointer
  final long[] startPointers;

  //平均一個chunk的文檔數
  final int[] avgChunkDocs;

  //平均一個chunk的size
  final long[] avgChunkSizes;

  final PackedInts.Reader[] docBasesDeltas; // delta from the avg

  final PackedInts.Reader[] startPointersDeltas; // delta from the avg
}

/**
 * {@link StoredFieldsReader} impl for {@link CompressingStoredFieldsFormat}.
 * @lucene.experimental
 */
public final class CompressingStoredFieldsReader extends StoredFieldsReader {

  //從fdt正排索引文件中得到
  private final int version;

  // field的基本信息
  private final FieldInfos fieldInfos;

  //fdt正排索引文件reader
  private final CompressingStoredFieldsIndexReader indexReader;

  //從fdt正排索引文件中得到,用於指向fdx數據文件的末端,指向numChunks地址4
  private final long maxPointer;

  //fdx正排數據文件句柄
  private final IndexInput fieldsStream;

  //塊大小
  private final int chunkSize;

  private final int packedIntsVersion;

  //壓縮類型
  private final CompressionMode compressionMode;

  //解壓縮處理對象
  private final Decompressor decompressor;

  //文檔數量,從segment元數據中得到
  private final int numDocs;

  //是否正在merge,默認爲false
  private final boolean merging;

  //初始化時new了一個BlockState,BlockState記錄下當前正排文件讀取的狀態信息
  private final BlockState state;
  //chunk的數量
  private final long numChunks; // number of compressed blocks written

  //dirty chunk的數量
  private final long numDirtyChunks; // number of incomplete compressed blocks written

  //是否close,默認爲false
  private boolean closed;
}

倒排索引文件

索引後綴:.tip,.tim

倒排索引也包含索引文件和數據文件,.tip爲索引文件,.tim爲數據文件,索引文件包含了每一個字段的索引元信息,數據文件有具體的索引內容。

5.5.0版本的倒排索引實現爲FST tree,FST tree的最大優點就是內存空間佔用很是低 ,具體能夠參看下這篇文章:http://www.cnblogs.com/bonelee/p/6226185.html

http://examples.mikemccandless.com/fst.py?terms=&cmd=Build+it 爲FST圖實例,能夠根據輸入的數據構造出FST圖

輸入到 FST 中的數據爲:
String inputValues[] = {"mop","moth","pop","star","stop","top"};
long outputValues[] = {0,1,2,3,4,5};

生成的 FST 圖爲:


elasticsearch_store_tip1.png



elasticsearch_store_tip2.png


文件示例


elasticsearch_store_tip3.png


具體實現類

public final class BlockTreeTermsReader extends FieldsProducer {
  // Open input to the main terms dict file (_X.tib)
  final IndexInput termsIn;
  // Reads the terms dict entries, to gather state to
  // produce DocsEnum on demand
  final PostingsReaderBase postingsReader;
  private final TreeMap<String,FieldReader> fields = new TreeMap<>();

  /** File offset where the directory starts in the terms file. */
  /索引數據文件tim的數據的尾部的元數據的地址
  private long dirOffset;
  /** File offset where the directory starts in the index file. */

  //索引文件tip的數據的尾部的元數據的地址
  private long indexDirOffset;

  //semgent的名稱
  final String segment;

  //版本號
  final int version;

  //5.3.x index, we record up front if we may have written any auto-prefix terms,示例中記錄的是false
  final boolean anyAutoPrefixTerms;
}

/**
 * BlockTree's implementation of {@link Terms}.
 * @lucene.internal
 */
public final class FieldReader extends Terms implements Accountable {

  //term的數量
  final long numTerms;

  //field信息
  final FieldInfo fieldInfo;

  final long sumTotalTermFreq;

  //總的文檔頻率
  final long sumDocFreq;

  //文檔數量
  final int docCount;

  //字段在索引文件tip中的起始位置
  final long indexStartFP;

  final long rootBlockFP;

  final BytesRef rootCode;

  final BytesRef minTerm;

  final BytesRef maxTerm;

  //longs:metadata buffer, holding monotonic values
  final int longsSize;

  final BlockTreeTermsReader parent;

  final FST<BytesRef> index;
}

倒排鏈文件

文件後綴:.doc, .pos, .pay

.doc保存了每一個term的doc id列表和term在doc中的詞頻

全文索引的字段,會有.pos文件,保存了term在doc中的位置

全文索引的字段,使用了一些像payloads的高級特性纔會有.pay文件,保存了term在doc中的一些高級特性

文件示例


elasticsearch_store_doc.png


具體實現類

/**
 * Concrete class that reads docId(maybe frq,pos,offset,payloads) list
 * with postings format.
 *
 * @lucene.experimental
 */
public final class Lucene50PostingsReader extends PostingsReaderBase {
  private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(Lucene50PostingsReader.class);
  private final IndexInput docIn;
  private final IndexInput posIn;
  private final IndexInput payIn;
  final ForUtil forUtil;
  private int version;

  //不分詞的字段使用的是該對象,基於skiplist實現了倒排鏈
  final class BlockDocsEnum extends PostingsEnum {
  }

  //全文檢索字段使用的是該對象
  final class BlockPostingsEnum extends PostingsEnum {
  }

  //包含高級特性的字段使用的是該對象
  final class EverythingEnum extends PostingsEnum {
  }
}

列存文件(docvalues)

文件後綴:.dvm, .dvd

索引文件爲.dvm,數據文件爲.dvd。

lucene實現的docvalues有以下類型:

  • 一、NONE 不開啓docvalue時的狀態

  • 二、NUMERIC 單個數值類型的docvalue主要包括(int,long,float,double)

  • 三、BINARY 二進制類型值對應不一樣的codes最大值可能超過32766字節,

  • 四、SORTED 有序增量字節存儲,僅僅存儲不一樣部分的值和偏移量指針,值必須小於等於32766字節

  • 五、SORTED_NUMERIC 存儲數值類型的有序數組列表

  • 六、SORTED_SET 能夠存儲多值域的docvalue值,但返回時,僅僅只能返回多值域的第一個docvalue

  • 七、對應not_anaylized的string字段,使用的是SORTED_SET類型,number的類型是SORTED_NUMERIC類型

其中SORTED_SET 的 SORTED_SINGLE_VALUED類型包括了兩類數據 : binary + numeric, binary是按ord排序的term的列表,numeric是doc到ord的映射。

文件示例


elasticsearch_store_dvd.png


具體實現類

/** reader for {@link Lucene54DocValuesFormat} */
final class Lucene54DocValuesProducer extends DocValuesProducer implements Closeable {
  //number類型的field的列存列表
  private final Map<String,NumericEntry> numerics = new HashMap<>();

  //字符串類型的field的列存列表
  private final Map<String,BinaryEntry> binaries = new HashMap<>();

  //有序字符串類型的field的列存列表
  private final Map<String,SortedSetEntry> sortedSets = new HashMap<>();

  //有序number類型的field的列存列表
  private final Map<String,SortedSetEntry> sortedNumerics = new HashMap<>();

  //字符串類型的field的ords列表
  private final Map<String,NumericEntry> ords = new HashMap<>();

  //docId -> address -> ord 中field的ords列表
  private final Map<String,NumericEntry> ordIndexes = new HashMap<>();

  //field的數量
  private final int numFields;

  //內存使用量
  private final AtomicLong ramBytesUsed;

  //數據源的文件句柄
  private final IndexInput data;

  //文檔數
  private final int maxDoc;
  // memory-resident structures
  private final Map<String,MonotonicBlockPackedReader> addressInstances = new HashMap<>();
  private final Map<String,ReverseTermsIndex> reverseIndexInstances = new HashMap<>();
  private final Map<String,DirectMonotonicReader.Meta> directAddressesMeta = new HashMap<>();

  //是否正在merge
  private final boolean merging;
}

/** metadata entry for a numeric docvalues field */
  static class NumericEntry {
    private NumericEntry() {}
    /** offset to the bitset representing docsWithField, or -1 if no documents have missing values */
    long missingOffset;

    /** offset to the actual numeric values */
    //field的在數據文件中的起始地址
    public long offset;

    /** end offset to the actual numeric values */
    //field的在數據文件中的結尾地址
    public long endOffset;

    /** bits per value used to pack the numeric values */
    public int bitsPerValue;

    //format類型
    int format;
    /** count of values written */
    public long count;
    /** monotonic meta */
    public DirectMonotonicReader.Meta monotonicMeta;

    //最小的value
    long minValue;

    //Compressed by computing the GCD
    long gcd;

    //Compressed by giving IDs to unique values.
    long table[];
    /** for sparse compression */
    long numDocsWithValue;
    NumericEntry nonMissingValues;
    NumberType numberType;
  }

  /** metadata entry for a binary docvalues field */
  static class BinaryEntry {
    private BinaryEntry() {}
    /** offset to the bitset representing docsWithField, or -1 if no documents have missing values */
    long missingOffset;
    /** offset to the actual binary values */
    //field的在數據文件中的起始地址
    long offset;
    int format;
    /** count of values written */
    public long count;

    //最短字符串的長度
    int minLength;

    //最長字符串的長度
    int maxLength;
    /** offset to the addressing data that maps a value to its slice of the byte[] */
    public long addressesOffset, addressesEndOffset;
    /** meta data for addresses */
    public DirectMonotonicReader.Meta addressesMeta;
    /** offset to the reverse index */
    public long reverseIndexOffset;
    /** packed ints version used to encode addressing information */
    public int packedIntsVersion;
    /** packed ints blocksize */
    public int blockSize;
  }

參考資料

lucene source code

lucene document

lucene字典實現原理——FST

本文地址:http://elasticsearch.cn/article/6178

相關文章
相關標籤/搜索