ES doc_values介绍2——本质是field value的列存储,做聚合分析用,ES默认开启,会占用存储空间...

一、doc_values介绍

doc values是一个我们再三重复的重要话题了,你是否意识到一些东西呢?

  • 搜索时,我们需要一个“词”到“文档”列表的映射
  • 排序时,我们需要一个“文档”到“词“列表的映射,换句话说,我们需要一个在倒排索引的基础上建立的“正排索引”

这里的“正排索引”结构通常在其他系统中(如关系型数据库)被称为“列式存储”。本质上,它是在数据字段的一列上存储所有value,这种结构在某些操作上会表现得很高效,比如排序。

在ES里这种“列式存储”就是我们熟悉的“doc values”,默认情况下它是被启用的,doc values在index-time(索引期)被创建:当一个字段被索引时,ES会把“词”加入到倒排索引中,同时把这些词也加入到面向“列式存储”的doc values中(存储在硬盘上)。

doc values通常被应用在以下几个方面:

  • 基于一个字段排序
  • 基于一个字段聚合
  • 执行某些filter上(如:geolocation filter)
  • 在script(脚本)中引用了一个或多个字段

由于doc values在索引期被序列化到硬盘上,我们可以利用操作系统去快速的访问它们,关于doc values在磁盘上是如何被管理的,后面会讲到。

大多数的字段默认情况下都会被索引,这使得他们可以被搜索到,倒排索引允许一个查询基于一个词表排序,也可以快速访问包含某个词的文档列表。

排序、聚合,和在脚本中访问一些字段值时都需要另一种不同的访问方式,因为倒排索引不支持这种访问,所以我们需要一种结构能查询到文档到词的映射。

doc values是在索引期创建基于磁盘的数据结构,这种结构使得上述访问成为可能。doc values支持绝大部分字段类型,除了“analyzed”类型的string字段。(因为对于string的列存储会先转换为数字id再存)

所有的字段都默认支持doc values,如果你确定你不需要在某个字段上排序或者聚合或者在脚本中访问,你可以disable掉:

  1. status_code字段默认开启doc_values
  2. session_id字段禁用了doc_values,虽然被禁用但是还是可以被查询

TIP:doc_values可以在同一个索引的同名字段上设置不同值,它也可以基于一个已存在的字段使用put mapping api来禁用它。

看如下的倒排索引结构:


如果我们想为每一个包含“brown”的文档编辑一份完整的词列表,我们可能会用如下查询:

看上面的查询部分。倒排索引通过词条排好了序,所以我们首先找到包含“brown”的词条列表,然后跨列扫描所有包含“brown”的文档,这里我们很幸运的找到了“Doc_1”和“Doc_2”。

然后在聚合部分,我们需要找Doc_1和Doc_2中找到所有的词,在倒排索引的去做这个操作很非常昂贵的:意味着我们不得不迭代索引中的每一个词,看它们是否包含在doc_1和doc_2中,这个过程是非常缓慢的,而且也是非常傻逼的:因为随着文档词量的增加,我们聚合的执行时间也会增加。

让我们看看下面的结构:

有了这个结构我们就会很容易得到doc_1和doc_2所包含的词条,我们只需要通过上面的结构把两个集合合并起来就行了。
因此,查询和聚合是非常复杂的,查询文档使用的是倒排索引,聚合文档使用的是正排索引(doc_values)

note:doc values不仅仅是用在聚合中,还被用在排序、脚本、子父文档关系(这里暂不做介绍)。

二、深入Doc Values

前面讲到的doc values给我们几个印象:快速访问、高效、基于硬盘。现在我们来看看doc values到底是如何工作的?

doc values是在“索引期“随着倒排索引一起生成的,也就是说doc values是基于每个索引段生成且是不可改变的(immutable),和倒排索引一样,doc values也会被序列化到磁盘上,这使得它具有了高效性和可扩展性。

通过序列化一个数据结构到磁盘上,我们可以依赖操作系统的file system cache 替代JVM的堆内存,当我们的“工作集”小于OS可用内存时,操作系统会自然的加载这些doc values到内存。这时doc values的性能和在JVM堆内存中表现是一样的。

但是当工作集大于操作系统可用内存时,操作系统将会按需加载doc values,这种情况下的访问速度会明显的慢于全量加载doc values的时候。但这种操作使得我们的服务器内存利用率远超过服务器最大内存限制。试想一下,如果全量加载到doc values到内存中势必会造成ES OutOfMemery。

NOTE:由于doc values不受JVM堆内存管理,所以我们可以把ES对内存设置得小一点,把更多的内存留给操作系统来换出(doc values),同时这也可以使JVM的GC工作在更小的堆内存上,更快更高效的执行GC。
通常,我们配置JVM的堆内存基本和操作系统内存各占一半(50%),由于引进了doc values所以我们可以考虑把JVM的堆内存设置得小一些,比如我们可以在一个64G的服务器上设置JVM堆内存为4 – 16GB比设置堆内存为32G更加高效。

三、Column-store compression(列式存储压缩)

本质上doc values是一个被序列化的面向“列式储存”的结构,我们前面讨论过列式存储在某些查询操作上是有优势的,不仅如此它们也更擅长数据压缩,特别是数字,这对磁盘存储和快速访问来说是及其重要的。

为了了解它是如何压缩数据的,我们看下面简单的doc values结构

像上面这种每行一条数据的形式,我们可以得到连续的数字块,如:[100,1000,1500,1200,300,1900,4200]。因为我们知道它们都是数字值可以被排列在一起通过一个一致的偏移量。

跟深层次的,这里有几种压缩方法可以运用在这些数字上。你可能知道上面的数字都是100的倍数,如果索引段上所有的的数字都共享一个“最大公约数”,那么就可以用这个最大公约数去压缩数据。如上面的数字我们可以除以100,得到的数据是[1,10,15,12,3,19,42]。这样这些数字会变得小一些,存储时占用的比特数也会小一些。

doc values使用几种手段来压缩数字。

  1. 如果所有的数字值都相等(或者缺失),会设置一个标记来表示该值
  2. 如果所有数字值的个数小于256个,将会使用一个简单的编码表来压缩
  3. 如果大于了256个,看看是否存在最大公约数,存在则使用最小公倍数压缩
  4. 如果不存在最大公约数,则存储偏移量来压缩数字。

如你看到的,你可能会想“这样做对数值型字段做压缩确实很好,那么对字符串类型呢?”,其实字符串压缩也是和数字压缩一样采用同样的方法通过一个序数表来压缩,字符串被去重、排序后被赋予了一个ID,这些ID就是数字,这样就可以采用上面的方案进行压缩了。对于序数表本身也会采用压缩存储。

转自:http://h2ex.com/1631













本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/bonelee/p/6401531.html,如需转载请自行联系原作者


<think>我们正在处理Elasticsearch中的错误:“[term] query does not support array of values”。根据引用[1]中的错误信息,当我们尝试在term查询中使用数组值(例如"couponIds": [1,2])时,Elasticsearch会抛出这个错误。 原因分析: 根据引用[3]:Elasticsearch是基于Lucene构建的,而Lucene最初是为字符串检索设计的。term查询在底层是用于精确匹配单个值的,它不支持直接传递数组。因此,当我们在term查询中传入一个数组时,就会触发这个错误。 解决方案: 我们需要将数组查询转换为Elasticsearch支持的形式。以下是几种常见的方法: 1. 使用`terms`查询代替`term`查询: `terms`查询是专门用于多值精确匹配的,它接受一个数组。 将原来的: "term": { "couponIds": [1,2] } 改为: "terms": { "couponIds": [1,2] } 2. 如果需要对同一个字段进行多个term查询(例如使用不同的条件),可以使用`bool`查询组合多个`term`子句。但是,这里的需求是匹配多个值,所以`terms`查询是最直接的。 因此,修正后的查询应该如下: ```json GET goods/_search { "from": 0, "size": 10, "query": { "bool": { "must": [ { "terms": { "couponIds": [1,2] } } ] } } } ``` 注意:`terms`查询会匹配字段中包含数组中任意一个值的文档(相当于OR操作)。如果需要同时匹配多个值(AND操作),则需要使用`bool`查询结合多个`term`查询,或者使用脚本查询,但这通常不是`terms`查询的用途。 另外,引用[2]提到了一个关于执行提示(execution hint)的注意事项,但这个问题我们的错误无关。引用[4]展示了返回查询结果的部分代码,也不直接相关。 总结:错误的原因是错误地使用了`term`查询来传入数组值,而应该使用`terms`查询。 如果用户需要的是同时匹配数组中的所有值(AND),那么应该使用`bool`查询的`must`子句包含多个`term`查询,例如: ```json GET goods/_search { "query": { "bool": { "must": [ { "term": { "couponIds": 1 }}, { "term": { "couponIds": 2 }} ] } } } ``` 但是注意,这种方式要求同一个文档的`couponIds`字段同时包含1和2。而`couponIds`字段如果是多值字段(如数组),那么Elasticsearch会检查每个值。 然而,根据需求,如果只是需要匹配多个值中的任意一个,那么使用`terms`查询即可。 因此,请根据实际需求选择使用`terms`(OR)还是多个`term`组合(AND)。 最后,关于这个错误,我们只需要将`term`改为`terms`即可解决。 相关问题: 1. Elasticsearch中`term`和`terms`查询有什么区别? 2. 如何在Elasticsearch中实现多值字段的AND操作(同时匹配多个值)? 3. Elasticsearch处理数组类型字段的内部机制是怎样的?</think>### 解决 Elasticsearch "[term] query does not support array of values" 错误 #### 问题原因分析 1. **`term` 查询限制**: - `term` 查询设计用于精确匹配单个值(如 `"field": "value"`),不支持直接传入数组值(如 `"field": [1,2]`)[^1] - Elasticsearch 底层基于 Lucene,其核心索引模型是 `term => array[docIds]` 的映射,导致 `term` 无法原生处理数组值[^3] 2. **数据类型不匹配**: - 当字段定义为 `keyword` 等单值类型时,尝试传递数组会触发此错误 - 即使字段是多值类型(如数组),`term` 查询仍要求单个输入值 #### 解决方案 ##### 方法1:使用 `terms` 查询替代(推荐) ```json GET goods/_search { "query": { "bool": { "must": [ { "terms": { // 使用复数形式 terms "couponIds": [1, 2] // 直接支持数组值 } } ] } } } ``` - ✅ `terms` 专为多值匹配设计 - ✅ 语义:匹配包含数组中任意值的文档 ##### 方法2使用 `bool` + 多个 `term` 查询 ```json GET goods/_search { "query": { "bool": { "must": [ { "term": { "couponIds": 1 }}, // 拆分为多个term查询 { "term": { "couponIds": 2 }} ] } } } ``` - ✅ 实现 AND 逻辑(同时包含1和2) - ⚠️ 如需 OR 逻辑,改用 `should` 子句 ##### 方法3:修改字段映射(长期方案) 如果字段需要存储数组值: ```json PUT goods/_mapping { "properties": { "couponIds": { "type": "integer", // 或 "keyword" "index": true, // 确保可索引 "doc_values": true // 支持聚合 } } } ``` - 多值字段会自动支持 `terms` 查询 - 重建索引后即可使用数组值查询 #### 调试建议 1. **检查字段映射**: ```json GET goods/_mapping ``` 确认字段类型是否支持多值 2. **验证查询语法**: - 使用 `_validate` API 预检: ```json POST goods/_validate/query?explain { "query": { ... } } ``` 3. **错误处理**: - 捕获 `parsing_exception` 并检查错误中的行号(如 `"line": 9, "col": 26`)[^1] - 检查 `allow_partial_search_results` 设置是否影响错误上报[^4] > **关键区别**: > `term` 查询:单值精确匹配(如 SQL `WHERE field = value`) > `terms` 查询:多值精确匹配(如 SQL `WHERE field IN (value1, value2)`)[^3] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值