当前位置:   article > 正文

Elasticsearch中数据是如何存储的

elasticsearch的储存方式

前言

很多使用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会包含如下的文件。

NameExtensionBrief Description
Segment Info.sisegment的元数据文件
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.posStores position information about where a term occurs in the index
全文索引的字段,会有该文件,保存了term在doc中的位置
Payloads.payStores additional per-position metadata information such as character offsets and user payloads
全文索引的字段,使用了一些像payloads的高级特性会有该文件,保存了term在doc中的一些高级特性
Norms.nvd, .nvm文件保存索引字段加权数据
Per-Document Values.dvd, .dvmlucene的docvalues文件,即数据的列式存储,用作聚合和排序
Term Vector Data.tvx, .tvd, .tvfStores offset into the document data file
保存索引字段的矢量信息,用在对term进行高亮,计算文本相关性中使用
Live Documents.liv记录了segment中删除的doc

测试数据示例

下面我们以真实的数据作为示例,看看lucene中各类型数据的容量占比。

写100w数据,有一个uuid字段,写入的是长度为36位的uuid,字符串总为3600w字节,约为35M。

数据使用一个shard,不带副本,使用默认的压缩算法,写入完成后merge成一个segment方便观察。

使用线上默认的配置,uuid存为不分词的字符串类型。创建如下索引:

  1. PUT test_field
  2. {
  3. "settings": {
  4. "index": {
  5. "number_of_shards": "1",
  6. "number_of_replicas": "0",
  7. "refresh_interval": "30s"
  8. }
  9. },
  10. "mappings": {
  11. "type": {
  12. "_all": {
  13. "enabled": false
  14. },
  15. "properties": {
  16. "uuid": {
  17. "type": "string",
  18. "index": "not_analyzed"
  19. }
  20. }
  21. }
  22. }
  23. }

首先写入100w不同的uuid,使用磁盘容量细节如下:

  1. health status index pri rep docs.count docs.deleted store.size pri.store.size
  2. green open test_field 1 0 1000000 0 122.7mb 122.7mb
  3. -rw-r--r-- 1 weizijun staff 41M Aug 19 21:23 _8.fdt
  4. -rw-r--r-- 1 weizijun staff 17K Aug 19 21:23 _8.fdx
  5. -rw-r--r-- 1 weizijun staff 688B Aug 19 21:23 _8.fnm
  6. -rw-r--r-- 1 weizijun staff 494B Aug 19 21:23 _8.si
  7. -rw-r--r-- 1 weizijun staff 265K Aug 19 21:23 _8_Lucene50_0.doc
  8. -rw-r--r-- 1 weizijun staff 44M Aug 19 21:23 _8_Lucene50_0.tim
  9. -rw-r--r-- 1 weizijun staff 340K Aug 19 21:23 _8_Lucene50_0.tip
  10. -rw-r--r-- 1 weizijun staff 37M Aug 19 21:23 _8_Lucene54_0.dvd
  11. -rw-r--r-- 1 weizijun staff 254B Aug 19 21:23 _8_Lucene54_0.dvm
  12. -rw-r--r-- 1 weizijun staff 195B Aug 19 21:23 segments_2
  13. -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使用的容量。

  1. health status index pri rep docs.count docs.deleted store.size pri.store.size
  2. green open test_field 1 0 1000000 0 13.2mb 13.2mb
  3. -rw-r--r-- 1 weizijun staff 5.5M Aug 19 21:29 _6.fdt
  4. -rw-r--r-- 1 weizijun staff 15K Aug 19 21:29 _6.fdx
  5. -rw-r--r-- 1 weizijun staff 688B Aug 19 21:29 _6.fnm
  6. -rw-r--r-- 1 weizijun staff 494B Aug 19 21:29 _6.si
  7. -rw-r--r-- 1 weizijun staff 309K Aug 19 21:29 _6_Lucene50_0.doc
  8. -rw-r--r-- 1 weizijun staff 7.0M Aug 19 21:29 _6_Lucene50_0.tim
  9. -rw-r--r-- 1 weizijun staff 195K Aug 19 21:29 _6_Lucene50_0.tip
  10. -rw-r--r-- 1 weizijun staff 244K Aug 19 21:29 _6_Lucene54_0.dvd
  11. -rw-r--r-- 1 weizijun staff 252B Aug 19 21:29 _6_Lucene54_0.dvm
  12. -rw-r--r-- 1 weizijun staff 195B Aug 19 21:29 segments_2
  13. -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个不一样的整型会是如何。

  1. health status index pri rep docs.count docs.deleted store.size pri.store.size
  2. green open test_field 1 0 1000000 0 13.6mb 13.6mb
  3. -rw-r--r-- 1 weizijun staff 6.1M Aug 28 10:19 _42.fdt
  4. -rw-r--r-- 1 weizijun staff 22K Aug 28 10:19 _42.fdx
  5. -rw-r--r-- 1 weizijun staff 688B Aug 28 10:19 _42.fnm
  6. -rw-r--r-- 1 weizijun staff 503B Aug 28 10:19 _42.si
  7. -rw-r--r-- 1 weizijun staff 2.8M Aug 28 10:19 _42_Lucene50_0.doc
  8. -rw-r--r-- 1 weizijun staff 2.2M Aug 28 10:19 _42_Lucene50_0.tim
  9. -rw-r--r-- 1 weizijun staff 83K Aug 28 10:19 _42_Lucene50_0.tip
  10. -rw-r--r-- 1 weizijun staff 2.5M Aug 28 10:19 _42_Lucene54_0.dvd
  11. -rw-r--r-- 1 weizijun staff 228B Aug 28 10:19 _42_Lucene54_0.dvm
  12. -rw-r--r-- 1 weizijun staff 196B Aug 28 10:19 segments_2
  13. -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实际存储容量跟整型数据长度差不多。

我们再看一下开启最佳压缩参数对存储空间的影响:

  1. health status index pri rep docs.count docs.deleted store.size pri.store.size
  2. green open test_field 1 0 1000000 0 107.2mb 107.2mb
  3. -rw-r--r-- 1 weizijun staff 25M Aug 20 12:30 _5.fdt
  4. -rw-r--r-- 1 weizijun staff 6.0K Aug 20 12:30 _5.fdx
  5. -rw-r--r-- 1 weizijun staff 688B Aug 20 12:31 _5.fnm
  6. -rw-r--r-- 1 weizijun staff 500B Aug 20 12:31 _5.si
  7. -rw-r--r-- 1 weizijun staff 265K Aug 20 12:31 _5_Lucene50_0.doc
  8. -rw-r--r-- 1 weizijun staff 44M Aug 20 12:31 _5_Lucene50_0.tim
  9. -rw-r--r-- 1 weizijun staff 322K Aug 20 12:31 _5_Lucene50_0.tip
  10. -rw-r--r-- 1 weizijun staff 37M Aug 20 12:31 _5_Lucene54_0.dvd
  11. -rw-r--r-- 1 weizijun staff 254B Aug 20 12:31 _5_Lucene54_0.dvm
  12. -rw-r--r-- 1 weizijun staff 224B Aug 20 12:31 segments_4
  13. -rw-r--r-- 1 weizijun staff 0B Aug 20 12:00 write.lock

结果中可以发现,只有正排数据会启动压缩,压缩能力确实强劲,不考虑唯一id字段,存储容量大概压缩到接近50%。

我们还做了一些实验,Elasticsearch默认是开启_all参数的,_all可以让用户传入的整体json数据作为全文检索的字段,可以更方便的检索,但在现实场景中已经使用的不多,相反会增加很多存储容量的开销,可以看下开启_all的磁盘空间使用情况:

  1. health status index pri rep docs.count docs.deleted store.size pri.store.size
  2. green open test_field 1 0 1000000 0 162.4mb 162.4mb
  3. -rw-r--r-- 1 weizijun staff 41M Aug 18 22:59 _20.fdt
  4. -rw-r--r-- 1 weizijun staff 18K Aug 18 22:59 _20.fdx
  5. -rw-r--r-- 1 weizijun staff 777B Aug 18 22:59 _20.fnm
  6. -rw-r--r-- 1 weizijun staff 59B Aug 18 22:59 _20.nvd
  7. -rw-r--r-- 1 weizijun staff 78B Aug 18 22:59 _20.nvm
  8. -rw-r--r-- 1 weizijun staff 539B Aug 18 22:59 _20.si
  9. -rw-r--r-- 1 weizijun staff 7.2M Aug 18 22:59 _20_Lucene50_0.doc
  10. -rw-r--r-- 1 weizijun staff 4.2M Aug 18 22:59 _20_Lucene50_0.pos
  11. -rw-r--r-- 1 weizijun staff 73M Aug 18 22:59 _20_Lucene50_0.tim
  12. -rw-r--r-- 1 weizijun staff 832K Aug 18 22:59 _20_Lucene50_0.tip
  13. -rw-r--r-- 1 weizijun staff 37M Aug 18 22:59 _20_Lucene54_0.dvd
  14. -rw-r--r-- 1 weizijun staff 254B Aug 18 22:59 _20_Lucene54_0.dvm
  15. -rw-r--r-- 1 weizijun staff 196B Aug 18 22:59 segments_2
  16. -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

 

具体实现类

  1. public final class SegmentInfos implements Cloneable, Iterable<SegmentCommitInfo> {
  2. // generation是segment的版本的概念,从文件名中提取出来,实例中为:2t/101
  3. private long generation; // generation of the "segments_N" for the next commit
  4. private long lastGeneration; // generation of the "segments_N" file we last successfully read
  5. // or wrote; this is normally the same as generation except if
  6. // there was an IOException that had interrupted a commit
  7. /** Id for this commit; only written starting with Lucene 5.0 */
  8. private byte[] id;
  9. /** Which Lucene version wrote this commit, or null if this commit is pre-5.3. */
  10. private Version luceneVersion;
  11. /** Counts how often the index has been changed. */
  12. public long version;
  13. /** Used to name new segments. */
  14. // TODO: should this be a long ...?
  15. public int counter;
  16. /** Version of the oldest segment in the index, or null if there are no segments. */
  17. private Version minSegmentLuceneVersion;
  18. private List<SegmentCommitInfo> segments = new ArrayList<>();
  19. /** Opaque Map&lt;String, String&gt; that user can specify during IndexWriter.commit */
  20. public Map<String,String> userData = Collections.emptyMap();
  21. }
  22. /** Embeds a [read-only] SegmentInfo and adds per-commit
  23. * fields.
  24. *
  25. * @lucene.experimental */
  26. public class SegmentCommitInfo {
  27. /** The {@link SegmentInfo} that we wrap. */
  28. public final SegmentInfo info;
  29. // How many deleted docs in the segment:
  30. private int delCount;
  31. // Generation number of the live docs file (-1 if there
  32. // are no deletes yet):
  33. private long delGen;
  34. // Normally 1+delGen, unless an exception was hit on last
  35. // attempt to write:
  36. private long nextWriteDelGen;
  37. // Generation number of the FieldInfos (-1 if there are no updates)
  38. private long fieldInfosGen;
  39. // Normally 1+fieldInfosGen, unless an exception was hit on last attempt to
  40. // write
  41. private long nextWriteFieldInfosGen; //fieldInfosGen == -1 ? 1 : fieldInfosGen + 1;
  42. // Generation number of the DocValues (-1 if there are no updates)
  43. private long docValuesGen;
  44. // Normally 1+dvGen, unless an exception was hit on last attempt to
  45. // write
  46. private long nextWriteDocValuesGen; //docValuesGen == -1 ? 1 : docValuesGen + 1;
  47. // TODO should we add .files() to FieldInfosFormat, like we have on
  48. // LiveDocsFormat?
  49. // track the fieldInfos update files
  50. private final Set<String> fieldInfosFiles = new HashSet<>();
  51. // Track the per-field DocValues update files
  52. private final Map<Integer,Set<String>> dvUpdatesFiles = new HashMap<>();
  53. // Track the per-generation updates files
  54. @Deprecated
  55. private final Map<Long,Set<String>> genUpdatesFiles = new HashMap<>();
  56. private volatile long sizeInBytes = -1;
  57. }

segment的元信息文件

文件后缀:.si

每个segment都有一个.si文件,记录了该segment的元信息。

segment元信息文件中记录了segment的文档数量,segment对应的文件列表等信息。

文件示例

 

elasticsearch_store_si.png

 

具体实现类

  1. /**
  2. * Information about a segment such as its name, directory, and files related
  3. * to the segment.
  4. *
  5. * @lucene.experimental
  6. */
  7. public final class SegmentInfo {
  8. // _bl
  9. public final String name;
  10. /** Where this segment resides. */
  11. public final Directory dir;
  12. /** Id that uniquely identifies this segment. */
  13. private final byte[] id;
  14. private Codec codec;
  15. // Tracks the Lucene version this segment was created with, since 3.1. Null
  16. // indicates an older than 3.0 index, and it's used to detect a too old index.
  17. // The format expected is "x.y" - "2.x" for pre-3.0 indexes (or null), and
  18. // specific versions afterwards ("3.0.0", "3.1.0" etc.).
  19. // see o.a.l.util.Version.
  20. private Version version;
  21. private int maxDoc; // number of docs in seg
  22. private boolean isCompoundFile;
  23. private Map<String,String> diagnostics;
  24. private Set<String> setFiles;
  25. private final Map<String,String> attributes;
  26. }

fields信息文件

文件后缀:.fnm

该文件存储了fields的基本信息。

fields信息中包括field的数量,field的类型,以及IndexOpetions,包括是否存储、是否索引,是否分词,是否需要列存等等。

文件示例

 

elasticsearch_store_fnm.png

 

具体实现类

  1. /**
  2. * Access to the Field Info file that describes document fields and whether or
  3. * not they are indexed. Each segment has a separate Field Info file. Objects
  4. * of this class are thread-safe for multiple readers, but only one thread can
  5. * be adding documents at a time, with no other reader or writer threads
  6. * accessing this object.
  7. **/
  8. public final class FieldInfo {
  9. /** Field's name */
  10. public final String name;
  11. /** Internal field number */
  12. //field在内部的编号
  13. public final int number;
  14. //field docvalues的类型
  15. private DocValuesType docValuesType = DocValuesType.NONE;
  16. // True if any document indexed term vectors
  17. private boolean storeTermVector;
  18. private boolean omitNorms; // omit norms associated with indexed fields
  19. //index的配置项
  20. private IndexOptions indexOptions = IndexOptions.NONE;
  21. private boolean storePayloads; // whether this field stores payloads together with term positions
  22. private final Map<String,String> attributes;
  23. // docvalues的generation
  24. private long dvGen;
  25. }

数据存储文件

文件后缀:.fdx, .fdt

索引文件为.fdx,数据文件为.fdt,数据存储文件功能为根据自动的文档id,得到文档的内容,搜索引擎的术语习惯称之为正排数据,即doc_id -> content,es的_source数据就存在这

索引文件记录了快速定位文档数据的索引信息,数据文件记录了所有文档id的具体内容。

文件示例

 

elasticsearch_store_fdt.png

 

具体实现类

  1. /**
  2. * Random-access reader for {@link CompressingStoredFieldsIndexWriter}.
  3. * @lucene.internal
  4. */
  5. public final class CompressingStoredFieldsIndexReader implements Cloneable, Accountable {
  6. private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(CompressingStoredFieldsIndexReader.class);
  7. final int maxDoc;
  8. //docid索引,快速定位某个docid的数组坐标
  9. final int[] docBases;
  10. //快速定位某个docid所在的文件offset的startPointer
  11. final long[] startPointers;
  12. //平均一个chunk的文档数
  13. final int[] avgChunkDocs;
  14. //平均一个chunk的size
  15. final long[] avgChunkSizes;
  16. final PackedInts.Reader[] docBasesDeltas; // delta from the avg
  17. final PackedInts.Reader[] startPointersDeltas; // delta from the avg
  18. }
  19. /**
  20. * {@link StoredFieldsReader} impl for {@link CompressingStoredFieldsFormat}.
  21. * @lucene.experimental
  22. */
  23. public final class CompressingStoredFieldsReader extends StoredFieldsReader {
  24. //从fdt正排索引文件中获得
  25. private final int version;
  26. // field的基本信息
  27. private final FieldInfos fieldInfos;
  28. //fdt正排索引文件reader
  29. private final CompressingStoredFieldsIndexReader indexReader;
  30. //从fdt正排索引文件中获得,用于指向fdx数据文件的末端,指向numChunks地址4
  31. private final long maxPointer;
  32. //fdx正排数据文件句柄
  33. private final IndexInput fieldsStream;
  34. //块大小
  35. private final int chunkSize;
  36. private final int packedIntsVersion;
  37. //压缩类型
  38. private final CompressionMode compressionMode;
  39. //解压缩处理对象
  40. private final Decompressor decompressor;
  41. //文档数量,从segment元数据中获得
  42. private final int numDocs;
  43. //是否正在merge,默认为false
  44. private final boolean merging;
  45. //初始化时new了一个BlockState,BlockState记录下当前正排文件读取的状态信息
  46. private final BlockState state;
  47. //chunk的数量
  48. private final long numChunks; // number of compressed blocks written
  49. //dirty chunk的数量
  50. private final long numDirtyChunks; // number of incomplete compressed blocks written
  51. //是否close,默认为false
  52. private boolean closed;
  53. }

倒排索引文件

索引后缀:.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图

  1. 输入到 FST 中的数据为:
  2. String inputValues[] = {"mop","moth","pop","star","stop","top"};
  3. long outputValues[] = {0,1,2,3,4,5};

生成的 FST 图为:

 

elasticsearch_store_tip1.png

 

 

elasticsearch_store_tip2.png

 

文件示例

 

elasticsearch_store_tip3.png

 

具体实现类

  1. public final class BlockTreeTermsReader extends FieldsProducer {
  2. // Open input to the main terms dict file (_X.tib)
  3. final IndexInput termsIn;
  4. // Reads the terms dict entries, to gather state to
  5. // produce DocsEnum on demand
  6. final PostingsReaderBase postingsReader;
  7. private final TreeMap<String,FieldReader> fields = new TreeMap<>();
  8. /** File offset where the directory starts in the terms file. */
  9. /索引数据文件tim的数据的尾部的元数据的地址
  10. private long dirOffset;
  11. /** File offset where the directory starts in the index file. */
  12. //索引文件tip的数据的尾部的元数据的地址
  13. private long indexDirOffset;
  14. //semgent的名称
  15. final String segment;
  16. //版本号
  17. final int version;
  18. //5.3.x index, we record up front if we may have written any auto-prefix terms,示例中记录的是false
  19. final boolean anyAutoPrefixTerms;
  20. }
  21. /**
  22. * BlockTree's implementation of {@link Terms}.
  23. * @lucene.internal
  24. */
  25. public final class FieldReader extends Terms implements Accountable {
  26. //term的数量
  27. final long numTerms;
  28. //field信息
  29. final FieldInfo fieldInfo;
  30. final long sumTotalTermFreq;
  31. //总的文档频率
  32. final long sumDocFreq;
  33. //文档数量
  34. final int docCount;
  35. //字段在索引文件tip中的起始位置
  36. final long indexStartFP;
  37. final long rootBlockFP;
  38. final BytesRef rootCode;
  39. final BytesRef minTerm;
  40. final BytesRef maxTerm;
  41. //longs:metadata buffer, holding monotonic values
  42. final int longsSize;
  43. final BlockTreeTermsReader parent;
  44. final FST<BytesRef> index;
  45. }

倒排链文件

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

.doc保存了每个term的doc id列表和term在doc中的词频

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

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

文件示例

 

elasticsearch_store_doc.png

 

具体实现类

  1. /**
  2. * Concrete class that reads docId(maybe frq,pos,offset,payloads) list
  3. * with postings format.
  4. *
  5. * @lucene.experimental
  6. */
  7. public final class Lucene50PostingsReader extends PostingsReaderBase {
  8. private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(Lucene50PostingsReader.class);
  9. private final IndexInput docIn;
  10. private final IndexInput posIn;
  11. private final IndexInput payIn;
  12. final ForUtil forUtil;
  13. private int version;
  14. //不分词的字段使用的是该对象,基于skiplist实现了倒排链
  15. final class BlockDocsEnum extends PostingsEnum {
  16. }
  17. //全文检索字段使用的是该对象
  18. final class BlockPostingsEnum extends PostingsEnum {
  19. }
  20. //包含高级特性的字段使用的是该对象
  21. final class EverythingEnum extends PostingsEnum {
  22. }
  23. }

列存文件(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

 

具体实现类

  1. /** reader for {@link Lucene54DocValuesFormat} */
  2. final class Lucene54DocValuesProducer extends DocValuesProducer implements Closeable {
  3. //number类型的field的列存列表
  4. private final Map<String,NumericEntry> numerics = new HashMap<>();
  5. //字符串类型的field的列存列表
  6. private final Map<String,BinaryEntry> binaries = new HashMap<>();
  7. //有序字符串类型的field的列存列表
  8. private final Map<String,SortedSetEntry> sortedSets = new HashMap<>();
  9. //有序number类型的field的列存列表
  10. private final Map<String,SortedSetEntry> sortedNumerics = new HashMap<>();
  11. //字符串类型的field的ords列表
  12. private final Map<String,NumericEntry> ords = new HashMap<>();
  13. //docId -> address -> ord 中field的ords列表
  14. private final Map<String,NumericEntry> ordIndexes = new HashMap<>();
  15. //field的数量
  16. private final int numFields;
  17. //内存使用量
  18. private final AtomicLong ramBytesUsed;
  19. //数据源的文件句柄
  20. private final IndexInput data;
  21. //文档数
  22. private final int maxDoc;
  23. // memory-resident structures
  24. private final Map<String,MonotonicBlockPackedReader> addressInstances = new HashMap<>();
  25. private final Map<String,ReverseTermsIndex> reverseIndexInstances = new HashMap<>();
  26. private final Map<String,DirectMonotonicReader.Meta> directAddressesMeta = new HashMap<>();
  27. //是否正在merge
  28. private final boolean merging;
  29. }
  30. /** metadata entry for a numeric docvalues field */
  31. static class NumericEntry {
  32. private NumericEntry() {}
  33. /** offset to the bitset representing docsWithField, or -1 if no documents have missing values */
  34. long missingOffset;
  35. /** offset to the actual numeric values */
  36. //field的在数据文件中的起始地址
  37. public long offset;
  38. /** end offset to the actual numeric values */
  39. //field的在数据文件中的结尾地址
  40. public long endOffset;
  41. /** bits per value used to pack the numeric values */
  42. public int bitsPerValue;
  43. //format类型
  44. int format;
  45. /** count of values written */
  46. public long count;
  47. /** monotonic meta */
  48. public DirectMonotonicReader.Meta monotonicMeta;
  49. //最小的value
  50. long minValue;
  51. //Compressed by computing the GCD
  52. long gcd;
  53. //Compressed by giving IDs to unique values.
  54. long table[];
  55. /** for sparse compression */
  56. long numDocsWithValue;
  57. NumericEntry nonMissingValues;
  58. NumberType numberType;
  59. }
  60. /** metadata entry for a binary docvalues field */
  61. static class BinaryEntry {
  62. private BinaryEntry() {}
  63. /** offset to the bitset representing docsWithField, or -1 if no documents have missing values */
  64. long missingOffset;
  65. /** offset to the actual binary values */
  66. //field的在数据文件中的起始地址
  67. long offset;
  68. int format;
  69. /** count of values written */
  70. public long count;
  71. //最短字符串的长度
  72. int minLength;
  73. //最长字符串的长度
  74. int maxLength;
  75. /** offset to the addressing data that maps a value to its slice of the byte[] */
  76. public long addressesOffset, addressesEndOffset;
  77. /** meta data for addresses */
  78. public DirectMonotonicReader.Meta addressesMeta;
  79. /** offset to the reverse index */
  80. public long reverseIndexOffset;
  81. /** packed ints version used to encode addressing information */
  82. public int packedIntsVersion;
  83. /** packed ints blocksize */
  84. public int blockSize;
  85. }

参考资料

lucene source code

lucene document

lucene字典实现原理——FST

转载于:https://my.oschina.net/weiweiblog/blog/3018519

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/859004
推荐阅读
相关标签
  

闽ICP备14008679号