一、Doc Values介绍

倒排索引在搜索包含指定 term 的文档时效率极高,但在执行相反操作,比如查询一个文档中包含哪些 term,以及进行排序、聚合等与指定字段相关的操作时,表现就很差了,这时候就需要用到 Doc Values。

倒排索引是将 term 映射到包含它们的文档,而 Doc Values 则是将文档映射到它们包含的所有词项,以下是一个示例:

Doc

Terms

Doc_1

brown, dog, fox, jumped, lazy, over, quick, the

Doc_2

brown, dogs, foxes, in, lazy, leap, over, quick, summer

Doc_3

dog, dogs, fox, jumped, over, quick, the

当数据被这样倒置之后,想要收集到 Doc_1 和 Doc_2 的唯一 token 就变得非常容易。只需要获取每个文档行,提取所有的词项,然后求两个集合的并集即可。

其实,Doc Values 本质上是一个序列化了的列式存储结构,这种结构非常适合排序、聚合以及字段相关的脚本操作。而且这种存储方式便于压缩,尤其是对于数字类型,压缩后能够大大减少磁盘空间占用,同时提升访问速度。下面是一个数字类型的 Doc Values 示例:

Doc

Terms

Doc_1

100

Doc_2

1000

Doc_3

1500

Doc_4

1200

Doc_5

300

Doc_6

1900

Doc_7

4200

列式存储意味着这些数据会形成一个连续的数据块:[100, 1000, 1500, 1200, 300, 1900, 4200]。因为我们知道它们都是数字(不像文档或行中看到的异构集合),所以可以使用统一的偏移量来将它们紧密排列。

而且,针对这样的数字有很多种压缩技巧。你会注意到这里每个数字都是 100 的倍数,Doc Values 会检测一个段里面的所有数值,并使用一个最大公约数来进行进一步的数据压缩。比如在这个例子中,可以用 100 作为公约数,那么以上数字就变为 [1, 10, 15, 12, 3, 19, 42],这样可用很少的 bit 就能存储,节约了磁盘空间。一般来说,Doc Values 按顺序检测以下压缩方案:

  • 如果所有值都相同(或缺失),就设置一个标志并记录该值

  • 如果少于 256 个值,就会使用一个简易码表

  • 如果值个数大于 256,就检查是否存在最大公约数

  • 如果没有最大公约数,就以偏移量的方式从最小值开始对所有值编码

String 类型使用顺序表,按和数字类型类似的方式编码。String 类型去重后排序,然后写入一个表中,并分配一个 ID 号,这些 ID 号就被当做数字类型的 Doc Values。这意味着字符串享有许多与数字相同的压缩特点。

Doc Values 是在字段索引时与倒排索引同时生成,并且生成以后是不可变的。

Doc Value 默认对除了 analyzed String 外的所有字段启用(因为分词后会生成很多 token 使得 Doc Values 效率降低)。但是当你知道某些字段永远不会进行排序、聚合以及脚本操作的时候,可以禁用 Doc Values 以节约磁盘空间、提升索引速度,示例如下:

PUT my_index{"mappings": {"my_type": {"properties": {"session_id": {"type": "string","index": "not_analyzed","doc_values": false}}}}}

以上配置后,session_id 字段就只能被搜索,不能被用于排序、聚合以及脚本操作了。

还可以通过设定 doc_values 为 true,index 为 no 来让字段不能被搜索但可以用于排序、聚合以及脚本操作:

PUT my_index{"mappings": {"my_type": {"properties": {"customer_token": {"type": "string","index": "not_analyzed","doc_values": true,"index": "no"}}}}}

Doc Value 的特点是快速、高效、内存友好,使用由 linux kernel 管理的文件系统缓存弹性存储。doc values 在排序、聚合或与字段相关的脚本计算中得到了高效运用,任何需要查找某个文档包含的值的操作都必须使用它。如果你确定某个 field 不会做字段相关操作,可以直接关掉 doc_values,节约内存,加快访问速度。

二、Fielddata介绍

上文说过,在排序、聚合以及在脚本中访问 field 值时,需要一种与倒排索引截然不同的数据访问模式:不同于倒排索引中的查找 term-> 找到对应 docs 的过程,我们需要直接查找 doc 然后找到指定某个 field 中包含的 terms。

大多数 field 使用索引时、磁盘上的 doc_values 来支持这种访问模式,但是分词了的 String field 不支持 Doc Values,而是使用一种叫 FieldData 的数据结构。

FieldData 主要是针对 analyzed String,它是一种查询时(query-time)的数据结构。

FieldData 缓存主要应用场景是在对某一个 field 排序或者计算类的聚合运算时。它会把这个 field 列的所有值加载到内存,目的是提供对这些值的快速文档访问。为 field 构建 FieldData 缓存可能会很昂贵,因此建议有足够的内存来分配它,并保持其处于已加载状态。

FieldData 是在第一次将该 field 用于聚合、排序或在脚本中访问时按需构建。FieldData 是通过从磁盘读取每个段来读取整个反向索引,然后逆置 term->doc 的关系,并将结果存储在 JVM 堆中构建的。所以,加载 FieldData 是开销很大的操作,一旦它被加载后,就会在整个段的生命周期中保留在内存中。

这里可以注意下 FieldData 和 Doc Values 的区别。较早的版本中,其他数据类型也是用的 FieldData,但是目前已经用随文档索引时创建的 Doc Values 所替代。

JVM 堆内存资源非常宝贵,能用好它对系统的高效稳定运行至关重要。FieldData 直接放在堆内,所以必须合理设定用于存放它的堆内存资源数。ES 中控制 FieldData 内存使用的参数是 indices.fielddata.cache.size,可以用 x% 表示占该节点堆内存百分比,也可以用如 12GB 这样的数值。默认状况下,这个设置是无限制的,ES 不会从 FieldData 中驱逐数据。如果生成的 fielddata 大小超过指定的 size,则将驱逐其他值以腾出空间。使用时一定要注意,这个设置只是一个安全策略而并非内存不足的解决方案。因为通过此配置触发数据驱逐,ES 会立刻开始从磁盘加载数据,并把其他数据驱逐以保证有足够空间,导致很高的 IO 以及大量的需要被垃圾回收的内存垃圾。

举个例子:每天为日志文件建一个新的索引。一般来说我们只对最近几天数据感兴趣,很少查询老数据。但是,按默认设置 FieldData 中的老索引数据是不会被驱逐的。这样的话,FieldData 就会一直持续增长直到触发熔断机制,这个机制会让你再也不能加载更多的 FieldData 到内存。在这种场景下,你只能对老的索引访问 FieldData,但不能加载更多新数据。所以,这个时候就可以通过以上配置来把最近最少使用的 FieldData 驱逐,为新进来的数据腾空间。

FieldData 是在数据被加载后再检查的,那么如果一个查询导致尝试加载超过可用内存的数据,就会导致 OOM 异常。ES 中使用了 FieldData Circuit Breaker 来处理上述问题,它可以通过分析一个查询涉及到的字段的类型、基数、大小等来评估所需内存。如果估计的查询大小大于配置的堆内存使用百分比限制,则断路器会跳闸,查询将被中止并返回异常。

断路器是在数据加载前工作的,所以你不用担心遇到 FieldData 导致的 OOM 异常。ES 拥有多种类型的断路器:

  • indices.breaker.fielddata.limit

  • indices.breaker.request.limit

  • indices.breaker.total.limit

可以根据实际需要进行配置。

FieldData 是为分词 String 而生,它会消耗大量的 java 堆空间,特别是加载基数(cardinality)很大的分词 String field 时。但是往往对这种类型的分词 Field 做聚合是没有意义的。

值得注意的是,FieldData 和 Doc Values 的加载时机不同,前者是首次查询时,后者是 doc 索引时。还有一点,FieldData 是按每个段来缓存的。

三、Doc values 与 Fielddata 对比

doc_values 与 fielddata 一个很显著的区别是,前者的工作地盘主要在磁盘,而后者的工作地盘在内存。

维度

doc_values

fielddata

创建时间

index 时创建

使用时动态创建

创建位置

磁盘

内存 (jvm heap)

优点

不占用内存空间

不占用磁盘空间

缺点

索引速度稍低

文档很多时,动态创建开销大,且占内存

索引速度稍低是相对于 fielddata 方案而言的,其实仔细想想也可以理解。拿排序举例,一个在磁盘排序,一个在内存排序,谁的速度快不言而喻。

而且,随着 ES 版本的升级,对于 doc_values 的优化越来越好,索引的速度已经很接近 fielddata 了,而且我们知道硬盘的访问速度也是越来越快(比如 SSD)。所以 doc_values 现在可以满足大部分场景,也是 ES 官方重点维护的对象。

所以,doc values 相比 field data 还是有很多优势的。因此 ES2.x 之后,支持聚合的字段属性默认都使用 doc_values,而不是 fielddata。

Global Ordinals 全局序号

Global Ordinals 是一个在 Doc Values 和 FieldData 之上的数据结构,它为每个唯一的 term 按字典序维护了一个自增的数字序列。每个 term 都有自己的一个唯一数字,而且字母 A 的全局序号小于字母 B。特别注意,全局序号只支持 String 类型的 field。

需要注意的是,Doc Values 和 FieldData 也有自己的 ordinals 序号,这个序号是特定 segment 和 field 中的唯一编号。通过提供 Segment Ordinals 和 Global Ordinals 间的映射关系,全局序号在此基础上创建,后者(即全局序号)在整个 shard 分片中是唯一的。

一个特定字段的 Global Ordinals 跟一个分片中的所有段相关,而 Doc Values 和 FieldData 的 ordinals 只跟单个段相关。因此,只要是一个新段要变得可见,就必须完全重建全局序号。

也就是说,跟 FieldData 一样,在默认情况下全局序号也是懒加载的,会在第一个请求 FieldData 命中一个索引时来构建全局序号。实际上,在为每个段加载 FieldData 后,ES 就会创建一个称为 Global Ordinals(全局序号)的数据结构,来构建一个由分片内的所有段中的唯一 term 组成的列表。

全局序号的内存开销小,原因是它由非常高效的压缩机制。提前加载的全局序号可以将加载时间从第一次搜索时转到全局序号刷新时。

全局序号的加载时间依赖于一个字段中的 term 数量,但是总的来说耗时较低,因为来源的字段数据都已经加载到内存了。

全局序号在用到段序号的时候很有用,比如排序或者 terms aggregation,可以提升执行效率。

我们举个简单的例子。比如有十亿级别的 doc,每个 doc 都有一个 status 字段,但只有 pending、published、deleted 三个状态数据。如果直接存整个 String 数据到内存,那么就算每个 doc 有 15 字节,一共就是差不多 14GB 的数据。怎么减少占用空间呢?首先想到的就是用数字来进行编码,码表如下:

Ordinal

Term

0

status_deleted

1

status_pending

2

status_published

这样的话,初始的那三个 String 就只在码表内被存了一次。FieldData 中的 doc 就可以直接用编码来指向实际值:

Doc

Ordinal

0

1 # pending

1

1 # pending

2

2 # published

3

0 # deleted

这样编码以后,直接把数据量压缩了十倍左右。但有个问题是 FieldData 是按每个段来分别加载、缓存的。那么就会出现一种情况,如果一个段内的 doc 只有 deleted 和 published 两个状态,那么就会导致该 FieldData 算出来的码表只有 0 和 1,这就和拥有 3 个状态的段算出的 FieldData 码表不同。这样的话,聚合的时候就必须一个段一个段的计算,最后再聚合,十分缓慢,开销巨大。

ES 的做法是用 Global Ordinals 这种构建在 FieldData 之上的小巧数据结构,编码会结合所有段来计算唯一值然后存放为一个序号码表。这样一来,term aggregation 可以只在全局序号上进行聚合,而且只会在聚合的最终阶段来计算从序号到真实的 String 值一次。这个机制可以提升聚合的性能 3-4 倍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/92238.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/92238.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/92238.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】解决VScode中文乱码问题

文章目录【C语言】解决VScode中文乱码问题弹出无法写入用户设置的处理方法弹出无法在只读编辑器编辑的问题处理方法【C语言】解决VScode中文乱码问题 💬欢迎交流:在学习过程中如果你有任何疑问或想法,欢迎在评论区留言,我们可以共…

MySQL笔记4

一、范式1.概念与意义范式(Normal Form)是数据库设计需遵循的规范,解决“设计随意导致后期重构困难”问题。主流有 三大范式(1NF、2NF、3NF),还有进阶的 BCNF、4NF、5NF 等,范式间是递进依赖&am…

切比雪夫不等式的理解以及推导【超详细笔记】

文章目录参考教程一、意义1. 正态分布的 3σ 法则2. 不等式的含义3. 不等式的意义二、不等式的证明1. 马尔科夫不等式马尔可夫不等式证明(YYY 为非负随机变量 )2. 切比雪夫不等式推导参考教程 一个视频,彻底理解切比雪夫不等式 一、意义 1. 正态分布的…

Spring Boot Jackson 序列化常用配置详解

一、引言在当今的 Web 开发领域,JSON(JavaScript Object Notation)已然成为数据交换的中流砥柱。无论是前后端分离架构下前后端之间的数据交互,还是微服务架构里各个微服务之间的通信,JSON 都承担着至关重要的角色 。它…

Jetpack ViewModel LiveData:现代Android架构组件的核心力量

引言在Android应用开发中,数据管理和界面更新一直是开发者面临的重大挑战。传统的开发方式常常导致Activity和Fragment变得臃肿,难以维护,且无法优雅地处理配置变更(如屏幕旋转)。Jetpack中的ViewModel和LiveData组件正…

Python数据分析案例79——基于征信数据开发信贷风控模型

背景 虽然模型基本都是表格数据那一套了,算法都没什么新鲜点,但是本次数据还是很值得写个案例的,有征信数据,各种,个人,机构,逾期汇总..... 这么多特征来做机器学习模型应该还不错。本次带来&…

板凳-------Mysql cookbook学习 (十二--------3_2)

3.3链接表 结构 P79页 用一个类图来表示EmployeeNode类的结构,展示其属性和关系: plaintext ----------------------------------------- | EmployeeNode | ----------------------------------------- | - emp_no: int …

深度学习图像预处理:统一输入图像尺寸方案

在实际训练中,最常见也最简单的做法,就是在送入网络前把所有图片「变形」到同一个分辨率(比如 256256 或 224224),或者先裁剪/填充成同样大小。具体而言,可以分成以下几类方案:一、图…

pytest-log

问题1:我们在运行测试用例的时候如何记录测试的log,如何使用?问题2:我写的函数,为了方便log记录,但是在pytest运行时,会兼容pytest且不会重复记录,怎么解决?1、pytest有内…

在安卓源码中添加自定义jar包给源码中某些模块使用

一、具体步骤 1. 准备目录与 Jar 包 在vendor下 创建新的模块目录,放入demo.jar 包: demojar/ # 模块目录 ├── Android.bp # 编译配置文件 └── demo.jar 2. 编写 Android.bp 配置 Android.bp 示例配置: java_import {…

buntu 22.04 上离线安装Docker 25.0.5(二)

以下有免费的4090云主机提供ubuntu22.04系统的其他入门实践操作 地址:星宇科技 | GPU服务器 高性能云主机 云服务器-登录 相关兑换码星宇社区---4090算力卡免费体验、共享开发社区-CSDN博客 兑换码要是过期了,可以私信我获取最新兑换码!&a…

初探 Web 环境下的 LLM 安全:攻击原理与风险边界

文章目录前言1 什么是大型语言模型(LLM)?1.1 LLM的核心特征1.2 LLM在Web场景中的典型应用2 LLM攻击的核心手段:提示注入与权限滥用3 LLM与API集成的安全隐患:工作流中的漏洞节点3.1 LLM-API集成的典型工作流3.2 工作流…

【新手向】PyTorch常用Tensor shape变换方法

【新手向】PyTorch常用Tensor shape变换方法 前言 B站UP主科研水神大队长的视频中介绍了“缝合模块”大法,其中专门强调了“深度学习 玩的就是shape”。受此启发,专门整理能够调整tensor形状的几个内置函数,方便以后更好地调整PyTorch代码中的…

React 18 vs Vue3:状态管理方案深度对比

🔥 背景: React有Redux、Zustand、Jotai等方案 Vue有Pinia、Vuex 4.x 如何选择适合项目的方案? 🔍 核心对比: 维度 React (Redux Toolkit) Vue3 (Pinia) 类型安全 ✅ 需手动配置TS ✅ 自动类型推导 代码量 较多(需写action) 较少(类似Vuex 5) 响应式原理 不可变数据…

UE5网络联机函数

Find Sessions Create Session Join Session Destroy Session Steam是p2p直接联机 一、steam提供的测试用AppId AppId是steam为每一款游戏所设定的独有标识,每一款要上架steam的游戏都会拥有独一无二的AppId。不过为了方便开发者测试,steam提供了游…

Spring Boot 监控:AOP vs Filter vs Java Agent

01前言 在 高并发 微服务 中, 传统 手动埋点(System.currentTimeMillis())就像用体温计量火箭速度——代码侵入、重复劳动、维护爆炸。 下文是无侵入、高精度、全链路 监控 API 耗时,全程不碰业务代码的方案! 02实战&…

基于Android的电子记账本系统

博主介绍:java高级开发,从事互联网行业多年,熟悉各种主流语言,精通java、python、php、爬虫、web开发,已经做了多年的毕业设计程序开发,开发过上千套毕业设计程序,没有什么华丽的语言&#xff0…

7月17日日记

结束了数学建模之后的这两天一直在紧张的复习,但是说实话效率有点低,因为可能觉得自己找到了两个小时速成课,觉得无所谓了,所以有点放松了。在宿舍杰哥和林雨城却一直在复习,感觉他们的微积分和线性代数复习的都比我好…

Linux下SPI设备驱动开发

一.SPI协议介绍1.硬件连接介绍引脚含义:DO(MOSI):Master Output, Slave Input,SPI主控用来发出数据,SPI从设备用来接收数据。DI(MISO):Master Input, Slave Output,SPI主控用来发出数据,SPI从设…

用Dify构建气象智能体:从0到1搭建AI工作流实战指南

作为一名Agent产品经理,我最近在负责气象智能体的建设项目。传统气象服务面临三大痛点:数据孤岛严重(气象局API、卫星云图、地面观测站等多源数据格式不一)、响应链路长(从数据采集到预警发布需人工介入多个环节)、交互体验单一(用户只能被动接收标准化预警,无法个性化…