ElasticSearch數(shù)據(jù)存儲內(nèi)容

1、前言

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

2、Elasticsearch索引結(jié)構(gòu)

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

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

一個shard就對應(yīng)了一個lucene的library。對于一個shard,Elasticsearch增加了translog的功能,類似于HBase WAL,是數(shù)據(jù)寫入過程中的中間數(shù)據(jù),其余的數(shù)據(jù)都在lucene庫中管理的。

所以Elasticsearch索引使用的存儲內(nèi)容主要取決于lucene中的數(shù)據(jù)存儲。

3、lucene數(shù)據(jù)存儲

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

3.1 lucene基本概念

  • segment : lucene內(nèi)部的數(shù)據(jù)是由一個個segment組成的,寫入lucene的數(shù)據(jù)并不直接落盤,而是先寫在內(nèi)存中,經(jīng)過了refresh間隔,lucene才將該時間段寫入的全部數(shù)據(jù)refresh成一個segment,segment多了之后會進行merge成更大的segment。lucene查詢時會遍歷每個segment完成。由于lucene* 寫入的數(shù)據(jù)是在內(nèi)存中完成,所以寫入效率非常高。但是也存在丟失數(shù)據(jù)的風險,所以Elasticsearch基于此現(xiàn)象實現(xiàn)了translog,只有在segment數(shù)據(jù)落盤后,Elasticsearch才會刪除對應(yīng)的translog。
  • doc : doc表示lucene中的一條記錄
  • field :field表示記錄中的字段概念,一個doc由若干個field組成。
  • term :term是lucene中索引的最小單位,某個field對應(yīng)的內(nèi)容如果是全文檢索類型,會將內(nèi)容進行分詞,分詞的結(jié)果就是由term組成的。如果是不分詞的字段,那么該字段的內(nèi)容就是一個term。
  • 倒排索引(inverted index): lucene索引的通用叫法,即實現(xiàn)了term到doc list的映射。
  • 正排數(shù)據(jù):搜索引擎的通用叫法,即原始數(shù)據(jù),可以理解為一個doc list。
  • docvalues :Elasticsearch中的列式存儲的名稱,Elasticsearch除了存儲原始存儲、倒排索引,還存儲了一份docvalues,用作分析和排序。

3.2 lucene文件內(nèi)容

lucene包的文件是由很多segment文件組成的,segments_xxx文件記錄了lucene包下面的segment文件數(shù)量。每個segment會包含如下的文件。

Name Extension Brief Description
Segment Info .si segment的元數(shù)據(jù)文件
Compound File .cfs, .cfe 一個segment包含了如下表的各個文件,為減少打開文件的數(shù)量,在segment小的時候,segment的所有文件內(nèi)容都保存在cfs文件中,cfe文件保存了lucene各文件在cfs文件的位置信息
Fields .fnm 保存了fields的相關(guān)信息
Field Index .fdx 正排存儲文件的元數(shù)據(jù)信息
Field Data .fdt 存儲了正排存儲數(shù)據(jù),寫入的原文存儲在這
Term Dictionary .tim 倒排索引的元數(shù)據(jù)信息
Term Index .tip 倒排索引文件,存儲了所有的倒排索引數(shù)據(jù)
Frequencies .doc 保存了每個term的doc id列表和term在doc中的詞頻
Positions .pos 全文索引的字段,會有該文件,保存了term在doc中的位置
Payloads .pay 全文索引的字段,使用了一些像payloads的高級特性會有該文件,保存了term在doc中的一些高級特性
Norms .nvd, .nvm 文件保存索引字段加權(quán)數(shù)據(jù)
Per-Document Values .dvd, .dvm lucene的docvalues文件,即數(shù)據(jù)的列式存儲,用作聚合和排序
Term Vector Data .tvx, .tvd, .tvf 保存索引字段的矢量信息,用在對term進行高亮,計算文本相關(guān)性中使用
Live Documents .liv 記錄了segment中刪除的doc

4、測試數(shù)據(jù)示例

下面我們以真實的數(shù)據(jù)作為示例,看看lucene中各類型數(shù)據(jù)的容量占比。

寫100w數(shù)據(jù),有一個uuid字段,寫入的是長度為36位的uuid,字符串總為3600w字節(jié),約為35M。

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

使用線上默認的配置,uuid存為不分詞的字符串類型。創(chuàng)建如下索引:

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,使用磁盤容量細節(jié)如下:


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

可以看到正排數(shù)據(jù)、倒排索引數(shù)據(jù),列存數(shù)據(jù)容量占比幾乎相同,正排數(shù)據(jù)和倒排數(shù)據(jù)還會存儲Elasticsearch的唯一id字段,所以容量會比列存多一些。

35M的uuid存入Elasticsearch后,數(shù)據(jù)膨脹了3倍,達到了122.7mb。Elasticsearch竟然這么消耗資源,不要著急下結(jié)論,接下來看另一個測試結(jié)果。

我們寫入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的數(shù)據(jù)Elasticsearch容量只有13.2mb,其中還有主要的占比還是Elasticsearch的唯一id,100w的uuid幾乎不占存儲容積。

所以在Elasticsearch中建立索引的字段如果基數(shù)越大(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

從結(jié)果可以看到,100w整型數(shù)據(jù),Elasticsearch的存儲開銷為13.6mb。如果以int型計算100w數(shù)據(jù)的長度的話,為400w字節(jié),大概是3.8mb數(shù)據(jù)。忽略Elasticsearch唯一id字段的影響,Elasticsearch實際存儲容量跟整型數(shù)據(jù)長度差不多。

我們再看一下開啟最佳壓縮參數(shù)對存儲空間的影響:

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

結(jié)果中可以發(fā)現(xiàn),只有正排數(shù)據(jù)會啟動壓縮,壓縮能力確實強勁,不考慮唯一id字段,存儲容量大概壓縮到接近50%。

我們還做了一些實驗,Elasticsearch默認是開啟_all參數(shù)的,_all可以讓用戶傳入的整體json數(shù)據(jù)作為全文檢索的字段,可以更方便的檢索,但在現(xiàn)實場景中已經(jīng)使用的不多,相反會增加很多存儲容量的開銷,可以看下開啟_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的存儲空間,多的數(shù)據(jù)都在倒排索引上,大約會增加30%多的存儲開銷。所以線上都直接禁用。

然后我還做了其他幾個嘗試,為了驗證存儲容量是否和數(shù)據(jù)量成正比,寫入1000w數(shù)據(jù)的uuid,發(fā)現(xiàn)存儲容量基本為100w數(shù)據(jù)的10倍。我還驗證了數(shù)據(jù)長度是否和數(shù)據(jù)量成正比,發(fā)現(xiàn)把uuid增長2倍、4倍,存儲容量也響應(yīng)的增加了2倍和4倍。在此就不一一列出數(shù)據(jù)了。

5、lucene各文件具體內(nèi)容和實現(xiàn)

5.1 lucene數(shù)據(jù)元信息文件

文件名為:segments_xxx

該文件為lucene數(shù)據(jù)文件的元信息文件,記錄所有segment的元數(shù)據(jù)信息。

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

lucene元信息文件還支持記錄userData,Elasticsearch可以在此記錄translog的一些相關(guān)信息。

5.1.1 文件示例

5.1.2 具體實現(xiàn)類

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;
}

5.2 segment的元信息文件

文件后綴:.si

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

segment元信息文件中記錄了segment的文檔數(shù)量,segment對應(yīng)的文件列表等信息。

5.2.1 文件示例

5.2.2 具體實現(xiàn)類

/**
 * 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;
}

5.3 fields信息文件

文件后綴:.fnm

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

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

5.3.1 文件示例

5.3.2 具體實現(xiàn)類

/**
 *  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在內(nèi)部的編號
  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;
}

5.4 數(shù)據(jù)存儲文件

文件后綴:.fdx, .fdt

索引文件為.fdx,數(shù)據(jù)文件為.fdt,數(shù)據(jù)存儲文件功能為根據(jù)自動的文檔id,得到文檔的內(nèi)容,搜索引擎的術(shù)語習慣稱之為正排數(shù)據(jù),即doc_id -> content,es的_source數(shù)據(jù)就存在這

索引文件記錄了快速定位文檔數(shù)據(jù)的索引信息,數(shù)據(jù)文件記錄了所有文檔id的具體內(nèi)容。

5.4.1 文件示例

5.4.2 具體實現(xiàn)類

/**
 * 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的數(shù)組坐標
  final int[] docBases;

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

  //平均一個chunk的文檔數(shù)
  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數(shù)據(jù)文件的末端,指向numChunks地址4
  private final long maxPointer;

  //fdx正排數(shù)據(jù)文件句柄
  private final IndexInput fieldsStream;

  //塊大小
  private final int chunkSize;

  private final int packedIntsVersion;

  //壓縮類型
  private final CompressionMode compressionMode;

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

  //文檔數(shù)量,從segment元數(shù)據(jù)中獲得
  private final int numDocs;

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

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

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

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

5.5 倒排索引文件

索引后綴:.tip,.tim

倒排索引也包含索引文件和數(shù)據(jù)文件,.tip為索引文件,.tim為數(shù)據(jù)文件,索引文件包含了每個字段的索引元信息,數(shù)據(jù)文件有具體的索引內(nèi)容。

5.5.0版本的倒排索引實現(xiàn)為FST tree,F(xiàn)ST tree的最大優(yōu)勢就是內(nèi)存空間占用非常低 ,具體可以參看下這篇文章:http://www.cnblogs.com/bonelee/p/6226185.html

http://examples.mikemccandless.com/fst.py?terms=&cmd=Build+it 為FST圖實例,可以根據(jù)輸入的數(shù)據(jù)構(gòu)造出FST圖

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

生成的 FST 圖為:

5.5.1 文件示例

5.5.2 具體實現(xiàn)類

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. */
  /索引數(shù)據(jù)文件tim的數(shù)據(jù)的尾部的元數(shù)據(jù)的地址
  private long dirOffset;
  /** File offset where the directory starts in the index file. */

  //索引文件tip的數(shù)據(jù)的尾部的元數(shù)據(jù)的地址
  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的數(shù)量
  final long numTerms;

  //field信息
  final FieldInfo fieldInfo;

  final long sumTotalTermFreq;

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

  //文檔數(shù)量
  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;
}

5.6 倒排鏈文件

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

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

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

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

5.6.1 文件示例

5.6.2 具體實現(xiàn)類

/**
 * 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實現(xiàn)了倒排鏈
  final class BlockDocsEnum extends PostingsEnum {
  }

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

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

5.7 列存文件(docvalues)

文件后綴:.dvm, .dvd

索引文件為.dvm,數(shù)據(jù)文件為.dvd。

lucene實現(xiàn)的docvalues有如下類型:

  • 1、NONE 不開啟docvalue時的狀態(tài)
  • 2、NUMERIC 單個數(shù)值類型的docvalue主要包括(int,long,float,double)
  • 3、BINARY 二進制類型值對應(yīng)不同的codes最大值可能超過32766字節(jié),
  • 4、SORTED 有序增量字節(jié)存儲,僅僅存儲不同部分的值和偏移量指針,值必須小于等于32766字節(jié)
  • 5、SORTED_NUMERIC 存儲數(shù)值類型的有序數(shù)組列表
  • 6、SORTED_SET 可以存儲多值域的docvalue值,但返回時,僅僅只能返回多值域的第一個docvalue
  • 7、對應(yīng)not_anaylized的string字段,使用的是SORTED_SET類型,number的類型是SORTED_NUMERIC類型

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

5.7.1 文件示例

5.7.2 具體實現(xiàn)類

/** 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的數(shù)量
  private final int numFields;

  //內(nèi)存使用量
  private final AtomicLong ramBytesUsed;

  //數(shù)據(jù)源的文件句柄
  private final IndexInput data;

  //文檔數(shù)
  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的在數(shù)據(jù)文件中的起始地址
    public long offset;

    /** end offset to the actual numeric values */
    //field的在數(shù)據(jù)文件中的結(jié)尾地址
    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的在數(shù)據(jù)文件中的起始地址
    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;
  }

轉(zhuǎn)載自:https://elasticsearch.cn/article/6178

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容