Elasticsearch中數據是如何存儲的


轉自 https://elasticsearch.cn/article/6178

前言

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

Elasticsearch索引結構

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

 

elasticsearch_store_arc.png

 

shard是Elasticsearch數據存儲的最小單位,index的存儲容量為所有shard的存儲容量之和。Elasticsearch集群的存儲容量則為所有index存儲容量之和。

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

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

lucene數據存儲

下面我們主要看下lucene的文件內容,在了解lucene文件內容前,大家先了解些lucene的基本概念。

lucene基本概念

  • segment : lucene內部的數據是由一個個segment組成的,寫入lucene的數據並不直接落盤,而是先寫在內存中,經過了refresh間隔,lucene才將該時間段寫入的全部數據refresh成一個segment,segment多了之后會進行merge成更大的segment。lucene查詢時會遍歷每個segment完成。由於lucene* 寫入的數據是在內存中完成,所以寫入效率非常高。但是也存在丟失數據的風險,所以Elasticsearch基於此現象實現了translog,只有在segment數據落盤后,Elasticsearch才會刪除對應的translog。
  • doc : doc表示lucene中的一條記錄
  • field :field表示記錄中的字段概念,一個doc由若干個field組成。
  • term :term是lucene中索引的最小單位,某個field對應的內容如果是全文檢索類型,會將內容進行分詞,分詞的結果就是由term組成的。如果是不分詞的字段,那么該字段的內容就是一個term。
  • 倒排索引(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有如下類型:

  • 1、NONE 不開啟docvalue時的狀態
  • 2、NUMERIC 單個數值類型的docvalue主要包括(int,long,float,double)
  • 3、BINARY 二進制類型值對應不同的codes最大值可能超過32766字節,
  • 4、SORTED 有序增量字節存儲,僅僅存儲不同部分的值和偏移量指針,值必須小於等於32766字節
  • 5、SORTED_NUMERIC 存儲數值類型的有序數組列表
  • 6、SORTED_SET 可以存儲多值域的docvalue值,但返回時,僅僅只能返回多值域的第一個docvalue
  • 7、對應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


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM