【Elasticsearch】Elasticsearch中FST的Off-Heap优化详解

news/2024/10/31 2:03:26/

转自:https://www.easyice.cn/archives/346

前言

一直以来,Elasticsearch(ES)堆内存中占据比重最大的是 FST,即 .tip(terms index)文件。这些文件占据的空间很大,1TB 的索引大约需要 2GB 或更多的内存。为了确保节点稳定运行,业界通常认为一个节点打开的索引不应超过 5TB。

从 ES 7.3 版本开始,.tip 文件被修改为通过 mmap 的方式加载,这使得 FST 占据的内存从堆内转移到了堆外,由操作系统的 page cache 管理。

参考 ES 7.3 的 release notes:

Also mmap terms index (.tip) files for hybridfs #43150 (issue: #42838)

现在,我们来深入探讨其中的一些细节。

hybridfs 的工作原理

hybridfs 是索引默认的 store 类型,它根据操作系统类型自动选择 nio 或者 mmap。那么究竟哪些文件被 mmap 方式打开呢?手册中提到:

Currently only the Lucene term dictionary, norms and doc values files are memory mapped. All other files are opened using Lucene NIOFSDirectory.

对应到文件扩展名,就是 .nvd(norms)、.dvd(doc values)、.tim(term dictionary)、.tip(term index)、.cfs(compound)类型的文件使用 mmap 方式加载,其余使用 nio

switch(extension) {case "nvd":case "dvd":case "tim":case "tip":case "cfs":return true;default:return false;
}

为什么使用 mmap 实现 Off-Heap

你可能会问,为什么把 .tip 文件通过 mmap 方式读取就实现了 Off-Heap?像 HBase 实现 Off-Heap 需要将数据转移到堆外的数据结构,为什么 ES 不需要?

FST 的查找过程

在堆内存(On-Heap)的情况下,Lucene 将 .tip 文件的数据读进一个数组。在 FST 查找时,seek 到某个位置,读取一些字节,然后再次 seek,再读取,相当于边读取边解析。

private Arc<T> findTargetArc(BytesReader in, ...) {// ...in.setPosition(follow.target);arc.numArcs = in.readVInt();arc.bytesPerArc = in.readVInt();arc.posArcsStart = in.getPosition();arc.nextArc = arc.posArcsStart;// ...
}

在 On-Heap 的情况下,这个 BytesReader 的初始化就是简单地将文件读进数组:

public void init(DataInput in, long numBytes) {bytesArray = new byte[(int) numBytes];in.readBytes(bytesArray, 0, bytesArray.length);
}

因此,在 Off-Heap 的情况下,mmap 像数组一样读取就可以了。

如何查看文件的缓存情况

如果想要查看文件被 page cache 缓存的百分比,可以使用以下工具:

  • vmtouch(推荐)
  • pcstat
  • hcache
  • fincore

要确认某个 .tip 文件是否被 mmap 方式读取的,可以使用 pmap 命令,被 mmap 映射的文件会在这里列出来。

Off-Heap 后的效果

使用 geonames 数据集写入索引 1TB,使用 _cat/segments API 查看 segments.memory 内存占用量,对比 Off-Heap 后的内存占用效果:

store.typesegments.memory
niofs4.7GB
hybridfs1.06GB

JVM 内存占用量降低了约 78%。不同数据样本结果不同,其他的可能会降低更多。

通过 _cat/segments 观测到的 segments.memory 指标,会比实际占用的 JVM 内存少一些,不过相差不大,上述结果可作为参考。

Page Cache 的管理

由于 Off-Heap 后的堆外内存由操作系统的 page cache 管理,什么时候被驱逐出去由操作系统决定,进程无法控制。如果 .tip 文件的内容被驱逐出 page cache,对 FST 的查找会涉及到磁盘 IO,对查询延迟有比较大的影响。

Page Cache 的回收策略

Linux 系统的 page cache 回收有两种情况:

  1. 系统内存不足时的自动回收:当系统可用内存不足时,系统会自动回收 page cache 缓存的数据,其中可能包括 mmap 映射的 .tip 文件。
  2. 手工回收:通过改写 /proc/sys/vm/drop_cachesposix_fadvise 调用来手工回收。

当索引处于打开(open)状态时,由 mmap 映射到 page cache 的 .tip 数据并不会被回收;而如果索引处于关闭(close)状态,则会被完全回收。

Page Cache 的回收算法

在 Linux 2.6.34 的内核中,对 page cache 的回收策略使用双链策略,参考《Linux 内核设计与实现(第三版)》。算法描述大致如下:

  • 引入两个链表:active listinactive list
  • 两个链表都是从尾部加入,头部移出。
  • 页面换出操作只在 inactive list 执行。
  • 对于文件缓存,当第一次访问的时候加入到 inactive list,再次访问的时候把它提升到 active list
  • active list 大小大于 inactive list,就将 active list 头部的页面降级到 inactive list

更多 page cache 的信息可以参考 Linux MM Page Replacement Design。

mmap 的原理

依据 mmap 的原理,文件描述符(fd)被映射为指针(或者说字节数组)供进程直接访问,仅在进程访问到相应位置的时候才去读取磁盘,是根据内容按需读取磁盘。

你可能会想,既然如此,_open 索引是不是变快了?原来 nio 需要把整个文件读进堆内存,现在 mmap 一下就结束了,那么等索引首次被查询的时候才会加载到 page cache?实际上,_open 索引并没变快,因为在 _open 索引的过程中,Lucene 会检查文件的校验和,把整个文件读取一遍:

// BlockTreeTermsReader constructor
CodecUtil.checksumEntireFile(indexIn);
ChecksumIndexInput in = new BufferedChecksumIndexInput(clone);
// 读取文件到目标位置,并更新校验和
in.seek(in.length() - footerLength());
return checkFooter(in);

关于 _id 字段的 Off-Heap 问题

Lucene 支持字段级的 Off-Heap 设置。ES 7.3 中将 .tip Off-Heap 时并不包含 _id 字段,#52518 中提到,这是因为担心降低写入速度。不过在经历了一些测试之后发现影响并不大。

一般来说,只有在使用显式 IDs 时,索引速率才会受到影响,因为否则 Elasticsearch 几乎不会在索引过程中查找 terms dictionary。因此,强制将 _id 字段的 terms index 保持在堆内存中,对于那些具有仅追加工作负载的用户来说是相当浪费的。此外,我使用 http_logs 数据集在索引时进行了基准测试,结果表明,即使在使用显式 IDs 的情况下,速度下降也足够小,可能不值得强制将 terms index 保持在堆内存中。

题外话:这段内容提到,使用外部 doc id 方式入库时需要从 term dictionary 中查询,这是因为使用外部 ID 写入时,ES 需要判断该 ID 是否存在,以便执行更新(update)或追加(append)操作。因此在分片中对 _id 字段执行 Lucene 的 seekExact 查询来判断此 ID 是否存在,所以使用外部 ID 入库时写入速度会降低一些(约 20%)。这也是 _id 字段需要写入 FST 的一个原因。

在将 _id 字段 Off-Heap 之后,使用 http_logs 数据集和外部 ID 的方式执行写入测试,写入速度降低了 1.8%,JVM 内存降低了 100 倍。

因此,在 ES 7.7 版本中,会将 _id 字段也放到堆外。

结语

把 FST 放到堆外可以让节点能够持有更多的数据,这对 ES 集群能处理的数据规模有重大提升,意义重大。但是 .tip 文件需要加载到内存的意义比 .tim 等文件要重要得多,page cache 总会有需要回收的时候,谁能保证 .tip 不被回收呢?所以总体来说,可能会让查询延迟增加不确定性,且不便重现和诊断。不过也不用太担心,这种情况一般很少发生。

参考资料

  • Elasticsearch Issue #38390
  • Elasticsearch Pull Request #42838
  • Elasticsearch Pull Request #43150
  • Elasticsearch Pull Request #52518
  • Elasticsearch 7.3.0 Release Notes
  • Linux MM Page Replacement Design

转自:https://www.easyice.cn/archives/346


http://www.ppmy.cn/news/1543240.html

相关文章

【Fastjson反序列化漏洞:深入了解与防范】

一、Fastjson反序列化漏洞概述 Fastjson是一款高性能的Java语言JSON处理库&#xff0c;广泛应用于Web开发、数据交换等领域。然而&#xff0c;由于fastjson在解析JSON数据时存在安全漏洞&#xff0c;攻击者可以利用该漏洞执行任意代码&#xff0c;导致严重的安全威胁。 二、F…

电脑连接海康相机并在PictureBox和HWindowControl中分别显示。

展示结果&#xff1a; 下面附上界面中所有控件的Name&#xff0c;只需照着红字设置对应的控件Name即可 下面附上小编主界面的全部代码&#xff1a; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; …

说一说QWidget

目录 关于QWidget 作为界面组件时&#xff0c;你需要有印象的 1. 控制属性 2. 组件状态与交互属性 3. 外观和样式属性 4. 布局与子组件管理属性 5. 图标和光标属性 6. 大小策略属性 作为单独的窗体的属性 写Qt快两年了&#xff0c;也写过一些规模偏大的软件&#xff0c…

将.py文件生成.exe文件的方法

1、安装pyinstaller&#xff08;已有忽略&#xff09;&#xff1a;pip install pyinstaller 2、进入目标文件&#xff08;需要编译的文件&#xff09;目录 3、编译&#xff1a;pyinstaller --onefile xx.py 4、文件生成后的路径&#xff1a;在目标文件目录下创建一个名为dist的…

iOS 本地存储地址(位置)

前言: UserDefaults 存在沙盒的 Library --> Preferences--> .plist文件 CoreData 存在沙盒的 Library --> Application Support--> xx.sqlite 一个小型数据库里 (注:Application Support 这个文件夹已开始是没有的,只有当你写了存储代码,运行之后,目录里才会出…

ssm基于web的网络游戏交易平台信息管理系统的设计与实现+vue

系统包含&#xff1a;源码论文 所用技术&#xff1a;SpringBootVueSSMMybatisMysql 免费提供给大家参考或者学习&#xff0c;获取源码请私聊我 需要定制请私聊 目 录 目 录 III 第1章 绪论 1 1.1选题动因 1 1.2目的和意义 1 1.3论文结构安排 2 第2章 开发环境与技术 …

编程语言的设计模式

编程语言的设计模式是一种总结和抽象&#xff0c;帮助开发者应对常见的编程问题。以下是几种主要的设计模式&#xff1a; 1. 创建型模式 单例模式 (Singleton Pattern)&#xff1a;确保一个类只有一个实例&#xff0c;并提供一个全局访问点。工厂模式 (Factory Pattern)&…

Spring Boot框架中的IO

1. 文件资源的访问与管理 在 Spring Boot 中&#xff0c;资源文件的访问与管理是常见的操作需求&#xff0c;比如加载配置文件、读取静态文件或从外部文件系统读取文件。Spring 提供了多种方式来处理资源文件访问&#xff0c;包括通过 ResourceLoader、Value 注解以及 Applica…