字段基数过高引发内存问题,通常是因为对 text 类型字段开启了 fielddata 用于聚合或排序,最稳妥的处理是改用 keyword 类型或避免对此类字段做聚合。
先说结论:高基数字段内存爆炸本质是 heap 被 fielddata 占满,优先检查映射类型和聚合语句。
- 先定位:确认是哪个索引和字段触发了 circuit breaker 或堆内存告警。
- 先做:关闭 text 字段的 fielddata,或将需要聚合的字段改为 keyword 类型。注意:关闭后原有聚合查询会立即失败,需先检查业务兼容性。
- 再验证:观察节点堆内存使用率和 circuit breaker 日志是否不再频繁触发。
命令速用版
GET /_stats/fielddata?human=true
GET /_nodes/stats/breaker?human=true
GET /_cat/indices?v&s=docs.count:desc为什么会这样
Elasticsearch 中 text 字段默认不可聚合,若强行开启 fielddata,会将所有唯一 term 加载到堆内存。基数越高,唯一 term 越多,内存占用线性增长。相比之下,keyword 类型使用 doc_values,数据存在磁盘,不占堆内存。
分步处理
1. 确认问题字段
查看 fielddata 占用最高的索引:
GET /_stats/fielddata?human=true检查 circuit breaker 触发记录:
GET /_nodes/stats/breaker?human=true2. 检查映射设置
确认疑似字段是否为 text 且开启了 fielddata:
GET /<index_name>/_mapping若看到 "fielddata": true 在 text 字段上,这是高风险信号。
3. 动态关闭 Fielddata(临时止损)
若确认业务暂无聚合需求,可立即关闭 fielddata 释放内存:
PUT /<index_name>/_mapping
{
"properties": {
"<field_name>": {
"type": "text",
"fielddata": false
}
}
}注意:修改后内存不会立即释放,可能需要重启节点或执行 force merge,且原有依赖该字段的聚合查询会报错。
4. 重构索引(根本解决)
如果该字段需要聚合,建议重建索引并将类型改为 keyword。无法直接修改已有字段类型,需使用 reindex API。
POST _reindex
{
"source": {
"index": "<old_index>"
},
"dest": {
"index": "<new_index>"
},
"conflicts": "proceed"
}风险提示:Reindex 前务必备份数据。生产环境建议配合别名(Alias)切换,避免业务中断。
怎么验证是否生效
执行调整后,持续观察集群状态:
- 堆内存使用率是否稳定在 75% 以下,不再频繁 Full GC。
- 日志中不再出现
[fielddata] breaker或[parent] breaker相关错误。 - 使用
_stats/fielddata确认该字段内存占用下降或清零。
常见坑
- 不要为了临时解决问题而调大 circuit breaker 阈值,这只是推迟崩溃时间。
- 避免对 UUID 或时间戳等高基数字段进行 terms 聚合。
- 修改 mapping 类型必须 reindex,直接修改会报错,需提前规划索引生命周期。
- 业务兼容性:关闭 fielddata 前,务必确认没有业务查询依赖该字段的聚合或排序功能。
参考来源
- Elasticsearch Guide: Fielddata, https://www.elastic.co/guide/en/elasticsearch/reference/current/fielddata.html
- Elasticsearch Guide: Circuit Breakers, https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-breaker.html
- Elasticsearch Guide: Mapping, https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html