较低的并发性更适合批量文档插入。某些并发性在某些情况下是有帮助的——It Depends™,我会详细介绍它——但并不是一个重大的或自动的胜利。
当涉及到 Elasticsearch 的写入性能时,有很多可以调整的地方。您应该检查一个非常快速的胜利:您的连接是否使用 HTTP keep-alive ?这将节省大量设置每个连接的 TCP 和 TLS 开销。仅仅这一改变就可以极大地提升性能,并且还可以为您的索引管道揭示一些有意义的架构注意事项。
所以检查一下,看看进展如何。从那里开始,我们应该从底部开始,然后向上努力。
磁盘上的索引是Lucene。 Lucene 是一个分段索引。这index这部分是您首先使用 Elasticsearch 的核心原因:可以在 O(log N) 时间内搜索排序术语的字典。这是超级快速和可扩展的。段部分是因为插入索引并不是特别快 - 根据您的实现,维护排序的成本为 O(log N) 或 O(N log N)。
所以 Lucene 的技巧是缓冲这些更新并附加一个新的段;本质上是迷你指数的集合。搜索一些相对较少数量的段仍然比每次更新时花费所有时间维护排序索引要快得多。随着时间的推移,Lucene 会照顾merging这些片段将它们保持在某个合理的大小范围内,并在此过程中删除已删除和覆盖的文档。
在 Elasticsearch 中,每个分片都是一个独特的 Lucene 索引。如果您的索引只有一个分片,那么拥有多个并发的批量更新流几乎没有什么好处。应用程序端的并发性可能会有一些好处,具体取决于索引管道收集和组装每批文档所需的时间。但在 Elasticsearch 方面,这只是一组缓冲区被写到一个又一个的段中。
分片使这变得更有趣。
Elasticsearch 的优势之一是能够分割跨多个分片的索引的数据。这有助于提高可用性,并有助于工作负载扩展到超出单个服务器的资源范围。
遗憾的是,并发性应该与索引所具有的主分片的数量相等或成比例,这并不那么简单。不过,作为一种粗略的启发式方法,这并不是一件可怕的事情。
您会看到,在内部,第一个处理请求的 Elasticsearch 节点会将批量请求转换为一系列单独的文档更新操作。每个文档更新都会发送到托管该文档所属分片的适当节点。响应由批量操作收集,以便它可以在响应中向客户端发送批量操作的摘要。
因此,此时,根据文档分片路由,在处理传入批量请求的过程中,某些分片可能比其他分片更繁忙。那有可能吗matter?我的直觉告诉我事实并非如此。这是可能的,但这将是不寻常的。
在我见过的大多数测试和分析中,以及根据我十多年使用 Lucene 的经验,索引的缓慢部分是将文档值转换为倒排索引格式。解析文本、将其分析为术语等可能非常复杂且成本高昂。只要批量请求具有足够的文档并且在分片之间分布得足够好,并发性就不会像在分片和段级别完成的工作饱和那样有意义。
在调整批量请求时,我的建议是这样的。
- 使用 HTTP 保持活动状态。这不是可选的。 (您正在使用 TLS,对吧?)
- 选择每个请求花费适当时间的批量大小。大概1秒左右,可能不会超过10秒。
- 如果您愿意,可以测量每个批量请求花费的时间,并动态地增加和缩小您的批次。
持久队列可以释放很多功能。如果可以获取和组装文档并将它们插入到 Kafka 中,那么该过程可以并行运行以使数据库饱和并并行化任何文档的非规范化或准备。然后,一个不同的进程从队列中提取并向服务器发送请求,并且通过一些轻微的协调,您可以在不同阶段测试和调整不同的并发性。当队列有助于将集群暂时置于只读模式时,队列还可以让您暂停各种迁移和维护任务的更新。
我在整个答案中都避免了复制,因为我建议调整复制的原因只有一个。那就是当您批量创建不服务任何生产流量的索引时。在这种情况下,它可以帮助通过服务器群节省一些资源,以关闭对索引的所有复制,并在索引基本上完成数据加载后启用复制。
最后,如果您无论如何提高并发性会怎么样?有什么风险?某些工作负载不控制并发性,并且没有时间或资源在搜索引擎前面放置队列。在这种情况下,Elasticsearch 可以避免相当大量的并发。它有相当充足的线程池来处理并发文档更新。如果这些线程池已饱和,它将拒绝响应,并显示 HTTP 429 错误消息和有关超出队列深度的明确消息。这些可能会影响集群的稳定性,具体取决于可用资源和索引中的分片数量。但这些都是非常引人注目的问题。
底线:不,相对于 1 个包含 200 个文档的批量,20 个并发批量(每个包含 10 个文档)可能不会提高性能。如果您的批量操作速度很快,您应该增加它们的大小,直到它们运行一两秒,或者出现问题。使用保活。如果存在其他应用程序端开销,请将并发性增加到 2 倍或 3 倍,并根据经验进行测量。如果索引对于任务至关重要,请使用快速、持久的队列。