Elasticsearch存储目录结构深入详解

发布时间 2024-01-08 13:25:43作者: 粒子先生

在本文中,我们将研究Elasticsearch的各个部分写入数据目录的文件。我们将查看节点,索引和分片级文件,并简要说明其内容,以便了解Elasticsearch写入磁盘的数据。

1、从Elasticsearch路径说起

Elasticsearch配置了多个路径:

    path.home:运行Elasticsearch进程的用户的主目录。默认为Java系统属性user.dir,它是进程所有者的默认主目录。

    path.conf:包含配置文件的目录。这通常通过设置Java系统属性es.config来设置,因为在找到配置文件之前它必然会被解析。

    path.plugins:子文件夹为Elasticsearch插件的目录。这里支持Sym-links,当从同一个可执行文件运行多个Elasticsearch实例时,可以使用它来有选择地启用/禁用某个Elasticsearch实例的一组插件。

    path.logs:存储生成的日志的位置。如果其中一个卷的磁盘空间不足,则将它放在与数据目录不同的卷上可能是有意义的。path.data:包含Elasticsearch存储的数据的文件夹的路径。

在本文中,我们将仔细研究数据目录(path.data)的实际内容,并尝试了解所有文件的用途。
2、文件从哪里来?

由于Elasticsearch使用Lucene来处理分片级别的索引和查询,因此数据目录中的文件由Elasticsearch和Lucene写入。两者的职责都非常明确:

    Lucene负责写和维护Lucene索引文件,

    而Elasticsearch在Lucene之上写与功能相关的元数据,例如字段映射,索引设置和其他集群元数据。 最终用户和支持功能

    在低级Lucene中不存在,由Elasticsearch提供。

在深入研究并最终找到Lucene索引文件之前,让我们看看Elasticsearch编写的外部级别数据。
3、节点数据

只需从空数据目录启动Elasticsearch即可生成以下目录树:

    node.lock文件用于确保一次只能从一个数据目录读取/写入一个Elasticsearch相关安装信息。

    有趣的是global-0.st文件。 global-前缀表示这是一个全局状态文件,

    而.st扩展名表示这是一个包含元数据的状态文件。您可能已经猜到,此二进制文件包含有关您的集群的全局元数据,前缀后的数字表示集群元数据版本,遵循跟随您的集群严格增加的版本控制方案。

    注意:虽然在紧急情况下使用十六进制编辑器在技术上可以编辑这些文件,但强烈建议不要这样做,因为它很快就会导致数据丢失。

4、索引数据

让我们创建一个分片索引并查看Elasticsearch更改的文件。

我们看到已经创建了与索引名称对应的新目录。 此目录有两个子文件夹:_state和0.

    前者state包含所谓的索引状态文件(indices / {index-name} / state / state- {version} .st),其中包含有关索引的元数据,例如 它的创建时间戳。 它还包含唯一标识符以及索引的设置和映射。

    后者0包含与索引的第一个(也是唯一的)分片相关的数据(分片0)。

接下来,我们将仔细研究一下。
5、分片数据

分片数据目录包含分片的状态文件,其中包括版本控制以及有关分片是主分片还是副本的信息。

在早期的Elasticsearch版本中,还在分片数据目录中找到了单独的{shard_id} / index / _checksums-文件(和.cks-files)。在当前版本中,这些校验和现在可以在Lucene文件的页脚中找到,因为Lucene已经为其所有索引文件添加了端到端校验和。

{shard_id} / index目录包含Lucene拥有的文件。 Elasticsearch通常不直接写入此文件夹(除了早期版本中的旧校验和实现)。这些目录中的文件构成了任何Elasticsearch数据目录的大小。

在我们进入Lucene的世界之前,我们将看一下Elasticsearch 事务日志,这在每个分片translog目录中的前缀translog-中存在。Translog日志对于Elasticsearch的功能和性能非常重要,因此我们将在下一节中更详细地解释它的用法。
6、每个分片的 事务日志(Transaction Log)

Elasticsearch事务日志确保可以安全地将数据索引到Elasticsearch,而无需为每个文档执行低级Lucene提交。提交Lucene索引会在Lucene级别创建一个新的segment,即执行fsync(),会导致大量磁盘I / O影响性能。

为了接受索引文档并使其可搜索而不需要完整的Lucene提交,Elasticsearch将其添加到Lucene IndexWriter并将其附加到事务日志中。在每个refresh_interval之后,它将在Lucene索引上调用reopen(),这将使数据可以在不需要提交的情况下进行搜索。这是Lucene Near Real Time API的一部分。当IndexWriter最终由于自动刷新事务日志或由于显式刷新操作而提交时,先前的事务日志将被丢弃并且新的事务日志将取代它。

如果需要恢复,将首先恢复在Lucene中写入磁盘的segments,然后重放事务日志,以防止丢失尚未完全提交到磁盘的操作。
7、Lucene索引文件

Lucene在记录Lucene索引目录中的文件方面做得很好,为了方便起见,这里重现了这些文件(Lucene中的链接文档也详细介绍了这些文件从Lucene 2.1返回后所经历的变化,所以检查一下 出来):

 
Lucene Index Files
Name     Extension     Brief Description
Segments File     segments_N     Stores information about a commit point
Lock File     write.lock     The Write lock prevents multiple IndexWriters from writing to the same file.
Segment Info     .si     Stores metadata about a segment
Compound File     .cfs, .cfe     An optional “virtual” file consisting of all the other index files for systems that frequently run out of file handles.
Fields     .fnm     Stores information about the fields
Field Index     .fdx     Contains pointers to field data
Field Data     .fdt     The stored fields for documents
Term Dictionary     .tim     The term dictionary, stores term info
Term Index     .tip     The index into the Term Dictionary
Frequencies     .doc     Contains the list of docs which contain each term along with frequency
Positions     .pos     Stores position information about where a term occurs in the index
Payloads     .pay     Stores additional per-position metadata information such as character offsets and user payloads
Norms     .nvd, .nvm     Encodes length and boost factors for docs and fields
Per-Document Values     .dvd, .dvm     Encodes additional scoring factors or other per-document information.
Term Vector Index     .tvx     Stores offset into the document data file
Term Vector Documents     .tvd     Contains information about each document that has term vectors
Term Vector Fields     .tvf     The field level info about term vectors
Live Documents     .liv     Info about what files are live

通常,您还会在Lucene索引目录中看到一个segments.gen文件,该文件是一个帮助文件,其中包含有关当前/最新segments_N文件的信息,并用于可能无法通过目录列表返回足够信息的文件系统,以确定 最新一代段文件。在较旧的Lucene版本中,您还可以找到带有.del后缀的文件。 它们与Live Documents(.liv)文件的用途相同 - 换句话说,这些是删除列表。
8、修复有问题的碎片

由于Elasticsearch分片包含Lucene索引,我们可以使用Lucene的强大的CheckIndex工具(http://t.cn/Rs0gKjCl),这使我们能够扫描和修复有问题的段,通常只需要很少的数据丢失。 我们通常会建议Elasticsearch用户简单地重新索引数据(re-index),但如果由于某种原因这是不可能的并且数据非常重要,那么这是一条可以采取的路线,即使它需要相当多的手工工作和时间, 取决于碎片的数量和它们的大小。

    Lucene CheckIndex工具包含在默认的Elasticsearch发行版中,无需额外下载。

如果CheckIndex检测到问题并且其建议修复它看起来很合理,您可以通过添加-fix命令行参数告诉CheckIndex应用修复程序。
9、存储快照

您可能想知道所有这些文件如何转换为快照存储库使用的存储。 不用再想了:拿这个集群,将它作为我的快照快照到基于文件系统的网关,并检查存储库中的文件,我们会找到这些文件(为简洁起见省略了一些文件):

在根目录下,我们有一个索引文件,其中包含有关此存储库中所有快照的信息,每个快照都有一个关联的快照和元数据文件。

根目录下的快照文件包含有关快照状态,快照包含的索引等信息。 根目录下的元数据文件包含快照时的群集元数据。

    当设置compress:true时,使用LZF压缩元数据和快照文件,LZF专注于压缩和解压缩速度,这使其非常适合Elasticsearch。

    数据存储有标题:ZV + 1字节,指示数据是否被压缩。 在标题之后,格式上将存在一个或多个压缩的64K块:2字节块长度+2字节未压缩大小+压缩数据。  使用此信息,您可以使用任何兼容LibLZF的解压缩程序。

在索引级别,还有另一个文件indices / {index_name} / snapshot- {snapshot_name},其中包含索引元数据,例如快照时索引的设置和映射。

在分片级别,您将找到两种文件:重命名的Lucene索引文件和分片快照文件:indices / {index_name} / {shard_id} / snapshot- {snapshot_name}。 此文件包含有关快照中使用的分片目录中的哪些文件的信息,以及从快照中的逻辑文件名到具体文件名的映射,这些文件名在还原时应存储为磁盘。 它还包含可用于检测和防止数据损坏的所有相关文件的校验和,Lucene版本控制和大小信息。.

    您可能想知道为什么这些文件已被重命名而不是仅保留其原始文件名,这可能更容易直接在磁盘上使用。

    原因很简单:可以在再次快照之前对索引进行快照,删除并重新创建它。 在这种情况下,几个文件最终会有相同的名称,但内容不同。

10、小结

    在本文中,我们查看了各种级别的Elasticsearch写入数据目录的文件:节点,索引和分片级别。我们已经看到了Lucene索引存储在磁盘上的位置,并简要描述了如何使用Lucene CheckIndex工具来验证和修复有问题的碎片。

    希望您不需要对Elasticsearch数据目录的内容执行任何操作,但是了解您最喜欢的基于搜索的数据库将哪种数据写入文件系统总是有帮助的!

    不需要完整记住每个文件的确切含义,关键的时候知道去哪里更快的查找最重要。

11、补充认知

一份数据写入es会产生多份数据用于不同查询方式,会比原数据占用更多磁盘空间。而索引setting里"codec": "best_compression"是针对_source进行压缩的,压缩算法是deflate压缩比为6。

对照上面的lucene表进行如下的关联。

    存储原文_source 的文件.fdt .fdm .fdx;

    存储倒排索引 的文件.tim .tip .doc;

    用于聚合排序 的列存文件.dvd .dvm;

    全文检索文件.pos .pay .nvd .nvm等。

    加载到内存中的文件有.fdx .tip .dvm,

其中.tip占用内存最大,而.fdt . tim .dvd文件占用磁盘最大,例如


 

11.5M    _ap9_1w3.liv 25.0G    _ap9.fdt 31.9M    _ap9.fdx 444K     _ap9.fnm 53.1G    _ap9_Lucene50_0.doc 64.2G    _ap9_Lucene50_0.tim 781M     _ap9_Lucene50_0.tip 87.7G    _ap9_Lucene54_0.dvd 920K     _ap9_Lucene54_0.dvm104.0K    _ap9.si

另外segment较小时文件内容是保存在.cfs文件中,.cfe文件保存Lucene各文件在.cfs文件的位置信息,这是为了减少Lucene打开的文件句柄数。

es节点上shard过多了会导致内存不够,可以对静态的索引进行

    POST {indexName}/_forcemerge?max_num_segments=1

 
定义

在Lucene中基本的概念包括:index、document、field和term。一个index包含一个documents的序列

    一个document是一个fields的序列
    一个field是一个命名的terms序列
    一个term是一个bytes的序列

在两个不同fields中的相同bytes序列被认为是不同的term。因此,term表示为一对:命名field的字符串,以及field内的bytes。

倒排索引

谈到倒排索引,那么首先看看正排是什么样子的呢?假设文档1包含【中文、英文、日文】,文档2包含【英文、日文、韩文】,文档3包含【韩文,中文】那么根据文档去查找内容的话

    文档1->【中文、英文、日文】
    文档2->【英文、日文、韩文】
    文档3->【韩文,中文】

反过来,根据内容去查找文档

    中文->【文档1、文档3】
    英文->【文档1、文档2】
    日文->【文档1、文档2】
    韩文->【文档2、文档3】

就是倒排索引,而Lucene擅长的也正在于此。

Fields的类型

    TextField:索引并分词,不包含词向量,多用于文本的正文
    StringField:索引但不分词,整个String作为单个标记索引,例如可用于“国家名称”或“ID”等,或者其它任何你想要用来排序的字段
    IntPoint:int型用于精确/范围查询的索引
    LongPoint:long型用于精确/范围查询的索引
    FloatPoint:float型用于精确/范围查询的索引
    DoublePoint:double型用于精确/范围查询的索引
    SortedDocValuesField:存储每个文档BytesRef值的字段,索引用于排序,如果需要存储值,需要再用StoredField实例
    SortedSetDocValuesField:存储每个文档一组BytesRef值的字段,索引用于faceting/grouping/joining,如果需要存储值,需要再用StoredField实例
    NumericDocValuesField:存储每个文档long值的字段,用于scoring/sorting/值检索
    SortedNumericDocValuesField:存储每个文档一组long值的字段,用于scoring/sorting/值检索
    StoredField:用于在汇总结果中检索的仅存储值

段(Segments)

Lucene的索引可能是由多个子索引或Segments组成。每个Segment是一个完全独立地索引,可以单独用于搜索。索引涉及

    为新添加的documents创建新的segments
    合并已经存在的segments

搜索可能涉及多个segments或/和多个索引,每个索引可能由一组segments组成。

文档编号

Lucene通过一个整型的文档编号指向每个文档,第一个被加入索引的文档编号为0,后续加入的文档编号依次递增。注意文档编号是可能发生变化的,所以在Lucene外部存储这些值时需要格外小心。
索引结构概述

每个segment索引包括信息

    Segment info:包含有关segment的元数据,例如文档编号,使用的文件
    Field names:包含索引中使用的字段名称集合
    Stored Field values:对于每个document,它包含属性-值对的列表,其中属性是字段名称。这些用于存储有关文档的辅助信息,例如其标题、url或访问数据库的标识符
    Term dictionary:包含所有文档的所有索引字段中使用的所有terms的字典。字典还包括包含term的文档编号,以及指向term的频率和接近度的指针
    Term Frequency data:对于字典中的每个term,包含该term的所有文档的数量以及该term在该文档中的频率,除非省略频率(IndexOptions.DOCS)
    Term Proximity data:对于字典中的每个term,term在每个文档中出现的位置。注意,如果所有文档中的所有字段都省略位置数据,则不会存在
    Normalization factors:对于每个文档中的每个字段,存储一个值,该值将乘以该字段上的匹配的分数
    Term Vectors:对于每个文档中的每个字段,可以存储term vector,term vector由term文本和term频率组成
    Per-document values:与存储的值类似,这些也以文档编号作为key,但通常旨在被加载到主存储器中以用于快速访问。存储的值通常用于汇总来自搜索的结果,而每个文档值对于诸如评分因子是有用的
    Live documents:一个可选文件,指示哪些文档是活动的
    Point values:可选的文件对,记录索引字段尺寸,以实现快速数字范围过滤和大数值(例如BigInteger、BigDecimal(1D)、地理形状交集(2D,3D))

文件命名

属于一个段的所有文件具有相同的名称和不同的扩展名。当使用复合索引文件,这些文件(除了段信息文件、锁文件和已删除的文档文件)将压缩成单个.cfs文件。当任何索引文件被保存到目录时,它被赋予一个从未被使用过的文件名字。
文件扩展名摘要
名称     文件扩展名     简短描述
Segments File     segments_N     保存了一个提交点(a commit point)的信息
Lock File     write.lock     防止多个IndexWriter同时写到一份索引文件中
Segment Info     .si     保存了索引段的元数据信息
Compound File     .cfs,.cfe     一个可选的虚拟文件,把所有索引信息都存储到复合索引文件中
Fields     .fnm     保存fields的相关信息
Field Index     .fdx     保存指向field data的指针
Field Data     .fdt     文档存储的字段的值
Term Dictionary     .tim     term词典,存储term信息
Term Index     .tip     到Term Dictionary的索引
Frequencies     .doc     由包含每个term以及频率的docs列表组成
Positions     .pos     存储出现在索引中的term的位置信息
Payloads     .pay     存储额外的per-position元数据信息,例如字符偏移和用户payloads
Norms     .nvd,.nvm     .nvm文件保存索引字段加权因子的元数据,.nvd文件保存索引字段加权数据
Per-Document Values     .dvd,.dvm     .dvm文件保存索引文档评分因子的元数据,.dvd文件保存索引文档评分数据
Term Vector Index     .tvx     将偏移存储到文档数据文件中
Term Vector Documents     .tvd     包含有term vectors的每个文档信息
Term Vector Fields     .tvf     字段级别有关term vectors的信息
Live Documents     .liv     哪些是有效文件的信息
Point values     .dii,.dim     保留索引点,如果有的话

锁文件

默认情况下,存储在索引目录中的锁文件名为“write.lock”。如果锁目录与索引目录不同,则锁文件将命名为“XXXX-write.lock”,其中XXXX是从索引目录的完整路径导出的唯一前缀。此锁文件确保每次只有一个写入程序在修改索引。

    翻译参考:http://t.cn/RsOPjTS

    补充参考:http://t.cn/RsWQAiV

 

    Lucene的索引结构是有层次结构的,主要分以下几个层次:
     
     
    索引(Index):
    在Lucene中一个索引是放在一个文件夹中的。
    如上图,同一文件夹中的所有的文件构成一个Lucene索引。
    段(Segment):
    一个索引可以包含多个段,段与段之间是独立的,添加新文档可以生成新的段,不同的段可以合并。
    如上图,具有相同前缀文件的属同一个段,图中共三个段 "_0" 和 "_1"和“_2”。
    segments.gen和segments_X是段的元数据文件,也即它们保存了段的属性信息。
    文档(Document):
    文档是我们建索引的基本单位,不同的文档是保存在不同的段中的,一个段可以包含多篇文档。
    新添加的文档是单独保存在一个新生成的段中,随着段的合并,不同的文档合并到同一个段中。
    域(Field):
    一篇文档包含不同类型的信息,可以分开索引,比如标题,时间,正文,作者等,都可以保存在不同的域里。
    不同域的索引方式可以不同,在真正解析域的存储的时候,我们会详细解读。
    词(Term):
    词是索引的最小单位,是经过词法分析和语言处理后的字符串。
     更多对应的文件后缀
     
    名称
    文件拓展名
    描述
    段文件
    segments_N    保存了索引包含的多少段,每个段包含多少文档。
    段元数据
    .si    保存了索引段的元数据信息
    锁文件
    write.lock    防止多个IndexWriter同时写到一份索引文件中。
    复合索引文件
    .cfs, .cfe    把所有索引信息都存储到复合索引文件中。
    索引段的域信息
    .fnm
    保存此段包含的域,以及域的名称和域的索引类型。
    索引段的文档信息
    .fdx, .fdt
    保存此段包含的文档,每篇文档中包含的域以及每个域的信息。
     
    索引段Term信息
    .tim, .tip
    .tim文件中存储着每个域中Term的统计信息且保存着指向.doc, .pos, and .pay 索引文件的指针。
     
    .tip文件保存着Term 字典的索引信息,可支持随机访问。
     
    文档中Term词频和跳表信息
    .doc
    保存此段中每个文档对应的Term频率信息。
    文档中Term的位置信息
    .pos
    保存此段中每个文档对应的Term位置信息。
    文档的有效载荷和部分位置信息
    .pay
    保存此段中每个文档的有效载体(payload) 和 Term的位置信息(offsets)。 其中有一部分的Term位置信息存储在.pos文件中。
    索引字段加权因子
    .nvd, .nvm    
    .nvm 文件保存索引字段加权因子的元数据
     
    .nvd 文件保存索引字段加权数据
     
    索引文档加权因子
    .dvd, .dvm
    .dvm 文件保存索引文档加权因子的元数据
     
    .dvd 文件保存索引文档加权数据
     
    索引矢量数据
    .tvx, .tvd, .tvf
    .tvd 存储此段文档的Term、Term频率、位置信息、有效载荷等信息。
     
    .tvx 索引文件,用于把特定的文档加载到内存。
     
    .tvf 保存索引字段的矢量信息。
     
    有效文档
    .liv
    保存有效文档的索引文件信息
    Lucene的索引结构中,即保存了正向信息,也保存了反向信息。
     
    所谓正向信息:
     
    按层次保存了从索引,一直到词的包含关系:索引(Index) –> 段(segment) –> 文档(Document) –> 域(Field) –> 词(Term)
    也即此索引包含了那些段,每个段包含了那些文档,每个文档包含了那些域,每个域包含了那些词。
    既然是层次结构,则每个层次都保存了本层次的信息以及下一层次的元信息,也即属性信息,比如一本介绍中国地理的书,应该首先介绍中国地理的概况, 以及中国包含多少个省,每个省介绍本省的基本概况及包含多少个市,每个市介绍本市的基本概况及包含多少个县,每个县具体介绍每个县的具体情况。
    如上图,包含正向信息的文件有:
    segments_N保存了此索引包含多少个段,每个段包含多少篇文档。
    XXX.fnm保存了此段包含了多少个域,每个域的名称及索引方式。
    XXX.fdx,XXX.fdt保存了此段包含的所有文档,每篇文档包含了多少域,每个域保存了那些信息。
    XXX.tvx,XXX.tvd,XXX.tvf保存了此段包含多少文档,每篇文档包含了多少域,每个域包含了多少词,每个词的字符串,位置等信息。
    所谓反向信息:
     
    保存了词典到倒排表的映射:词(Term) –> 文档(Document)
    如上图,包含反向信息的文件有:
    XXX.tis,XXX.tii保存了词典(Term Dictionary),也即此段包含的所有的词按字典顺序的排序。
    XXX.frq保存了倒排表,也即包含每个词的文档ID列表。
    XXX.prx保存了倒排表中每个词在包含此词的文档中的位置。
    在了解Lucene索引的详细结构之前,先看看Lucene索引中的基本数据类型。
     
     
    二、基本类型
     
    Lucene索引文件中,用以下基本类型来保存信息:
     
    Byte:是最基本的类型,长8位(bit)。
    UInt32:由4个Byte组成。
    UInt64:由8个Byte组成。
    VInt:
    变长的整数类型,它可能包含多个Byte,对于每个Byte的8位,其中后7位表示数值,最高1位表示是否还有另一个Byte,0表示没有,1表示有。
    越前面的Byte表示数值的低位,越后面的Byte表示数值的高位。
    例如130化为二进制为 1000, 0010,总共需要8位,一个Byte表示不了,因而需要两个Byte来表示,第一个Byte表示后7位,并且在最高位置1来表示后面还有一个Byte, 所以为(1) 0000010,第二个Byte表示第8位,并且最高位置0来表示后面没有其他的Byte了,所以为(0) 0000001。
     
     
     
     
    Chars:是UTF-8编码的一系列Byte。
    String:一个字符串首先是一个VInt来表示此字符串包含的字符的个数,接着便是UTF-8编码的字符序列Chars。
     
    三、基本规则
     
    Lucene为了使的信息的存储占用的空间更小,访问速度更快,采取了一些特殊的技巧,然而在看Lucene文件格式的时候,这些技巧却容易使我们感到困惑,所以有必要把这些特殊的技巧规则提取出来介绍一下。
     
    在下不才,胡乱给这些规则起了一些名字,是为了方便后面应用这些规则的时候能够简单,不妥之处请大家谅解。
     
    1. 前缀后缀规则(PREFIX+SUFFIX)
     
    Lucene在反向索引中,要保存词典(Term Dictionary)的信息,所有的词(Term)在词典中是按照字典顺序进行排列的,然而词典中包含了文档中的几乎所有的词,并且有的词还是非常的长 的,这样索引文件会非常的大,所谓前缀后缀规则,即当某个词和前一个词有共同的前缀的时候,后面的词仅仅保存前缀在词中的偏移(offset),以及除前 缀以外的字符串(称为后缀)。
     
    [图]前缀后缀规则
     
     
     
    比如要存储如下词:term,termagancy,termagant,terminal,
     
    如果按照正常方式来存储,需要的空间如下:
     
    [VInt = 4] [t][e][r][m],[VInt = 10][t][e][r][m][a][g][a][n][c][y],[VInt = 9][t][e][r][m][a][g][a][n][t],[VInt = 8][t][e][r][m][i][n][a][l]
     
    共需要35个Byte.
     
    如果应用前缀后缀规则,需要的空间如下:
     
    [VInt = 4] [t][e][r][m],[VInt = 4 (offset)][VInt = 6][a][g][a][n][c][y],[VInt = 8 (offset)][VInt = 1][t],[VInt = 4(offset)][VInt = 4][i][n][a][l]
     
    共需要22个Byte。
     
    大大缩小了存储空间,尤其是在按字典顺序排序的情况下,前缀的重合率大大提高。
     
    2. 差值规则(DELTA)
     
    在Lucene的反向索引中,需要保存很多整型数字的信息,比如文档ID号,比如词(Term)在文档中的位置等等。
     
    由上面介绍,我们知道,整型数字是以VInt的格式存储的。随着数值的增大,每个数字占用的Byte的个数也逐渐的增多。所谓差值规则(Delta)就是先后保存两个整数的时候,后面的整数仅仅保存和前面整数的差即可。
     
    [图]差值规则
     
     
     
    比如要存储如下整数:16386,16387,16388,16389
     
    如果按照正常方式来存储,需要的空间如下:
     
    [(1) 000, 0010][(1) 000, 0000][(0) 000, 0001],[(1) 000, 0011][(1) 000, 0000][(0) 000, 0001],[(1) 000, 0100][(1) 000, 0000][(0) 000, 0001],[(1) 000, 0101][(1) 000, 0000][(0) 000, 0001]
     
    供需12个Byte。
     
    如果应用差值规则来存储,需要的空间如下:
     
    [(1) 000, 0010][(1) 000, 0000][(0) 000, 0001],[(0) 000, 0001],[(0) 000, 0001],[(0) 000, 0001]
     
    共需6个Byte。
     
    大大缩小了存储空间,而且无论是文档ID,还是词在文档中的位置,都是按从小到大的顺序,逐渐增大的。
     
    3. 或然跟随规则(A, B?)
     
    Lucene的索引结构中存在这样的情况,某个值A后面可能存在某个值B,也可能不存在,需要一个标志来表示后面是否跟随着B。
     
    一般的情况下,在A后面放置一个Byte,为0则后面不存在B,为1则后面存在B,或者0则后面存在B,1则后面不存在B。
     
    但这样要浪费一个Byte的空间,其实一个Bit就可以了。
     
    在Lucene中,采取以下的方式:A的值左移一位,空出最后一位,作为标志位,来表示后面是否跟随B,所以在这种情况下,A/2是真正的A原来的值。
     
     
    如果去读Apache Lucene - Index File Formats这篇文章,会发现很多符合这种规则的:
     
    .frq文件中的DocDelta[, Freq?],DocSkip,PayloadLength?
    .prx文件中的PositionDelta,Payload? (但不完全是,如下表分析)
    当然还有一些带?的但不属于此规则的:
     
    .frq文件中的SkipChildLevelPointer?,是多层跳跃表中,指向下一层表的指针,当然如果是最后一层,此值就不存在,也不需要标志。
    .tvf文件中的Positions?, Offsets?。
    在此类情况下,带?的值是否存在,并不取决于前面的值的最后一位。
    而是取决于Lucene的某项配置,当然这些配置也是保存在Lucene索引文件中的。
    如Position和Offset是否存储,取决于.fnm文件中对于每个域的配置(TermVector.WITH_POSITIONS和TermVector.WITH_OFFSETS)
    为什么会存在以上两种情况,其实是可以理解的:
     
    对于符合或然跟随规则的,是因为对于每一个A,B是否存在都不相同,当这种情况大量存在的时候,从一个Byte到一个Bit如此8倍的空间节约还是很值得的。
    对于不符合或然跟随规则的,是因为某个值的是否存在的配置对于整个域(Field)甚至整个索引都是有效的,而非每次的情况都不相同,因而可以统一存放一个标志。
    文章中对如下格式的描述令人困惑:
    Positions -->  Freq
    Payload -->
    PositionDelta和Payload是否适用或然跟随规则呢?如何标识PayloadLength是否存在呢?
    其实PositionDelta和Payload并不符合或然跟随规则,Payload是否存在,是由.fnm文件中对于每个域的配置中有关Payload的配置决定的(FieldOption.STORES_PAYLOADS) 。
    当Payload不存在时,PayloadDelta本身不遵从或然跟随原则。
    当Payload存在时,格式应该变成如下:Positions -->  Freq
    从而PositionDelta和PayloadLength一起适用或然跟随规则。
     
    4. 跳跃表规则(SKIP LIST)  
     
    为了提高查找的性能,Lucene在很多地方采取的跳跃表的数据结构。
     
    跳跃表(Skip List)是如图的一种数据结构,有以下几个基本特征:
     
    元素是按顺序排列的,在Lucene中,或是按字典顺序排列,或是按从小到大顺序排列。
    跳跃是有间隔的(Interval),也即每次跳跃的元素数,间隔是事先配置好的,如图跳跃表的间隔为3。
    跳跃表是由层次的(level),每一层的每隔指定间隔的元素构成上一层,如图跳跃表共有2层。
     
     
    需要注意一点的是,在很多数据结构或算法书中都会有跳跃表的描述,原理都是大致相同的,但是定义稍有差别:
     
    对间隔(Interval)的定义: 如图中,有的认为间隔为2,即两个上层元素之间的元素数,不包括两个上层元素;有的认为是3,即两个上层元素之间的差,包括后面上层元素,不包括前面的上 层元素;有的认为是4,即除两个上层元素之间的元素外,既包括前面,也包括后面的上层元素。Lucene是采取的第二种定义。
    对层次(Level)的定义:如图中,有的认为应该包括原链表层,并从1开始计数,则总层次为3,为1,2,3层;有的认为应该包括原链表层,并 从0计数,为0,1,2层;有的认为不应该包括原链表层,且从1开始计数,则为1,2层;有的认为不应该包括链表层,且从0开始计数,则为0,1层。 Lucene采取的是最后一种定义。
    跳跃表比顺序查找,大大提高了查找速度,如查找元素72,原来要访问2,3,7,12,23,37,39,44,50,72总共10个元素,应用跳 跃表后,只要首先访问第1层的50,发现72大于50,而第1层无下一个节点,然后访问第2层的94,发现94大于72,然后访问原链表的72,找到元 素,共需要访问3个元素即可。
     
    然而Lucene在具体实现上,与理论又有所不同,在具体的格式中,会详细说明。
————————————————
版权声明:本文为CSDN博主「aa1215018028」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/aa1215018028/article/details/86024184