ES_flattened
一、 flattened
类型是什么?为什么需要它?
flattened
是一种特殊的映射类型,它将一个完整的 JSON 对象“扁平化”处理,将其所有子字段的值合并为一个字段,并将这个字段索引为一个统一的 keyword
类型的集合。
核心思想: 牺牲字段级查询的精度,换取映射的简洁性、存储的效率和对未知字段的包容性。
1. 面临的问题:映射爆炸
想象一个日志系统,每条日志都有一个 headers
字段,里面是来自不同客户端、浏览器的 HTTP 请求头。这些请求头的键(如 User-Agent
, X-Forwarded-For
, Accept-Language
, 以及各种自定义头)千变万化。
如果使用默认的动态映射("dynamic": "true"
),Elasticsearch 会为每一个新出现的关键字(如 headers.User-Agent
, headers.X-Forwarded-For
)创建一个新的映射字段。对于一个大型系统,这可能会在短时间内产生成千上万个字段。后果是:
- 集群状态(Cluster State)膨胀:映射信息存储在集群状态中,并同步到每个节点。过大的映射会使集群状态变得臃肿,导致管理操作(如创建新索引)变慢,甚至引发稳定性问题。
- 内存压力:大量的字段会消耗大量的 JVM 堆内存。
2. flattened
提供的解决方案
使用 flattened
类型,你只需要定义一个字段,例如 headers
,类型为 flattened
。无论传入的对象有多少个键值对,ES 都不会为内部的键(如 User-Agent
)创建独立的映射条目。整个 headers
对象在映射中只表现为一个字段。
它将:
{"headers": {"User-Agent": "Mozilla/5.0", "X-Custom-ID": "abc123"}}
索引为类似于:
["User-Agent": "Mozilla/5.0", "X-Custom-ID": "abc123"]
(概念上,非实际存储格式)
所有子字段的值都被视为 keyword
,这意味着它们可以被搜索、过滤和聚合,但不能被分词,也不能进行范围查询等需要特定数据类型的操作。
二、 flattened
类型的工作原理与特性
- 存储机制:将整个 JSON 对象扁平化为一系列键值对,并将所有值作为
keyword
类型进行处理和索引。 - 映射开销:无论子字段有多少,在映射中只计为一个字段。这是它解决映射爆炸的关键。
- 查询能力:
- 支持:精确匹配查询(
term
,terms
)、前缀查询(prefix
)、存在性查询(exists
)等基于keyword
的查询。 - 不支持:全文搜索(
match
query)、数值范围查询(range
on numbers)、日期查询等。因为 ES 不知道也不关心子字段值的具体数据类型。
- 支持:精确匹配查询(
三、 结合实际案例:电商平台商品属性与日志处理
案例一:电商商品动态规格(Specifications)
在电商系统中,不同品类的商品拥有完全不同的属性。手机的属性是 ["cpu", "memory", "screen_size"]
,而衣服的属性是 ["color", "size", "material"]
。为每个属性预先定义映射是不现实的。
1. 映射设计:
我们使用 flattened
类型来存储这些动态变化的规格参数。
PUT /products
{"mappings": {"properties": {"title": {"type": "text"},"price": {"type": "float"},"specs": { // 动态规格属性,使用flattened类型"type": "flattened"}}}
}
2. 索引文档:
插入一个手机和一个衣服商品。
// 插入一部手机
POST /products/_doc/1
{"title": "高性能智能手机","price": 3999,"specs": {"cpu": "骁龙8 Gen 2","memory": "12GB","storage": "256GB","screen_size": "6.7英寸"}
}// 插入一件衣服
POST /products/_doc/2
{"title": "纯棉T恤","price": 99,"specs": {"color": "白色","size": "L","material": "100%棉"}
}
映射中始终只有 title
, price
, specs
三个字段,尽管 specs
对象内部结构完全不同。
3. 查询示例:
-
查找所有内存为 12GB 的手机:
GET /products/_search {"query": {"term": {"specs": "12GB"} // 注意:这是在整个flattened字段中搜索值} }
这个查询可能会找到内存是12GB的手机,也可能找到描述中有“12GB”的其他商品,不够精确。
-
更精确的查找:查找 CPU 是“骁龙8 Gen 2” 且 内存是“12GB”的商品:
GET /products/_search {"query": {"bool": {"must": [{"term": {"specs.cpu": "骁龙8 Gen 2" // 使用点号语法指定子字段}},{"term": {"specs.memory": "12GB"}}]}} }
这才是
flattened
类型的正确用法:通过flattened_field_name.sub_field_name
来精确查询特定的键值对。ES 虽然不为specs.cpu
创建映射,但在内部索引中仍然保留了键和值的关系,支持这种形式的查询。
案例二:集中式日志管理(如 HTTP Access Log)
1. 映射设计:
PUT /app-logs-2023-10
{"mappings": {"properties": {"@timestamp": {"type": "date"},"message": {"type": "text"},"http": {"type": "flattened" // 存储HTTP请求和响应信息},"user_agent": { // 单独拿出来,因为可能需要单独分析"type": "text"}}}
}
2. 索引一条日志:
POST /app-logs-2023-10/_doc
{"@timestamp": "2023-10-27T10:00:00.000Z","message": "GET /api/v1/products HTTP/1.1 200 1234","http": {"request": {"method": "GET","headers": {"Accept": "application/json","X-Request-ID": "abc-123"}},"response": {"status_code": 200,"body": {"bytes": 1234}}},"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}
3. 查询示例:
-
查找所有包含特定追踪ID的请求:
GET /app-logs-2023-10/_search {"query": {"term": {"http.request.headers.X-Request-ID": "abc-123"}} }
-
统计所有非200状态的请求:
GET /app-logs-2023-10/_search {"query": {"bool": {"must_not": [{"term": {"http.response.status_code": 200}}]}} }
四、 架构师视角:flattened
的优缺点与最佳实践
优点:
- 防止映射爆炸:这是其最核心的价值,完美解决了不可知字段或海量动态字段带来的映射膨胀问题。
- 减少存储开销:相比为每个子字段创建完整映射,
flattened
存储更高效。 - 保持一定的查询能力:虽然功能受限,但仍支持关键的精确查询和聚合,在很多时候已经足够。
缺点与限制:
- 查询功能受限:无法进行全文搜索、数值计算和范围查询(数字被当作关键字)。你不能对
flattened
里的数字子字段做gte
/lte
范围过滤。 - 不支持高亮:无法对
flattened
字段的内容进行高亮显示。 - 索引开销:由于需要索引所有内容,写入时可能会比严格的
keyword
映射稍慢。
最佳实践与决策树:
-
何时使用
flattened
?- 数据源是用户生成的,其结构不受你控制(如表单提交、API参数)。
- 字段名是动态的、不可枚举的(如 HTTP 头、标签、商品属性)。
- 你主要需要对字段进行存在性检查、精确匹配和简单的聚合。
- 你的首要目标是控制映射大小,维护集群健康度。
-
何时使用其他方案?
- 需要对子字段进行全文搜索 -> 使用动态映射 +
text
子字段(如果字段数可控)。 - 需要对子字段进行数值/日期范围查询 -> 考虑在应用层提前解析,并将重要字段作为顶级字段单独定义。
- 数据结构是固定的、已知的 -> 使用明确的
object
类型并预定义子字段。 - 数组中的对象需要独立查询(如博客评论)-> 使用
nested
类型。
- 需要对子字段进行全文搜索 -> 使用动态映射 +
决策流程:
遇到动态对象
-> 需要防止映射爆炸吗?
-> 是
-> 只需要精确匹配/存在性查询吗?
-> 是
-> 使用 flattened
。
否
-> 需要范围查询/全文搜索吗?
-> 是
-> 考虑应用层处理或忍受映射爆炸的风险。
总结
flattened
类型是 Elasticsearch 映射工具箱中一把非常专业的“手术刀”。它并非万能,但在特定的场景下——即需要高效、安全地处理大量未知或动态字段,且查询模式较为简单时——它是无可替代的最佳选择。
将其与 object
、nested
和动态映射策略结合使用,可以构建出既灵活又稳健的数据模型,从容应对真实世界中海量复杂数据的挑战。