Elasticsearch 高级聚合分析:百分位数统计与存储原理剖析
在生产级别的监控系统中,评估系统性能通常不仅看平均值,更关注长尾延迟。例如,TP50、TP90 和 TP99 是衡量用户体验的核心指标。它们分别代表 50%、90% 和 99% 的请求落在了多长的耗时范围内。
百分位数聚合分析实战
首先,我们创建一个名为 service_metrics 的索引,模拟记录不同地区的 API 响应耗时数据。
PUT /service_metrics
{
"mappings": {
"properties": {
"response_time": { "type": "long" },
"region": { "type": "keyword" },
"timestamp": { "type": "date" }
}
}
}
POST /service_metrics/_bulk
{ "index": {}}
{ "response_time" : 120, "region" : "华东", "timestamp" : "2023-10-01" }
{ "index": {}}
{ "response_time" : 85, "region" : "华东", "timestamp" : "2023-10-01" }
{ "index": {}}
{ "response_time" : 310, "region" : "华南", "timestamp" : "2023-10-01" }
{ "index": {}}
{ "response_time" : 150, "region" : "华东", "timestamp" : "2023-10-01" }
{ "index": {}}
{ "response_time" : 600, "region" : "华南", "timestamp" : "2023-10-01" }
{ "index": {}}
{ "response_time" : 220, "region" : "华南", "timestamp" : "2023-10-01" }
通过 percentiles 聚合,我们可以一次性获取多个分位点的数值:
GET /service_metrics/_search
{
"size": 0,
"aggs": {
"rt_percentiles": {
"percentiles": {
"field": "response_time",
"percents": [50, 90, 99]
}
},
"average_rt": {
"avg": {
"field": "response_time"
}
}
}
}
基于 SLA 的百分位等级分析
在服务等级协议(SLA)中,我们通常需要统计:有多少比例的请求在 200ms 以内?有多少在 500ms 以内?这需要用到 percentile_ranks 聚合。
GET /service_metrics/_search
{
"size": 0,
"aggs": {
"by_region": {
"terms": {
"field": "region"
},
"aggs": {
"sla_stats": {
"percentile_ranks": {
"field": "response_time",
"values": [200, 500]
}
}
}
}
}
}
Elasticsearch 在计算百分位数时采用了 TDigest 算法。为了在海量数据中保持性能,它使用近似算法而非全量排序。你可以通过 compression 参数控制精度:数值越大,结果越精确,但内存消耗也越高(默认值为 100)。
聚合分析的存储模型:Doc Values
Elasticsearch 的搜索依赖于"倒排索引",但聚合、排序和脚本执行则依赖于"正排索引",即 Doc Values。其核心特性如下:
- 列式存储:在索引阶段(Index-time)与倒排索引同步生成,数据按列组织,非常适合聚合计算。
- 持久化存储:Doc Values 存储在磁盘中。Elasticsearch 依赖操作系统的文件系统缓存(OS Cache)来加速访问。
- 内存策略:官方建议将主要的内存留给 OS Cache 而非 JVM Heap。例如,64GB 的服务器,建议 JVM 设置为 16GB-31GB 之间,剩余内存用于系统缓存,以保障 Doc Values 的高效读取。
分词字段的聚合:Fielddata
默认情况下,text 类型的字段是不支持聚合的,因为它们没有 Doc Values。Doc Values 无法处理经过分词器处理的字符串字段。如果必须对 text 字段进行聚合,需要开启 fielddata。
PUT /service_metrics/_mapping
{
"properties": {
"description": {
"type": "text",
"fielddata": true
}
}
}
Fielddata 的原理与代价:
1. 运行时构建:Fielddata 是在查询执行时实时生成的,它会将整个分词后的倒排索引加载到 JVM 内存中,重新构建正排结构。
2. 内存压力:由于 Fielddata 完全驻留在 JVM 堆内存中,对大型索引开启此功能极易导致 OOM(内存溢出)或频繁的 GC 停顿。
3. 建议:尽量使用 keyword 类型进行聚合。如果需要对一段文字既进行全文检索又进行聚合,建议使用 fields 多字段定义,分别为其配置 text 和 keyword 类型。
