!声明:本文翻译自《A Dive into the Elasticsearch Storage》
本文我们将研究Elasticsearch各功能模块写入数据目录中的文件。我们将分别从节点层面,索引层面和分片层面进行了解,并简单解释他们的内容,以帮助大家了解Elasticsearch写入磁盘的数据。
Elasticsearch的路径配置
Elasticsearch有许多路径配置
- path.home: 运行Elasticsearch进程用户的home目录。默认值为JAVA系统变量
user.dir
,该变量的默认值为进程拥有者的home目录 - path.conf: 包含配置文件的目录。该路径通常通过JAVA系统变量
es.config
设置,因为该值需要在配置文件被找到前解析。 - path.plugins: 包含Elasticsearch插件子目录的文件目录。该目录支持符号链接,当同一个可执行程序运行多个Elasticsearch实例时,该特性可以为其中某个Elasticsearch实例选择性禁用和启用一系列插件。
- path.work: 曾用于存储Elasticsearch运行时的临时文件,已废弃。
- path.log: 存储日志的目录。建议将该目录与数据目录的存储空间隔离,防止其中一个空间耗尽影响另一个。
- path.data: Elasticsearch数据文件存储的目录
本文,我们将详细查看数据目录(path.data)的内容,并研究这些文件的用途。
这些文件来自哪里
因为Elasticsearch底层分片层面使用Lucene来索引和查询,因此数据目录中的文件是由Elasticseach和Lucene共同写入的。
两者的分工很明确:Lucene负责写入和维护Lucene索引文件,Elasticsearch负责写入和维护构建在Lucene之上特性的元数据文件,如字段映射,索引设置和其他集群元数据(底层Lucene不支持但Elasticsearch支持的用户侧或支撑侧特性)。
在深入研究Lucene索引文件之前,我们先来看一下由Elasticsearch创建的数据目录外层结构。
节点数据
我们先简单地从Elasticsearch集群的空数据目录开始,其目录结构如下
代码语言:txt复制$ tree data
data
└── elasticsearch
└── nodes
└── 0
├── _state
│ └── global-0.st
└── node.lock
node.lock
文件用于保证同一时刻只有一个Elasticsearch实例正在读取和写入该数据目录。
global-0.st
是一个比较有意思的文件。global-
前缀表明这是一个全局状态文件,.st
后缀表明这是一个包含元数据的状态文件。正如你所猜测的那样,这个二进制文件包含集群的全局元数据,前缀后面的数字表示集群元数据的版本(集群提供的一个严格的版本机制)
尽管在紧急状况下使用hex编辑器修改这些文件在技术上是可行的,但强烈建议不要这样做,因为这样会很快导致数据丢失。
索引数据
接着我们为Elasticsearch创建一个单分片索引,观察Elasticsearch的文件变化
代码语言:txt复制$ curl localhost:9200/foo -XPOST -H 'Content-Type: application/json' -d '{"settings":{"index.number_of_shards": 1}}'
{"acknowledged":true}
$ tree -h data
data
└── [ 102] elasticsearch
└── [ 102] nodes
└── [ 170] 0
├── [ 102] _state
│ └── [ 109] global-0.st
├── [ 102] indices
│ └── [ 136] foo
│ ├── [ 170] 0
│ │ ├── .....
│ └── [ 102] _state
│ └── [ 256] state-0.st
└── [ 0] node.lock
我们可以看到一个新的目录被创建了出来,文件夹的名字是索引的名称。该目录有两个子目录:_state
和0
。前者包含了索引的状态文件(indices/{index-name}/_state/state-{version}.st
),该文件包含了索引的元数据如创建时间戳。它还包含了一个唯一ID以及索引的settings和mappings。后者包含了索引的第一个分片的相关数据。接下来我们将进一步查看该文件。
分片数据
分片数据目录包含一个该分片的状态文件,该文件包含分片的版本信息以及该分片是主分片还是副本分片。
代码语言:txt复制$ tree -h data/elasticsearch/nodes/0/indices/foo/0
data/elasticsearch/nodes/0/indices/foo/0
├── [ 102] _state
│ └── [ 81] state-0.st
├── [ 170] index
│ ├── [ 36] segments.gen
│ ├── [ 79] segments_1
│ └── [ 0] write.lock
└── [ 102] translog
└── [ 17] translog-1429697028120
在早期的Elasticsearch版本中,分片数据目录中有单独的 {shard_id}/index/_checksums-
(和.cks-
文件)。当前版本,这些checksums文件放置在在lucene文件的尾部,因为Lucene为它们的所有的索引文件增加了校验和机制。
{shard_id}/index
目录包含了Lucene拥有的文件。Elasticsearch不会直接操作这个目录(除了早期为了实现校验和机制)。该目录下的文件占据了Elasticsearch数据目录的绝大多数空间。
在进入Lucene世界之前,我们来看一下Elasitcsearch的transaction log, 该文件毫无疑问存放在每个分片的translog
目录,前缀为tanslog-
。transaction log对Elasticsearch的功能和性能非常重要,因此我们在下一段更进一步对它进行介绍。
分片的Transaction Log
Elasticsearch的Transaction log可以保证数据在不用对每个文档进行Lucene底层commit操作的前提下安全地索引到Elasticsearch中。对一个Lucene进行commit操作会在Lucene层面创建一个segment,该segment会写入同步到磁盘(fyync()-ed),导致大量的磁盘I/O操作,影响Elasticsearch性能。
为了在不执行一个完整Lucene commit的前提下,向Elasticsearch写入一个文件并让其可以被检索,Elasticsearch将该文档添加到Lucene IndexWriter
并将其追加到transaction log。每个refresh_interval
时间间隔,它会调用Lucene索引的reopen()方法,该方法可以让没有执行commit的数据可以被检索。当IndexWriter
由于自动flush或显式flush操作而进行周期性commit后,之前的transaction log被丢弃,并产生一个新的transactionlog。
当需要进行恢复操作时,写入磁盘的segment会被先恢复,接着transaction log会被重放来保证没有进行完整commit操作的数据不会丢失。
Lucene索引文件
Lucene在管理Lucene索引目录中文件方面表现出色,可以参考下表(表中的Lucene文档链接详细介绍了从了Lucene2.1到现在的文件变化,可以了解一下)
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 index目录下还会有一个segments.gen
文件。该文件是包含当前(最新)segment_N文件信息的辅助文件,用于当文件系统列举目录信息不全时辅助生成最新的segment文件。
在老版本的Lucene中,还会有.del
后缀文件。这些文件和存活文档文件(.liv
)起到相同作用,用于保存删除文档列表。关于存活文档(Live Documents)和删除列表(deletion lists), 可以参考Elasticsearch from the Bottom Up
修复异常分片
由于Elasticsearch分片包含一个Lucene索引,我们可以使用Lucene的CheckIndex工具。这个工具可以帮助在损失尽可能少数据的情况下查看和修复异常。我们通常建议用户reindx数据,但是当无法进行reindx或用户数据非常重要时,这也是可以尝试的一种方法。尽管该方法需要一些手动操作,且因分片个数和大小的不同会有一定的耗时。
代码语言:txt复制Elasticsearch的安装包中默认包含了Lucene CheckIndex工具不需要额外下载
# change this to reflect your shard path, the format is
# {path.data}/{cluster_name}/nodes/{node_id}/indices/{index_name}/{shard_id}/index/
$ export SHARD_PATH=data/elasticsearch/nodes/0/indices/foo/0/index/
$ java -cp lib/elasticsearch-*.jar:lib/*:lib/sigar/* -ea:org.apache.lucene... org.apache.lucene.index.CheckIndex $SHARD_PATH
如果CheckIndex工具发现一个问题,并且其提供的修复建议值得被采用,可以在执行CheckIndex时添加-fix
参数来应用这些修复。
存储快照
你可能有疑问快照仓库中的文件是如何转换为Elasticsearch的底层存储文件的。为了解决这个疑惑,对当前集群,我们使用备份快照接口将索引备份到文件系统的my-snapshot
仓库,并查看仓库中的文件,我们会发现以下文件:
$ tree -h snapshots
snapshots
├── [ 31] index
├── [ 102] indices
│ └── [ 136] foo
│ ├── [1.2K] 0
│ │ ├── [ 350] __0
│ │ ├── [1.8K] __1
...
│ │ ├── [ 350] __w
│ │ ├── [ 380] __x
│ │ └── [8.2K] snapshot-my-snapshot
│ └── [ 249] snapshot-my-snapshot
├── [ 79] metadata-my-snapshot
└── [ 171] snapshot-my-snapshot
我们可以在根目录包含一个index文件,该文件包含了该仓库下所有快照的信息。每个快照有一个关联的snaphost-
和metadata-
文件。snapshot-
文件包含快照的状态信息,快照归属的索引等信息。metadata-
文件包含集群在执行快照时的元数据。
当压缩被设置为开启时(
compress:true
),metadata-
和snapshot-
会使用LZF进行压缩。该压缩算法侧重于压缩和解压速度,比较适合于Elasticsearch快照场景。快照数据有一个header,该header是一个ZV 1位的标志位,表示数据是否被压缩。header后面有一个或多个64K压缩数据块,数据块格式为:2字节的block长度 2字节的压缩前大小 压缩数据。有了这些信息,用户可以使用LibLZF合适的decompresser对数据进行解压。如果想了解更多关于LZF的信息,可以参考LZFFormat。
在索引层面还有一个indices/{index_name}/snapshot-{snapshot_name}
文件,该文件包含了索引的元数据如索引在执行快照时的settings和mappings。
在分片层面有两种文件:重命名的Lucene索引文件和分片快照文件(indices/{index_name}/{shard_id}/snapshot-{snapshot_name}
)。分片快照文件包含了快照使用了分片目录下的哪些文件以及快照恢复时快照中的逻辑文件名称和磁盘中实际文件名称的映射关系等信息。它还包含了所有用于防止数据损坏文件的校验和,Lucene版本信息和大小信息等。
你可能疑惑为什么这些文件被重命名而不是使用它们的原始名称,这样可以更方便地进行磁盘操作。原因很简单:在进行备份快照前,该索引可能已经进行过了一次备份快照,销毁,重建的过程。在这种情况下,可能会出现多文件拥有相同的名字,但是不同的内容。
总结
本文我们通过多个层面查看了Elasticsearch数据目录下的文件:节点层面,索引层面以及分片层面。我们查看了Lucene索引文件在磁盘中的存储位置,并简单讨论了如何使用Lucene CheckIndex工具来验证和修复异常分片。
希望你永远不需要在Elasticseearch的数据目录内容上执行任何操作,但是了解你最喜爱的数据存储系统是如何向文件系统写入数据对你是有好处的。