作者:daniel
日志领域是Elasticsearch(ES)最重要也是规模最大的应用场景之一。这得益于 ES 有高性能倒排索引、灵活的 schema、易用的分布式架构,支持高吞吐写入、高性能查询,同时有强大的数据治理生态、端到端的完整解决方案。但原生 ES 在高吞吐写入、低成本存储、高性能查询等方面还有非常大的优化空间,本文重点剖析腾讯云大数据 ES 团队在这三个方面的内核增强优化。
我们先来看看日志领域的挑战有哪些。
首先从日志本身的数据特点来看,主要以半结构化、非结构化为主,传统的数据库方案没法很好的解决解析、存储等问题。ES 支持 JSON 且支持动态映射,高版本还支持 Runtime Feilds,能在查询时动态提取字段,天然支持了日志场景复杂、灵活的数据结构。
日志数据进入 ES 则面临高并发、高吞吐的写入性能挑战。内核层面我们进行了定向路由、物理复制等重磅优化,有效解决日志入口吞吐瓶颈,提升写入性能一倍+。
日志数据的存储是另一大挑战,日志往往是海量的且往往价值不高,存储成本成为用户最顾虑的痛点之一。腾讯云 ES 一方面通过压缩编码优化来降低单位文档存储成本,另一方面我们通过基于云原生环境自研混合存储方案,实现存储与计算分离,降低存储成本 50% 以上。
日志数据的出口,查询性能也是一大挑战,海量数据场景下,大查询对资源的开销非常高,带来的查询延时也非常明显。ES 原生支持了倒排索引,点查过滤的性能非常好,但无法满足日志场景大范围查询、聚合分析等性能需求。对此,腾讯云 ES 进行了查询裁剪、查询并行化等一系列系统性优化,提升查询性能数十倍。
下面是日志领域的挑战总结,后面将针对高吞吐写入、低成本存储、高性能查询层面的优化进行详细分析。
ES 的一个 Bulk 写入先进到协调节点(任意一个数据节点),会被拆分为若干个分片粒度的子写入请求,并分发给对应的数据节点。当集群规模较大,索引的分片数较多的场景下,分布式写入的扇出放大非常严重,很容易受到个别长尾节点的 GC、慢盘、网络抖动等影响,导致写入堆积,资源利用率低,拒绝率高。
腾讯云 ES 内核通过引入写入定向路由优化,将用户的一个 Bulk 请求路由到一个分片数可控的分片组,降低写入请求扇出影响,容忍慢节点,在不可靠的环境中提供可靠的服务。基于定向路由优化,某日志业务场景,写入吞吐提升一倍多,CPU 资源利用率提升 58%,拒绝率从 3% 降至 0。
定向路由解决了慢节点对写入的影响,接下来我们看看如何优化写入层面冗余的计算开销来提升性能。
ES 单机引擎写入,底层数据接收到可见分为三个步骤,如上图所示。
ES 分布式层的写入,数据先从协调节点转发至 Primary,主分片写完 Lucene 和 Translog 之后并行转发给多个 Replica,副本分片完成 Lucene 和 Translog 的写入。这个过程中,副本分片的 Lucene 写入是冗余的,因为这个写入栈在 Primary 上进行了一遍,在 Replica 上会完整的再来一遍,开销非常高。
物理复制解决的就是从分片上冗余写入栈的开销。
如上图方案所示,第一步当 Primary 产生新的 Segment 之后,会及时通过物理拷贝方式同步至 Replica,使得 Segment 在 Replica 上可见,同时消除 Replica 上内存构建 Segment 过程的计算开销。当 Primary 上产生新的 Commit 文件之后,也会及时同步至 Replica。同步 Commit 文件的目的主要是在 Replica 侧清理过期的 Translog 和被合并的 Segment。例如在上面第三、四步中就描述了 Primary 侧 Segment 合并之后同步到 Replica 侧清理的过程。
通过 Segment 物理复制的能力,有效裁剪了 Replica 写入的计算开销,在主从副本场景下提升写入性能接近一倍。
前面我们重点介绍了海量日志数据高吞吐写入层面的性能优化。海量的数据流入到 ES 之后,存储是另一大挑战,接下来我们来探讨一下海量存储场景如何进行成本优化,我们先来看看背景。
存储成本的影响面主要分为三个层面:
从上面几个维度的成本影响面来看,我们的优化重点一方面是降低单位文档的存储成本,另一方面是降低冗余副本、存储介质的成本。
压缩编码优化是在原始数据结构不变的前提下,降低单位文档存储成本非常有效的方式。上图中描述了 ES 底层 Lucene 的存储格式,以及这些格式所用到的压缩算法。其中列存压缩是由腾讯贡献给社区的。
Lucene 列存存储分为两部分,term dictionary 和 term ords,每个字段值的原始内容存储在 term dictionary 中,实际编码、计算的时候为了避免冗余而采用 term ords,后者会随着新数据的持续写入而动态更新。优化的主要内容是对 term dictionary 中的原始字段字面内容进行压缩存储。从优化后的压缩效果对比来看,写入、merge 耗时基本不变的情况下,列存存储下降了 40%+。
除了优化列存以外,我们还引入了新的通用压缩算法。
Zstandard 算法在压缩比上比 LZ4 更优,在性能上比 Deflate 更优,能兼容两者的优点。我们将该通用压缩算法引入到行存、列存、索引文件压缩中,实测业务开启 Zstandard 算法之后,整体存储成本下降 30%+。
前面主要介绍了通过压缩编码优化降低单位文档存储成本,而单位文档的存储优化是有极限的。另一个方向是从存储架构层面进行优化。在云原生的背景下,我们引入了自研混合存储引擎方案。
混合存储引擎的整体设计思路是基于典型的 delta + base 架构。其中 delta 部分我们采用 SSD,主要目的为了扛高并发写入,以及小 segment 的存储及合并;而 base 部分采用对象存储,用于存储大量不可变的大 segment,一方面其高可用(4 个 9 一个 5)、高可靠(12 个 9)、按量付费、免运维的架构降低了大量运维成本,更重要的是其提供的标准、低频、归档等灵活的低成本存储方案能大幅降低海量日志的存储成本。
接下来我们结合索引数据的生命周期看看混合存储引擎的整体设计。
混合存储引擎整体分为两层。上层存储介质为 SSD,提供高并发的写入能力,准实时产生的 segment 会通过物理复制的能力从 primary 拷贝至 replica。同时主从副本均写入 translog,确保主从切换数据无缝对接及重启数据不丢失,更重要的是主从副本之间的数据、segment 完全一致,这便于我们在 primary 上将数据下沉至底层共享存储后,replica 可以无缝挂载查询。每个分片本地都会有一个 RemoteDirectoryWrapper 封装了对远程共享存储数据的访问,包含了数据缓存的逻辑。底层共享存储采用对象存储,数据按照索引、分片粒度分目录存储,segment 数据文件一对一映射,方便存取。
下面我们细分流程看看整体方案的设计细节。
如上图所示,前五个阶段,主要是我们之前描述的物理复制的流程,主要目的是为了提升写入性能,确保 primary 和 replica 数据保持完全一致,为后续的数据共享提供基础。前面已描述详细流程,这里不展开分析。
第六个阶段,本地 segment 通过 merge 产生了较大的 segment,会被冻结不再参与 merge,并下沉至底层共享存储。注意这个阶段是从热数据就开始的,并不是数据降温后才启动下沉,可以避免数据降温后整体下沉的排队拥塞,当然可以根据用户的需求进行灵活配置。此时,日志场景大量的查询会集中在本地,本地 primary 和 replica 也能很好的抗住读写压力。
第七个阶段,数据的查询频次有所降低。一般情况,本地的 primary 即可满足绝大部分查询性能需求。此时 replica 会从本地卸载,读取会走远端共享存储,同时本地会有缓存机制保存用户常用查询数据提升性能。此时的查询会优先打到 primary。通过卸载本地 replica,我们可以缩减约 50% 的 SSD 容量。
第八个阶段,查询频率大幅缩减。Primary 上只有部分数据或部分 segment 需要被查询,此时 primary 上的部分文件或 segment 会先被卸载。同时本地构建缓存体系加速查询。本阶段 SSD 的缩减达到 70% 左右,但仍然能满足业务的查询需求。
第九个阶段,查询几乎没有了,数据处于归档状态。本地的 primary 彻底实现卸载,依靠本地的缓存加速满足极少量的查询需求。本阶段 SSD 的缩减到达 90% 左右。
在整个数据生命周期中,segment 呈现三种形态:
上面是完整的索引生命周期中数据的演变过程,存储重心随着数据逐渐降温过程逐步从 SSD 到对象存储迁移,且用户无明显感知,最终整体存储成本下降 50% - 80%。
日志场景传统的降本方案一般采用冷热分层。混合存储引擎与冷热分层主要的区别包括:
截止目前日志场景海量数据的低成本存储优化到这里就介绍完毕了,后面继续介绍查询性能优化。
前面我们分析了日志场景的海量存储成本优化。数据存储降本之后,接下来要考虑的是数据流出,如何解决用户查询性能问题。因为混合存储底层采用对象存储,其和 SSD 的性能差异肯定是有一个量级的区别。我们需要系统性做一些查询优化、缓存优化来找回这种存储介质的变更带来的性能损耗。
我们先来看看混合存储引擎背景下,查询性能的瓶颈在哪些方面。
ES 有高效的倒排索引,点查的性能是非常强悍的。多索引或通过别名关联索引查询也是相对传统数据库的一个优势特性,而这一特性也会带来一些性能瓶颈问题。如上图所示,当我们查询的索引是一个星号,或者一个索引前缀匹配,或是一个别名的时候,底层可能关联多个物理索引,而我们的查询可能只有部分索引会有数据命中。而 ES 实际执行查询会将底层所有匹配的索引、包括每个索引底层的每个分片逐一遍历查询。这样会存在大量的无用索引、分片、segment 的查询空转扫描。
ES 底层分片之间是可以并行化的,但是单个分片内部多个 segment 之间是串行化的。混合存储引擎的底层是对象存储,多个 segment 串行化导致 IO 排队严重,查询效率低下,尤其是在大查询拉取数据较多的场景下。
基于上面两大影响面分析,我们针对性的优化思路是查询裁剪和查询并行化,下面我们来展开分析。
在日志场景,索引一般是按照一定的时间周期进行滚动,腾讯自研了自治索引,帮助用户托管索引分片的管理,用户无需关心底层分片的数量、大小、分布配置。简化数据接入门槛。在此基础上,我们维护了索引级别的 Min/Max,当查询请求进来的时候,可以针对用户的查询区间进行精准的物理索引过滤裁剪,大幅降低无用索引的空转扫描过程。通过索引分片维度的时序裁剪,大幅提升查询性能,内部视频日志业务实测查询性能提升 8 倍。
单个分片包含多个 segment,在查询过程中会依次遍历 segment 进行查询。但往往只有部分 segment 或者 segment 内部部分数据段存在有效数据,因此 segment 维度的查询裁剪也是优化的重点。
Segment 级别查询裁剪分为两个维度:
原生 ES 单个分片内部多个 segment 查询为串行化执行。Segment 并行化的思路主要是将多个 segment 的文档进行统筹规划,按照多线程切分,并行化查询。对于做完 forcemerge 的场景也能很好的提高并行度。于此同时,我们在拉取底层共享存储时,并不会整个文件拉回来,而是结合前面描述的数据裁剪能力,按照查询需要拉取局部的数据段,大幅提升拉取效率。混合存储引擎中,开启并行化查询优化相较原生版本查询性能提升 5 倍。
下一阶段,腾讯云 ES 将打造云原生数据平台,闭环 PB 级数据检索、分析场景,全面覆盖日志场景低成本、高性能的需求。底层基于共享存储,消除冗余副本,实现存算分离架构,中间计算层会提供多种维度的集群,实现读写分离,检索、分析场景分离等。上层提供各种免运维的数据服务,实现资源灵活调度,数据跨节点、跨逻辑集群挂载等。
目前腾讯云推出的 Elasticsearch Serverless 服务已覆盖上述大部分能力,后续会持续完善,敬请关注。