调整分片大小

edit

调整分片大小

edit

Elasticsearch中的每个索引被划分为一个或多个分片,每个分片可以跨多个节点进行复制,以防止硬件故障。如果你正在使用数据流,那么每个数据流都由一系列索引支持。单个节点上可以存储的数据量是有限的,因此你可以通过添加节点并增加索引和分片的数量来提高集群的容量。然而,每个索引和分片都有一些开销,如果你将数据分散到过多的分片中,开销可能会变得难以承受。一个拥有过多索引或分片的集群被称为过度分片。过度分片的集群在响应搜索时效率较低,在极端情况下甚至可能变得不稳定。

创建分片策略

edit

防止过度分片和其他分片相关问题的最佳方法是创建一个分片策略。分片策略帮助您确定并维护集群的最佳分片数量,同时限制这些分片的大小。

不幸的是,没有一种放之四海而皆准的分片策略。在一个环境中有效的策略在另一个环境中可能无法扩展。一个好的分片策略必须考虑你的基础设施、使用场景和性能预期。

创建分片策略的最佳方法是使用与生产环境中相同的查询和索引负载,在生产硬件上对生产数据进行基准测试。有关我们推荐的方法,请观看定量集群规模调整视频。在测试不同的分片配置时,使用Kibana的Elasticsearch监控工具来跟踪集群的稳定性和性能。

Elasticsearch 节点的性能通常受限于底层存储的性能。 请查看我们关于优化存储以提高索引搜索速度的建议。

以下部分提供了一些在设计分片策略时应考虑的提醒和指南。如果您的集群已经过度分片,请参阅减少集群的分片数量

大小考虑

edit

在构建分片策略时,请记住以下几点。

每个分片上的搜索运行在一个单线程上

edit

大多数搜索会命中多个分片。每个分片在一个CPU线程上运行搜索。虽然一个分片可以运行多个并发搜索,但跨大量分片的搜索可能会耗尽节点的搜索线程池。这可能导致低吞吐量和搜索速度变慢。

每个索引、分片、段和字段都有开销

edit

每个索引和每个分片都需要一些内存和CPU资源。在大多数情况下,少量的大分片比许多小分片使用的资源更少。

段在分片的资源使用中扮演着重要角色。大多数分片包含多个段,这些段存储其索引数据。Elasticsearch将一些段元数据保存在堆内存中,以便可以快速检索以进行搜索。随着分片的增长,其段会被合并成更少、更大的段。这减少了段的数目,意味着堆内存中保存的元数据更少。

每个映射字段在内存使用和磁盘空间方面也会带来一些开销。默认情况下,Elasticsearch 会自动为每个文档中的每个字段创建映射,但您可以关闭此行为,以控制您的映射

此外,每个段都需要为每个映射字段分配少量堆内存。每个段每个字段的堆内存开销包括字段名称的副本,如果适用则使用ISO-8859-1编码,否则使用UTF-16编码。通常这不会引起注意,但如果你的分片具有高段数且相应的映射包含高字段数和/或非常长的字段名称,你可能需要考虑这一开销。

Elasticsearch 自动在数据层内平衡分片

edit

集群的节点被分组到数据层中。在每个层中, Elasticsearch 会尝试将索引的分片分布到尽可能多的节点上。当 您添加新节点或节点发生故障时,Elasticsearch 会自动在层的剩余节点上重新平衡索引的 分片。

最佳实践

edit

在适用的情况下,使用以下最佳实践作为分片策略的起点。

删除索引,而不是文档

edit

已删除的文档不会立即从Elasticsearch的文件系统中移除。 相反,Elasticsearch会在每个相关的分片上标记该文档为已删除。被标记的文档将继续占用资源,直到在周期性的 段合并过程中被移除。

如果可能,请删除整个索引。Elasticsearch 可以直接从文件系统中立即删除已删除的索引并释放资源。

使用数据流和ILM进行时间序列数据处理

edit

数据流允许您在多个基于时间的后备索引中存储时间序列数据。您可以使用索引生命周期管理 (ILM)来自动管理这些后备索引。

这种设置的一个优势是 自动滚动,当当前索引达到定义的max_primary_shard_sizemax_agemax_docsmax_size阈值时,会创建一个新的写入索引。当索引不再需要时,您可以使用ILM自动删除它并释放资源。

ILM 还使您能够轻松地随着时间的推移更改分片策略:

  • 想要减少新索引的分片数量?
    在数据流的index.number_of_shards设置中更改 匹配索引模板
  • 想要更大的分片或更少的后备索引?
    增加您的ILM策略的滚动更新阈值
  • 需要跨越更短时间间隔的索引?
    通过更快地删除较旧的索引来抵消增加的分片数量。您可以通过降低策略的 删除阶段min_age阈值来实现这一点。

每个新的后备索引都是一个进一步调整策略的机会。

目标是将分片大小控制在最多2亿个文档,或者在10GB到50GB之间

edit

每个分片都存在一些开销,无论是在集群管理方面还是在搜索性能方面。搜索一千个50MB的分片将比搜索一个包含相同数据的50GB分片要昂贵得多。然而,非常大的分片也可能导致搜索变慢,并且在故障后恢复时间更长。

分片的物理大小没有严格的限制,理论上每个分片最多可以包含略超过二十亿个文档。然而,经验表明,对于许多用例来说,分片大小在10GB到50GB之间通常表现良好,只要每个分片的文档数量保持在2亿以下。

根据您的网络和使用情况,您可能可以使用更大的分片,而对于企业搜索和类似用例,较小的分片可能更为合适。

如果你使用ILM,将滚动更新操作max_primary_shard_size 阈值设置为 50gb 以避免分片大于50GB, 并将 min_primary_shard_size 阈值设置为 10gb 以避免分片小于10GB。

要查看当前分片的大小,请使用cat shards API

GET _cat/shards?v=true&h=index,prirep,shard,store&s=prirep,store&bytes=gb

The pri.store.size 值显示了索引中所有主分片的总大小。

index                                 prirep shard store
.ds-my-data-stream-2099.05.06-000001  p      0      50gb
...

如果一个索引的分片由于超过推荐的50GB大小而导致性能下降,您可以考虑调整索引分片的大小。分片是不可变的,因此它们的大小是固定的,所以必须通过纠正设置来复制索引。这需要首先确保有足够的磁盘空间来复制数据。之后,您可以通过以下选项之一使用纠正的设置复制索引的数据:

  • 运行 Split Index 以增加主分片的数量
  • 创建一个具有正确设置的目标索引,然后运行 Reindex

请注意,执行恢复快照和/或 克隆索引将不足以解决分片的规模问题。

一旦源索引的数据被复制到目标索引中,源索引就可以被删除。然后,您可以考虑为目标索引设置创建别名,以便源索引的名称指向它以保持连续性。

请参阅此修复分片大小视频以获取示例故障排除演练。

主节点应至少为每3000个索引分配1GB的堆内存

edit

主节点可以管理的索引数量与其堆大小成正比。每个索引所需的堆内存的确切数量取决于各种因素,例如映射的大小和每个索引的分片数量。

一般来说,每个主节点的堆内存每GB应少于3000个索引。例如,如果你的集群有每个堆内存为4GB的专用主节点,那么你应该少于12000个索引。如果你的主节点不是专用主节点,那么同样的容量指导原则适用:你应该为集群中的每个主节点保留至少1GB的堆内存,每3000个索引。

请注意,此规则定义了主节点可以管理的索引数量的绝对最大值,但并不保证涉及如此多索引的搜索或索引性能。您还必须确保您的数据节点有足够的资源来处理您的负载,并且您的整体分片策略满足所有性能要求。另请参阅每个分片上的搜索运行在一个线程上每个索引、分片、段和字段都有开销

要检查每个节点的堆配置大小,请使用cat nodes API

GET _cat/nodes?v=true&h=heap.max

您可以使用cat shards API来检查每个节点的分片数量。

GET _cat/shards?v=true

添加足够的节点以保持在集群分片限制内

edit

集群分片限制防止在每个节点上创建超过1000个非冻结分片,以及在每个专用冻结节点上创建超过3000个冻结分片。确保您的集群中有足够数量的每种类型节点来处理您所需的分片数量。

为字段映射器和开销分配足够的堆内存

edit

映射字段在每个节点上消耗一些堆内存,并要求数据节点额外占用堆内存。 确保每个节点有足够的堆内存用于映射,并且还要为与其工作负载相关的开销预留额外空间。以下部分展示了如何确定这些堆内存需求。

集群状态中的映射元数据
edit

集群中的每个节点都有一份集群状态的副本。 集群状态包括每个索引的字段映射信息。这些信息会占用堆内存。您可以使用 集群统计 API 来获取去重和压缩后所有映射的总堆内存开销。

GET _cluster/stats?human&filter_path=indices.mappings.total_deduplicated_mapping_size*

这将显示类似于以下示例输出的信息:

{
  "indices": {
    "mappings": {
      "total_deduplicated_mapping_size": "1gb",
      "total_deduplicated_mapping_size_in_bytes": 1073741824
    }
  }
}
检索堆大小和字段映射器开销
edit

您可以使用Nodes stats API来获取每个节点的两个相关指标:

  • 每个节点上的堆大小。
  • 每个节点上字段的任何额外估计堆开销。这特定于数据节点,除了上述集群状态字段信息外,数据节点还为每个映射字段增加了额外的堆开销。对于不是数据节点的节点,此字段可能为零。
GET _nodes/stats?human&filter_path=nodes.*.name,nodes.*.indices.mappings.total_estimated_overhead*,nodes.*.jvm.mem.heap_max*

对于每个节点,这将显示如以下示例输出所示的信息:

{
  "nodes": {
    "USpTGYaBSIKbgSUJR2Z9lg": {
      "name": "node-0",
      "indices": {
        "mappings": {
          "total_estimated_overhead": "1gb",
          "total_estimated_overhead_in_bytes": 1073741824
        }
      },
      "jvm": {
        "mem": {
          "heap_max": "4gb",
          "heap_max_in_bytes": 4294967296
        }
      }
    }
  }
}
考虑额外的堆开销
edit

除了上述两个字段开销指标外,您还必须为Elasticsearch的基本使用以及如索引、搜索和聚合等工作负载留出足够的堆内存。对于许多合理的工作负载,额外的0.5GB堆内存就足够了,如果工作负载非常轻,您可能需要更少,而繁重的工作负载可能需要更多。

示例
edit

作为一个例子,考虑上述数据节点的输出。该节点的堆至少需要:

  • 集群状态字段信息的1 GB。
  • 数据节点字段的额外估计堆开销为1 GB。
  • 其他开销的额外堆为0.5 GB。

由于节点在示例中具有最大4GB的堆大小,因此对于总共需要的2.5GB堆大小来说是足够的。

如果节点的堆最大大小不足,请考虑 避免不必要的字段, 或者扩展集群,或者重新分配索引分片。

请注意,上述规则并不一定能保证涉及大量索引的搜索或索引性能。您还必须确保您的数据节点有足够的资源来处理您的负载,并且您的整体分片策略满足所有性能要求。另请参阅每个分片上的搜索运行在一个线程上每个索引、分片、段和字段都有开销

避免节点热点

edit

如果过多的分片被分配到特定的节点,该节点可能会成为热点。例如,如果单个节点包含过多具有高索引量的索引分片,该节点可能会出现问题。

为了防止热点问题,使用 index.routing.allocation.total_shards_per_node 索引 设置来明确限制单个节点上的分片数量。您可以使用 更新索引设置 API 配置 index.routing.allocation.total_shards_per_node

PUT my-index-000001/_settings
{
  "index" : {
    "routing.allocation.total_shards_per_node" : 5
  }
}

避免不必要的映射字段

edit

默认情况下,Elasticsearch 自动为每个文档中的每个字段创建映射。每个映射字段对应于磁盘上的一些数据结构,这些数据结构对于在该字段上进行高效的搜索、检索和聚合是必需的。每个映射字段的详细信息也保存在内存中。在许多情况下,这种开销是不必要的,因为一个字段不会用于任何搜索或聚合。使用 显式映射 而不是动态映射,以避免创建从未使用的字段。如果一组字段通常一起使用,请考虑使用 copy_to 在索引时将它们合并。如果一个字段很少使用,最好将其设为 运行时字段

您可以使用字段使用统计 API 获取有关正在使用哪些字段的信息,并且可以使用分析索引磁盘使用情况 API 分析映射字段的磁盘使用情况。但请注意,不必要的映射字段也会带来一些内存开销以及它们的磁盘使用。

减少集群的分片数量

edit

如果你的集群已经过度分片,你可以使用以下一种或多种方法来减少其分片数量。

创建覆盖更长时间段的指数

edit

如果你使用ILM并且你的保留策略允许,避免为翻转操作使用max_age阈值。相反,使用max_primary_shard_size来避免创建空索引或许多小的分片。

如果您的保留策略需要一个max_age阈值,请增加它以创建覆盖更长时间间隔的索引。例如,您可以按周或月创建索引,而不是每天创建索引。

删除空索引或不需要的索引

edit

如果您正在使用ILM并基于max_age阈值滚动索引, 您可能会无意中创建没有文档的索引。这些空索引 没有任何好处,但仍然会消耗资源。

您可以使用cat count API找到这些空索引。

GET _cat/count/my-index-000001?v=true

一旦你有了一个空索引的列表,你可以使用删除索引 API来删除它们。你也可以删除任何其他不需要的索引。

DELETE my-index-000001

在非高峰时段强制合并

edit

如果你不再向索引写入数据,你可以使用强制合并 API将较小的段合并成较大的段。 这可以减少分片开销并提高搜索速度。然而,强制合并是资源密集型的。如果可能的话,请在非高峰时段运行强制合并。

POST my-index-000001/_forcemerge

将现有索引缩减为更少的分片

edit

如果你不再向索引写入数据,你可以使用shrink index API来减少其分片数量。

ILM 还为暖阶段中的索引提供了一个收缩操作

合并较小的索引

edit

您还可以使用reindex API将具有相似映射的索引合并到一个大型索引中。对于时间序列数据,您可以将短期时间段的索引重新索引到一个涵盖更长时间段的新索引中。例如,您可以将10月份的每日索引(具有共享的索引模式,如my-index-2099.10.11)重新索引到一个每月的my-index-2099.10索引中。重新索引后,删除较小的索引。

POST _reindex
{
  "source": {
    "index": "my-index-2099.10.*"
  },
  "dest": {
    "index": "my-index-2099.10"
  }
}

排查分片相关错误

edit

以下是如何解决常见的分片相关错误。

此操作将增加 [x] 个分片,但当前集群已打开 [y]/[z] 个最大分片;

edit

集群设置 cluster.max_shards_per_node 限制了集群中每个节点的最大打开分片数。此错误表示某个操作将超出此限制。

如果你确信你的更改不会使集群不稳定,你可以使用集群更新设置 API临时增加限制并重试该操作。

PUT _cluster/settings
{
  "persistent" : {
    "cluster.max_shards_per_node": 1200
  }
}

这种增加应该是暂时的。作为长期解决方案,我们建议您向过度分片的数据层添加节点或减少集群的分片数量。要在做出更改后获取集群的当前分片数量,请使用集群统计API

GET _cluster/stats?filter_path=indices.shards.total

当长期解决方案到位时,我们建议您重置cluster.max_shards_per_node限制。

PUT _cluster/settings
{
  "persistent" : {
    "cluster.max_shards_per_node": null
  }
}

更多信息,请参阅排查分片容量问题

分片中的文档数量不能超过 [2147483519]

edit

每个 Elasticsearch 分片都是一个独立的 Lucene 索引,因此它共享 Lucene 的 MAX_DOC 限制,即最多只能有 2,147,483,519 ((2^31)-129) 个文档。这个每个分片的限制适用于 docs.count 加上 docs.deleted 的总和,如 索引统计 API 所报告的。超过此限制将导致如下错误:

Elasticsearch exception [type=illegal_argument_exception, reason=Number of documents in the shard cannot exceed [2147483519]]

此计算可能与Count API的计算不同,因为Count API不包括嵌套文档,也不计算已删除的文档。

这个限制远高于每个分片大约2亿文档的推荐最大文档数量

如果你遇到这个问题,尝试通过使用 Force Merge API 来合并一些已删除的文档以缓解它。例如:

POST my-index-000001/_forcemerge?only_expunge_deletes=true

这将启动一个异步任务,可以通过任务管理API进行监控。

可能还有助于删除不需要的文档, 或者将索引拆分重新索引到具有更多分片的索引中。