作者:刘晓国
你可以使用多种策略来增加批处理作业和/或在线交易的 Elasticsearch 写容量。在过去的几年中,在写入容量方面,我遇到了瓶颈,并在不同的 ES 群集上犯了许多错误。 尤其是其中一项要求是写入具有严格 SLA 的实时索引以进行读取操作时。
如果你在生产环境中使用 Elasticsearch,很可能你也已经遇到了这些问题,甚至可能犯了一些与过去相同的错误!
我认为,对 ES 如何在幕后工作的总体概述有一个清晰的了解,当你试图从系统中获得最佳性能时,这将大有帮助,因此,让我们开始吧。
简单来说,Elasticsearch 是由 Lucene 提供支持的搜索引擎。 它为 “搜索部分” 提供了强大的动力,而 Elasticsearch 为你提供了可扩展性,可用性,REST API,专门的数据结构等(从广义上来说)。
Elasticsearch 架构的概略示意图
从图中可以看出,Elasticsearch 在可用节点上对每个 Lucene 索引进行了分片。 分片可以是主分片或副本分片。
每个分片都是一个 Lucene 索引,这些索引中的每个索引都可以具有多个 segment,每个 segement 都是一个反向索引。
这些 segment 是在文档摄入期间创建的,并且是不可变的。 这意味着,当你编辑或删除 Lucene 段中已经存在的文档时,将创建一个新 segment,而不是更改前一个 segment。
这是要考虑的非常重要的事情。 由于这种架构,Elasticsearch 不支持更新,甚至不支持部分更新。
每次更新文档中的单个字段时,都会创建一个新的 segment,并且上一个 segment 中的信息会标记为已删除。
索引编制过程中的文件路径
在此图中,我们可以看到 Elasticsearch 如何存储新文档。一旦文档到达,它将被提交到一个称为 “translog” 的事务日志和一个内存缓冲区。
事务日志是ES如何恢复仅在发生故障时在内存中的数据的方式。当 “刷新/refresh” 操作发生时,内存缓冲区中的所有文档将生成一个内存中的 Lucene segment。此操作用于使新文档可供搜索。
最终,根据不同的触发条件(稍后将对此进行详细介绍),所有这些段都合并为一个 segment 并保存到磁盘中,并且清除了事务日志。
既然我们已经掌握了 Elasticsearch 如何保存信息的基本情况,那么我们就可以从调整开始了。
让我们探索从客户端到操作系统的各种方法,这些方法可以极大地提高其写入吞吐量和写入/索引编制的速度。
客户端策略
批量索引
ES 提供了用于文档索引的 Bulk API。正如人们所期望的那样,它使索引编制变得更快。建议进行一些基准测试,以确定适合你数据的批处理大小。
并行化
ES 本质上可以水平扩展-索引工作也应如此。确保将需要摄取的数据分布在可以并行运行的多个工作程序中。请注意在 ES 线程池队列已满时发生的错误,如TOO_MANY_REQUESTS(429)(并确保在发生这种情况时具有指数补偿)。
响应过滤
filter_path 参数,可用于减少 Elasticsearch 返回的响应。该参数采用逗号分隔的过滤器列表,该过滤器以点符号表示:
POST“ es-endpoint/index-name/_bulk?filter_path=-took,-items.index._index,-items.index._type”
保存前汇总
如果你要通过 “随机” 操作(例如用户更新其用户名)来更新文档,而不是运行已经具有汇总信息的批处理作业,则这一点尤其重要。
假设你使用 ES 在社交网络项目上进行全文搜索,并且每次用户获得新关注者时都需要更新用户文档。
你很快就会开始看到多个超时问题,并且全面滞后。
你可能只是在更新一个简单的字段,但是 ES 被迫删除当前文档并创建一个全新的文档!
一个简单的解决方案是使用 Redis,Kafka 或任何其他方式 “缓冲” 这些用户更改,然后将这些多个文档编辑聚合到一个文档中,然后再保存到 Elasticsearch。更好的是,按 “键” 分组一段时间,然后使用批量操作将这些文档发送到 ES。
文件策略
脚本编写
使用以下一些技巧可能很诱人:
1. "script" : { 2. "inline": "ctx._source.counter = ctx._source.value ? ctx._source.counter += 1 : 1" 3. }
尽管乍一看,使用脚本在更新或任何其他逻辑期间自动递增字段可能是一个好主意,但它伸缩性不佳,你甚至可以使用此类策略来破坏整个集群。 我必须承认,几年前我犯了这个错误……
特别是因为即使你设置了查询 timeout,也无法保证像本 github 问题中所述的那样在服务器端强制执行该查询。
Elastic 有一篇很好的文章介绍了一般文档的经验法则,请在此处阅读。
索引策略
Refresh Interval
这可能是在索引策略上发挥最大作用的配置之一。
刷新(refresh)操作使新数据可用于搜索。刷新时,仅存在于内存中的最近索引的数据将传输到新的 Lucene segement。此操作不会将这些段刷新/提交到磁盘,但是由于有 Translog,因此数据是安全的,稍后将对此进行更多说明。
如果索引在最近30秒内收到一个或多个搜索请求,则 Elasticsearch 将每秒执行一次刷新。
如果你正在使用 Elasticsearch,则你的系统可能已经为最终的一致性做好了准备,并且增加刷新间隔可能是你的理想选择。
你可以在批量摄入作业期间为索引设置较大的 refresh_interval,然后在作业完成后将其重新设置为标准值。如果你同时执行许多不同的并发作业,则你可以尝试了解你可以使用多大的 refresh_interval。
更多信息在这里。
自动生成的ID
由于 ES 无需检查唯一性,因此允许 ES 代表你生成 ID 可以提高文档创建速度。
在1.4版之前,使用自动生成的 ID 的性能提高更为明显。从那时起,大大提高了ES id 的查找速度,从而减少了使用 self-id 时的损失。
但这仅在使用 “Lucene友好” 格式的情况下才是正确的。可以在此处找到有关良好 ID 的精彩文章。
例子包括零填充的顺序 ID,UUID-1 和 nanotime。这些 ID 具有一致的顺序模式,可以很好地压缩。相反,诸如 UUID-4 之类的 ID 本质上是随机的,这提供了较差的压缩并降低了 Lucene 的速度。
也可以使用 “normal” 字段(也就是文档中的主键字段)来存储 “self-id”,但是请记住,你将失去一些优势,例如:
- 快速路由,即 ES 知道包含该文档的分片
- 唯一性保证
- 按 id 删除
禁用副本
如果你可以在批量索引作业期间禁用副本,那么它也将大大提高索引速度。
当你为文档建立索引时,每个副本将为该索引建立索引,从而增加了工作量,并且随着副本数目的增加而增加。
另一方面,如果在批量作业之前禁用它们,而仅在之后启用它们,则新信息将以序列化二进制格式复制,而无需分析或合并段。
节点策略
Indexing Buffer Size
此设置将控制 ES 将为索引保留多少内存。默认值为堆的 10%。由于这将在节点的所有索引之间共享,因此你需要确保每个索引至少分配了 512 MB。如果你增加了 Translog 的大小,则还需要增加这些设置以避免过早刷新。
有关这些设置的更多信息,请点击此处。
Translog
Translog 是 ES 事务日志。每个分片有1个,它随内存缓冲区一起更新,如果发生崩溃,它可用于将数据持久化到 Lucene 的磁盘中。当达到一定大小(默认值为 512 MB)时,它将触发刷新操作,该操作会将所有内存中的 Lucene 段提交到磁盘。此刷新操作采用 Lucene 提交的形式。提交期间,内存中的所有段都合并为一个段并保存到磁盘。
可以想象,这是一个昂贵的操作。你可以增加事务日志以在磁盘上创建更大的段(总体上性能更好),同时还可以减少这种昂贵的操作节奏。
负责该值的字段是 flush_threshold_size。在这里阅读更多有关它的信息。
Segment 合并限制(ES 6中已弃用)
你会在多篇文章中看到有关此设置的误导性信息,因此,我决定在此处进行特别说明:
从 Elasticsearch 2 开始,不再使用通常在合并开始落后时发生的 20MB/s节流阀的默认值。相反,ES 现在使用 Lucene 的自适应合并 IO 节流,该节流根据当前负载和服务器性能来更改 IO 限制。
在 Elasticsearch 6 中,如 indexs.store.throttle.max_bytes_per_sec 和 indices.store.throttle.type 之类的设置不再存在,如你在此处看到的那样。
跨集群复制
使用此设置,你可以将所有读取定向到 “follower” 索引,而仅写入 “leader” 索引。这样,你就不会出现读取与写入竞争的情况。
操作系统和服务器策略
禁用交换(disable swapping)
交换会破坏你的性能。 ES 占用大量内存,因此,如果你不小心,很可能会开始交换内存。
您有多种禁用交换的方法。
Filesystem Cache
Linux 自动使用空置的 RAM 来缓存文件。 Elastic 建议运行文件系统缓存的运行 Elasticsearch 的机器至少有一半内存可用。确保你未将 ES_HEAP_SIZE 设置为超过计算机内存的50%,以便其余空间可用于文件系统缓存。
你还应该避免拥有超过 32GB 的 HEAP,因为它将开始使用未压缩的指针,这将影响性能并使用双倍的内存。
储存类型
避免使用 NFS 或 EFS 之类的网络磁盘,并尝试始终使用 SSD。
像 AWS EBS 这样的硬件可能是一个不错的选择,但是直接连接的磁盘将总是更快,尤其是在您具有 RAID 设置的情况下。
这一切听起来如何?还有什么我漏掉的吗?或者你想让我继续拓展的吗?