我正在尝试将数据发送到 Amazon OpenSearch Service 集群。但是,我收到一个断路异常错误,指出我的数据太大。为什么会出现这种情况,我该怎样解决呢?
简短描述
当请求到达 OpenSearch Service 节点时,断路器会估算加载所需数据所需的内存量。然后,OpenSearch Service 会将估算的大小与配置的堆大小限制进行比较。如果估算的数据大小大于可用的堆大小,则查询将终止。因此,将抛出 CircuitBreakerException 以防止节点过载。
OpenSearch Service 使用以下断路器来防止 JVM OutofMemoryError 异常:
- 请求
- Fielddata
- 未完成的请求
- 核算
- 父断路器
**注意:**了解这五个断路器中的哪一个引发了异常非常重要,因为每个断路器都有自己的调优需求。有关断路器类型的更多信息,请参阅 Elasticsearch 网站上的断路器设置。
要获取每个节点和每个断路器的当前内存使用情况,请使用以下命令:
GET _nodes/stats/breaker
另请注意,断路器只是一种最大努力的机制。虽然断路器为防止节点过载提供了一定的弹性,但您最终仍可能会收到 OutOfMemoryError。断路器仅在明确预留了内存的情况下才能跟踪内存,因此并非总能预先估算出确切的内存使用量。例如,如果您有少量内存堆,则未跟踪内存的相对开销会更大。有关断路器和节点弹性的更多信息,请参阅 Elasticsearch 网站上的使用实存断路器提高节点弹性。
为防止数据节点过载,请按照排查高 JVM 内存压力部分中提供的提示执行操作。
解决方法
断路器异常
如果您使用的是 Elasticsearch 版本 7.x 和更高版本以及 16GB 的堆,则在达到断路器限制时会收到以下错误:
{
"error": {
"root_cause": [{
"type": "circuit_breaking_exception",
"reason": "[parent] Data too large, data for [<http_request>] would be [16355096754/15.2gb], which is larger than the limit of [16213167308/15gb]",
"bytes_wanted": 16355096754,
"bytes_limit": 16213167308
}],
"type": "circuit_breaking_exception",
"reason": "[parent] Data too large, data for [<http_request>] would be [16355096754/15.2gb], which is larger than the limit of [16213167308/15gb]",
"bytes_wanted": 16355096754,
"bytes_limit": 16213167308
},
"status": 503
}
此示例输出表明要处理的数据太大,以致于父断路器无法处理。父断路器(断路器类型)负责集群的整体内存使用情况。当发生父级断路器异常时,表示所有断路器使用的总内存量超过了设置的限制。当集群超过 16GB 的 95%(即 15.2GB 堆)时,父断路器会引发异常。
您可以通过计算内存使用量和设置的断路器限制之间的差值来验证此逻辑。使用示例输出中的值,并从“[16213167308/15gb] 的限制”中减去“实际使用量:[15283269136/14.2gb]”。此计算表明,该请求需要大约 1.02GB 的预留新字节内存才能成功处理请求。但是,在此示例中,当数据请求进入时,集群的可用空闲内存堆少于 0.8GB。因此,断路器会跳闸。
断路器异常消息可以这样解释:
- [<http_request>**] 的数据:**客户端向集群中的节点发送 HTTP 请求。OpenSearch Service 会在本地处理请求,或者将其传递到另一个节点进行其他处理。
- **将为 [#]:**处理请求时堆大小的外观。
- **[#] 的限制:**当前断路器限制。
- **实际使用量:**JVM 堆的实际使用量。
- **预留新字节:**处理请求所需的实际内存。
JVM 内存压力
断路异常通常是由高 JVM 内存压力造成的。JVM 内存压力是指用于您的集群中所有数据节点的 Java 堆的百分比。检查 Amazon CloudWatch 中的 JVMMemoryPressure 指标以确定当前使用量。
**注意:**数据节点的 JVM 堆大小设置为物理内存 (RAM) 大小的一半,最大为 32GB。例如,如果物理内存 (RAM) 为每个节点 128GB,则堆大小仍将为 32GB(最大堆大小)。否则,堆大小将计算为物理内存大小的一半。
高 JVM 内存压力可能是由以下原因造成的:
- 对集群的请求数量增加。检查 Amazon CloudWatch 中的 IndexRate 和 SearchRate 指标以确定当前负载。
- 聚合、通配符以及在查询中使用了较宽的时间范围。
- 各节点间的分区分配不平衡或者一个集群中的分区太多。
- 索引映射激增。
- 使用 fielddata 数据结构来查询数据。Fielddata 可能会消耗大量堆空间,并在分段的生命周期内保留在堆中。因此,使用 fielddata 时,集群上的 JVM 内存压力仍然很高。有关更多信息,请参阅 Elasticsearch 网站上的 Fielddata。
排查高 JVM 内存压力
要解决高 JVM 内存压力的问题,请尝试按照以下提示操作:
- 减少传入到集群的流量,尤其是在工作负载繁重的情况下。
- 考虑扩展集群以获取更多 JVM 内存来支持您的工作负载。
- 如果无法扩展集群,请尝试通过删除旧索引或未使用的索引来减少分区数量。由于分区元数据存储在内存中,因此减少分区数量可以减少总体内存使用量。
- 启用慢速日志以识别错误的请求。
**注意:**在启用配置更改之前,请验证 JVM 内存压力是否低于 85%。这样做可以避免给现有资源带来额外的开销。
- 优化搜索和索引请求,然后选择正确的分区数。有关索引和分区计数的更多信息,请参阅开始使用 OpenSearch Service:我需要多少个分区?
- 禁用和避免使用 fielddata。 默认情况下,除非在索引映射中明确定义,否则文本字段上的 fielddata 将设置为“false”。
- 使用重建索引 API 或创建或更新索引模板 API,将索引映射类型更改为关键字。您可以将关键字类型用作对文本字段执行聚合和排序操作的替代方法。
- 避免对文本字段进行聚合以防止字段数据增加。如果使用更多字段数据,则会占用更多的堆空间。使用集群统计 API 操作检查您的字段数据。
- 使用以下 API 调用清除 fielddata 缓存:
POST /index_name/_cache/clear?fielddata=true (index-level cache)
POST */_cache/clear?fielddata=true (cluster-level cache)
**警告:**如果您清除 fielddata 缓存,则任何正在进行的查询都可能会中断。
有关更多信息,请参阅如何排查 OpenSearch Service 集群上的高 JVM 内存压力问题?
相关信息
Amazon OpenSearch Service 最佳实践
如何提高我的 Amazon OpenSearch Service 集群上的索引性能?