轉自 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。
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的一些相關信息。
文件示例
具體實現類
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<String, String> 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對應的文件列表等信息。
文件示例
具體實現類
/**
* 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,包括是否存儲、是否索引,是否分詞,是否需要列存等等。
文件示例
具體實現類
/**
* 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的具體內容。
文件示例
具體實現類
/**
* 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 圖為:
文件示例
具體實現類
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中的一些高級特性
文件示例
具體實現類
/**
* 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的映射。
文件示例
具體實現類
/** 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;
}
參考資料
本文地址:http://elasticsearch.cn/article/6178