巧用es flattened
好久没更新了 今天讲下如何借助ES的flatten数据类型解决字段膨胀的问题
背景介绍
目前商城业务中需要使用ES完成一项特殊的排序逻辑:任意一件商品可以被运营划分到多个品类下,当遇到节假日或者热点时,运营同学需要干预商品排序,即在某些品类下的商品可以按照特定字段值进行排序,没有设置运营排序值的则需要按照综合分进行排序
问题描述
我们知道ES并不擅长做数据关联,那么如何实现一件商品可以按照不同的品类权重值进行排序呢?
解决思路
这里想到的是借助ES的嵌套结构来实现,即每一个嵌套子文档都是一个k/v结构,key为品类id,value为排序值,如下:
"navigator_sort" : {
"895729" : 0,
"895839" : 0 ,
"895567" : 100,
"898804" : 0,
"898805" : 0,
"895566" : 75
}
如果需要按照指定的品类进行排序,那么可以这么做:
"sort": [
{
"navigator_sort.895567": {
"order": "desc"
}
}
]
面临的问题
默认情况下,Elasticsearch 会在提取nested字段中内容时,会自动映射文档中包含的字段。 虽然这是使用 Elasticsearch 的最简单方法,但随着时间的推移,它往往会导致字段爆炸,并且 Elasticsearch 的性能将受到 “内存不足(out of memory)” 错误以及索引和查询数据时性能不佳的影响。
什么是字段爆炸?
Elasticsearch 必须为每个新字段更新集群状态,并且该集群状态必须传递给所有节点。跨节点的集群状态传输是单线程操作 , 因此要更新的字段映射越多,完成更新所需的时间就越长。这种延迟通常以性能不佳的集群而结束,有时会导致整个集群停机。这被称为 “映射爆炸(mapping explosion)”。
这也是 Elasticsearch 从 5.x 及更高版本开始将索引中的字段数限制为 1,000 个的原因之一。如果我们的字段数超过 1,000,我们必须手动更改默认索引字段限制(使用 index.mapping.total_fields.limit 设置)或者我们需要重新考虑我们的架构。
替代方案 flattened
使用 Elasticsearch Flattened数据类型,具有大量嵌套字段的对象被视为单个关键字字段。 换句话说,Elasticsearch 扁平化数据类型用于有效减少Mapping中包含的字段数量,同时仍允许我们查询扁平化数据
# 构建索引
PUT my-index-000001
{
"mappings": {
"properties": {
"title": {
"type": "text"
},
"create_user": {
"type": "keyword"
},
"full_path_json": {
"type": "flattened",
"similarity": "boolean"
}
}
}
}
# 保存一条这样的数据
{
"title" : "Something really urgent",
"create_user" : "1",
"full_path_json" : [
{
"op_user" : "A",
"folders" : [
"v3",
"v4",
"v3tv4"
],
"A" : {
"op_time" : "2022-01-12",
"is_share" : 1,
"in_folder" : 1
}
},
{
"op_user" : "2",
"2" : {
"op_time" : "2022-12-12",
"is_share" : 1
}
}
]
}
# 查询和排序
GET my-index-000001/_search
{
"sort": [
{
"full_path_json.A.op_time": {
"order": "desc"
}
}
],
"query": {
"bool": {
"filter": [
{
"term": {
"title": "urgent"
}
},
{
"term": {
"full_path_json.folders": "f3"
}
}
]
}
}
}
虽然可以查询在单个字段中 “扁平化” 的嵌套字段,但需要注意某些限制。 扁平对象中的所有字段值都存储为keyword 【 keyword类型字段不进行任何类型的文本分词(text tokenization)或分析,而是按原样存储】
失去的特性:
1、失去了使用不区分大小写的查询的能力,这样你就不必输入完全匹配的查询
2、排序仅支持按照字符大小进行排序