起因
使用float类型字段去做聚合时会出现精度丢失,与在 _source 中看到的不符
复现
PUT /term-test
{
"settings": {
"refresh_interval": "1s",
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"demo_type": {
"properties": {
"float_field": {
"type": "float"
}
}
}
}
}
POST term-test/demo_type
{
"float_field": 0.06
}
POST term-test/demo_type
{
"float_field": 0.06000004
}
GET term-test/_search
{
"query": {
"match_all": {}
},
"aggs": {
"aa": {
"terms": {
"field": "float_field"
}
}
}
}
返回结果
"hits" : [
{
"_source" : {
"float_field" : 0.06
}
},
{
"_source" : {
"float_field" : 0.06000004
}
}
]
"aggregations" : {
"aa" : {
"buckets" : [
{
"key" : 0.05999999865889549,
"doc_count" : 1
},
{
"key" : 0.060000039637088776,
"doc_count" : 1
}
]
}
}
原因
- _soucre 返回的是插入时的原值,不会做更改,即使字段在mapping类型与插入的实际类型不一致,也不会做任何处理,所以实际做聚合的值与 _source 里的值有可能会不一样。
- mapping里有一个字段属性 doc_values,默认值为true,具体作用可见文档(doc-values),下面引用部分:
Doc values are the on-disk data structure, built at document index time, which makes this data access pattern possible. They store the same values as the _source but in a column-oriented fashion that is way more efficient for sorting and aggregations. Doc values are supported on almost all field types, with the notable exception of analyzed string fields.
简单来说ES为了聚合或排序时效率更好,把字段值以另外的方案存储起来,做聚合或者排序等操作时会使用这个值,这个值可以通过下面的方式查看(Doc value Fields)
GET term-test/_search
{
"docvalue_fields": ["float_field"],
"query": {
"match_all": {}
},
"aggs": {
"aa": {
"terms": {
"field": "float_field"
}
}
}
}
返回结果,注意观察 _source 和 fields 里的字段值
"hits" : [
{
"_source" : {
"float_field" : 0.06
},
"fields" : {
"float_field" : [
0.05999999865889549
]
}
},
{
"_source" : {
"float_field" : 0.06000004
},
"fields" : {
"float_field" : [
0.060000039637088776
]
}
}
]
"aggregations" : {
"aa" : {
"buckets" : [
{
"key" : 0.05999999865889549,
"doc_count" : 1
},
{
"key" : 0.060000039637088776,
"doc_count" : 1
}
]
}
}
解决方案
目前发现有两种解决方案来保证聚合结果的精度
- 字段改为使用 double 类型
- 字段改为使用 scaled_float 类型,同时还要设置都一个字段 scaling_factor,用来配置需要保留的精度,可以查看文档 scaled_float
但是以上的两种方式相对 float 类型都会增加存储空间,并且效率不如 float 类型,这也是es的Dynamic mapping里默认浮点数对应的类型是 float 的原因,Dynamic field mapping
拓展
查阅资料时还获取到了一些额外的信息
- _source里的内容不受mapping的影响,只会跟插入时一致
- doc_values会受mapping影响,比如说mapping的类型是integer,但是插入的值是浮点型,例如是1.7,实际保存的会是 1
- 倒排索引储存的字段值也会受mapping影响,所以如果query条件是大于1,那么也不会返回这条 document
- mapping字段有一个属性store,默认是false,如果设置为true时会额外存储这个字段的值,同样存储的值也会受mapping影响,文档:store,可以通过这种方式去获取这个值:stored_fields
下面是一些关于希望ES支持 BigDecimal 来保证浮点数精度的讨论
ps: 太长了有些没看完