如何让ES低成本、高性能?滴滴落地ZSTD压缩算法的实践分享

前文分别介绍了滴滴自研的ES强一致性多活是如何实现的、以及如何提升ES的性能潜力。由于滴滴ES日志场景每天写入量在5PB-10PB量级,写入压力和业务成本压力大,为了提升ES的写入性能,我们让ES支持ZSTD压缩算法,本篇文章详细展开滴滴在落地ZSTD压缩算法上的思考和实践。

背景

ES通过索引(Index)对外提供数据检索能力,索引是用于组织和存储数据的逻辑单元。每个索引由若干个分片(shard)组成,每个分片就是一个Lucene索引,可以在不同的节点上进行分布式存储和并行处理,提高性能和可伸缩性。每个分片由一组段文件(segment)组成,段是分片中更小的存储和搜索单元,是一组物理文件,包含了检索需要的倒排索引(词项和文档ID的映射关系)和文档存储(字段值和其他元数据),如下图:
ES数据模型

Lucene作为ES的底层索引引擎,提供了灵活的数据检索能力,同时也导致CPU、存储占用较为严重。为实现降本增效,23年上半年,ES团队开启了Lucene压缩编码优化专项,通过改进存储层压缩算法,从而降低单位Document所占用的资源。本文概述了ES的底层索引文件,并介绍了Lucene存储压缩编码的优化。

Lucene索引文件介绍

ES的压缩编码优化专项涉及到Lucene底层的文件存储,Lucene索引由一组Segment构成,每个Segment包含了一系列文件,重点文件类型如下图:
Segment构成

Read More

解锁滴滴ES的性能潜力:JDK 17和ZGC的升级之路

前文介绍了滴滴自研的ES强一致性多活是如何实现的,其中也提到为了提升查询性能和解决查询毛刺问题,滴滴ES原地升级JDK17和ZGC,在这个过程中我们遇到了哪些问题,怎样解决的,以及最终上线效果如何,这篇文章就带大家深入了解。

背景

滴滴ES在2020年的时候由2.X升级到7.6.0,该版本是在官方7.6.0的基础上改造而来,支持的是JDK11,采用的垃圾回收器是G1。ES的业务主要分为两类,一类是日志场景,该场景写多读少,高峰期CPU使用率在85%左右,写入性能是它的主要瓶颈;另一类是非日志场景,例如POI检索、订单、支付,这些场景对查询耗时及查询稳定性都有着较高的要求。

随着ES业务数据量的增长,GC导致的查询稳定性压力剧增,已经逐渐无法满足业务需求。以下是ES面临的主要问题:

非日志场景的GC问题

1、Yong GC平均暂停时间长,一些集群的平均暂停时间达到百毫秒级别。

GC暂停时间长在订单集群这种112G大内存的进程中表现尤为明显。下图为订单集群的GC暂停时间,可以发现一次Yang GC的平均暂停时间就达到了200ms,有些甚至超过了1s。ES的核心业务POI、订单等对查询耗时的P99以及Max都有一定的需求,GC暂停导致的查询延时以及毛刺问题无法满足业务需求。

订单集群的GC暂停时间

Read More

探索ES高可用:滴滴自研跨数据中心复制技术详解

Elasticsearch 是一个基于Lucene构建的开源、分布式、RESTful接口的全文搜索引擎,其每个字段均可被索引,且能够横向扩展至数以百计的服务器存储以及处理TB级的数据,其可以在极短的时间内存储、搜索和分析大量的数据。

滴滴ES发展至今,承接了公司绝大部分端上检索和日志场景,包括地图POI检索、订单检索、客服、内搜及把脉ELK场景等。

近几年围绕稳定性、成本、效率和数据安全这几个方向持续探索:

  • 滴滴ES有很多在线P0级检索场景,为了提升集群稳定性,我们自研了跨数据中心复制能力,实现多机房数据写入强一致性,并配合管控平台让ES支持多活能力;
  • 为了提升查询性能和解决查询毛刺问题,我们在7.6版本上原地升级支持JDK 17;
  • ES日志场景每天写入量在5PB-10PB量级,写入压力和业务成本压力大,为了提升ES的写入性能,我们让ES支持ZSTD压缩算法;
  • 由于ES索引里包含很多敏感数据,我们又完善了ES的安全认证能力。

基于以上探索,我们总结了一定的经验,现分成4篇文章详细介绍。本篇文章介绍滴滴ES如何实现索引的跨数据中心复制从而保证索引的高可用。

滴滴跨数据中心复制能力 - Didi Cross Datacenter Replication,由滴滴自研,简称DCDR,它能够将数据从一个 Elasticsearch 集群原生复制到另一个 Elasticsearch 集群。如图所示,DCDR工作在索引模板或索引层面,采用主从索引设计模型,由Leader索引主动将数据push到Follower索引,从而保证了主从索引数据的强一致性。

DCDR跨数据中心复制能力图

Read More

Elasticsearch常用的命令

集群健康状态相关

查看集群健康状态

1
GET _cluster/health
number_of_pending_tasks:是指主节点创建索引并分配shards等任务,如果该指标数值一直未减小代表集群存在不稳定因素 。

查看未分配原因

1
GET _cluster/allocation/explain

有以下几种可能

  • INDEX_CREATED : Unassigned as a result of an API creation of an index. 索引创建 : 由于API创建索引而未分配的
  • CLUSTER_RECOVERED : Unassigned as a result of a full cluster recovery. 集群恢复 : 由于整个集群恢复而未分配
  • INDEX_REOPENED : Unassigned as a result of opening a closed index. 索引重新打开
  • DANGLING_INDEX_IMPORTED : Unassigned as a result of importing a dangling index. 导入危险的索引
  • NEW_INDEX_RESTORED : Unassigned as a result of restoring into a new index. 重新恢复一个新索引
  • EXISTING_INDEX_RESTORED : Unassigned as a result of restoring into a closed index. 重新恢复一个已关闭的索引
  • REPLICA_ADDED : Unassigned as a result of explicit addition of a replica. 添加副本
  • ALLOCATION_FAILED : Unassigned as a result of a failed allocation of the shard. 分配分片失败
  • NODE_LEFT : Unassigned as a result of the node hosting it leaving the cluster. 集群中节点丢失
  • REROUTE_CANCELLED : Unassigned as a result of explicit cancel reroute command. reroute命令取消
  • REINITIALIZED : When a shard moves from started back to initializing, for example, with shadow replicas. 重新初始化
  • REALLOCATED_REPLICA : A better replica location is identified and causes the existing replica allocation to be cancelled. 重新分配副本

查看具体索引未分配或不搬迁的原因

1
2
3
4
5
6
GET _cluster/allocation/explain
{
"index":"index_name",
"shard":0,
"primary":true
}

返回结果:

  1. The current state of the shard.
  2. The reason for the shard originally becoming unassigned.
  3. Whether to allocate the shard.
  4. Whether to allocate the shard to the particular node.
  5. The decider which led to the no decision for the node.导致该节点没有决策的决策器。
  6. An explanation as to why the decider returned a no decision, with a helpful hint pointing to the setting that led to the decision. 解释为什么决策器返回“否”决策,并提供一个有用的提示,指出导致该决策的设置。

解释一个索引为什么分配到该节点

1
2
3
4
5
6
7
GET _cluster/allocation/explain
{
"index": "my-index-000001",
"shard": 0,
"primary": false,
"current_node": "my-node"
}

重新尝试分配失败的shard

1
POST _cluster/reroute?retry_failed=true

查看等待中的任务

1
GET _cat/pending_tasks

可以看到任务都被指派了优先级( 比如说 URGENT 要比 HIGH 更早的处理 )

查看nodes

1
GET _cat/nodes

Read More

科普下搜索索引里常用的压缩算法

前言

数据压缩是存储领域常用的优化手段,压缩算法可以减少数据的大小减少存储成本、减少磁盘的寻道时间提高I/O的性能、减少数据的传输时间并提高缓冲区的命中率,节省的I/O时间可以轻易补偿它带来的CPU额外开销。目前在用的主流压缩算法包括zlib、snappy和lz4等。压缩算法并不是压缩比越高越好,压缩比越高,其解压缩速度可能越慢,CPU消耗就会越大,这需要根据硬件配置和业务场景做Trade off。本文主要介绍了如下几种搜索索引里常见的压缩算法,大部分压缩算法也适用于OLAP领域,同时有些场景可能需要结合多种场景实现,比如倒排索引的posting list压缩就需要结合PForDelta及Simple算法才能获得更好的压缩比。同时在选择压缩算法时,也会考虑该压缩算法是否支持流式压缩等。

  • Fixed length
  • Variable Byte
  • Improved Variable Byte
  • Group Varint
  • Run Length Encoding
  • Dictionary Coding
  • Simple 9
  • Simple 16
  • PForDelta
  • Huffman Coding
  • LZ77
  • Elasticsearch 行存压缩算法

Fixed length

压缩方法

找到一组数据的最大值,之后计算出最大位宽N:

示例

1
10,35,100,170,370,29000,30000,30010

2^15 = 32768 > 30010,则位宽为15,即每个32bit的数据可以用15bit表示

Read More

Elasticsearch性能调优之毫秒级搜索POI业务

背景

地图POI搜索业务使用的Elasticsearch集群是自建的2.3版本,维护ES需要耗费很多精力和人力。而目前公司有专门的ES搜索团队,且ES版本已经升级到7.6版本。ES 7.6版本有很多新的特性,如IndexSorting,查询加速,索引off-heap,全新的熔断器等等。6月份,ES搜索团队决定与地图POI合作共建,将ES升级到公司里的ES 7.6,专业的人做专业的事,ES团队维护集群稳定性、提升性能等,这样地图POI团队可以更好的聚焦到业务架构和推荐系统中台化上。

地图POI分为国内POI和国际化POI,其面向在线搜索业务,对搜索耗时要求比较高,国内POI要求TP50耗时小于5ms,TP99 耗时小于20ms,国际化POI要求TP50耗时小于5ms,TP99耗时小于60ms。且两个业务方的索引查询超时时间都为180ms。由于其业务特点,所以在迁移ES 7.6时,并非一帆风顺,遇到很多性能和稳定性问题,这篇文章会介绍下遇到的性能问题和解决方法。由于篇幅有限,本文只介绍了影响性能最大的三个问题及优化手段和排查问题过程,分别为:

  • 前缀查询优化
  • 查询截断
  • 查询超时毛刺

前缀查询优化

调好合适的分片数,之后将索引从Hive离线导入到ES,做一轮压测,耗时曲线图如下图所示,耗时都接近800ms了,并且超过180ms就认为是超时,这与期望的性能有比较大的差距:

通过gateway里面的审计日志,计算出国际化POI TP50、TP90、TP99时间,如下图所示:

可以看到TP99时间到了619ms,这与预期的性能有很大差距。所以我们需要看下为什么查询性能这么慢,我们将审计日志里面的压测DSL全部拿到,之后根据查询耗时降序排序,然后将DSL模板归类,发现耗时最久的是类似下面这种DSL:

Read More

ES 内存管理分析

刚接触ES,研究了下ES内存管理,参考了一些文章,整理了一篇文章,方便自己记忆。

命令 GET _cat/nodes?help 列出所有node, 并展示node所在机器的运行状态信息,help可显示帮助信息

1
GET _cat/nodes?h=name,hp,hm,rp,rm,qcm,rcm,fm,sm&v

解析下上面参数的意义

信息如下:

以红框里的node为例, 内存占用 = (8.6 gb)qcm + (1gb) rcm + (0.35gb) fm + (2.2 gb)sm,大约12 gb。

Read More