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

前言

数据压缩是存储领域常用的优化手段,压缩算法可以减少数据的大小减少存储成本、减少磁盘的寻道时间提高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

使用火焰图定位 OLAP 引擎瓶颈

在维护 OLAP 引擎时,很多时候需要对引擎做系统的性能分析和优化,此时往往需要查看 CPU 耗时,了解主要耗时点及瓶颈在哪里。俗语有曰:兵欲善其事必先利其器,程序员定位性能问题也需要一件“利器”。性能调优工具(perf)能够显示系统的调用栈及时间分布,但是呈现内容上只能单一的列出调用栈或者非层次化的时间分布,不够直观。火焰图(flame graph)能够帮助大家更直观的发现问题。本文将以 Presto 为例,介绍火焰图的使用技巧。

初识火焰图

Perf 的原理是这样子的:每隔一个固定的时间,就在 CPU 上(每个核上都有)产生一个中断,在中断上看看,当前是哪个 pid,哪个函数,然后给对应的 pid 和函数加一个统计值,这样,我们就知道 CPU 有百分几的时间在某个 pid,或者某个函数上了。而火焰图(Flame Graph)是由 Linux 性能优化大师 Brendan Gregg 发明的,和所有其他的 profiling 方法不同的是,火焰图以一个全局的视野来看待时间分布,它从底部往顶部,列出所有可能导致性能瓶颈的调用栈。

火焰图整个图形看起来就像一个跳动的火焰,这就是它名字的由来。火焰图有以下特征(这里以采样CPU 火焰图为例):

  • 每一列代表一个调用栈,每一个格子代表一个函数。
  • 纵轴展示了栈的深度,按照调用关系从下到上排列。最顶上格子代表采样时,正在占用 CPU 的函数
  • 横轴的意义是指:火焰图将采集的多个调用栈信息,并行关系。横轴格子的宽度代表其在采样中出现频率,所以一个格子的宽度越大,说明它是瓶颈原因的可能性就越大。
  • 火焰图格子的颜色是随机的暖色调,方便区分各个调用信息。
  • 其他的采样方式也可以使用火焰图, 比如内存

所以,火焰图就是看顶层的哪个函数占据的宽度最大。只要有”平顶”,就表示该函数可能存在性能问题,也是我们性能优化收益最大的地方。
Java生态常见的用于perf的工具有:allocation-instrumenter、YourKit Profiler、async-profiler、JProfiler、Arthas(基于 async-profiler )。笔者推荐使用阿里巴巴出品的 Arthas 或 async-profiler,笔者喜欢使用 async-profiler 这个 perf 工具生成火焰图,主要原因是用法简单,足够满足日常排查性能问题了。

Read More

说下那些导致Presto查询变慢的JVM Bug和解决方法

背景

维护Presto集群,遇到了一些JVM Bug会严重导致Presto查询变慢,这里分享下Bugs表现及解决方法。

Ref Proc 耗时太久

线上Presto运行一段时间后,查询会越来越慢,打了下火焰图,发现30%的CPU都浪费在young gc上了,看了下gc log,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

2020-12-21T20:50:15.009+0800: 4753827.928: [GC pause (GCLocker Initiated GC) (young) (initial-mark), 0.5723063 secs]
[Parallel Time: 66.9 ms, GC Workers: 33]
[GC Worker Start (ms): Min: 4753827940.4, Avg: 4753827940.5, Max: 4753827940.7, Diff: 0.4]
[Ext Root Scanning (ms): Min: 26.2, Avg: 27.3, Max: 54.9, Diff: 28.7, Sum: 899.6]
[Update RS (ms): Min: 0.0, Avg: 9.2, Max: 9.8, Diff: 9.8, Sum: 304.3]
[Processed Buffers: Min: 0, Avg: 19.9, Max: 35, Diff: 35, Sum: 657]
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Object Copy (ms): Min: 0.5, Avg: 11.2, Max: 13.8, Diff: 13.3, Sum: 369.9]
[Termination (ms): Min: 0.0, Avg: 7.8, Max: 8.4, Diff: 8.4, Sum: 256.8]
[Termination Attempts: Min: 1, Avg: 1.5, Max: 4, Diff: 3, Sum: 51]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.5]
[GC Worker Total (ms): Min: 55.3, Avg: 55.5, Max: 55.7, Diff: 0.5, Sum: 1831.3]
[GC Worker End (ms): Min: 4753827996.0, Avg: 4753827996.0, Max: 4753827996.1, Diff: 0.1]
[Code Root Fixup: 0.4 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.6 ms]
[Other: 504.4 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 501.3 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.5 ms]
[Humongous Register: 0.2 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.2 ms]
[Eden: 320.0M(3264.0M)->0.0B(3392.0M) Survivors: 416.0M->288.0M Heap: 9068.7M(72.0G)->8759.2M(72.0G)]
[Times: user=1.70 sys=0.39, real=0.57 secs]

从上面我们可以看到 Ref Proc: 501.3 ms,总共young gc耗时0.57s,而Ref Proc耗时就达到了0.5s。我们需要看下Ref Proc耗时主要在哪个地方。
我们通过配置gc参数-XX:+PrintReferenceGC,可以看到详细的Reference GC时间,并且如下图Case,JNI Weak Reference是主要瓶颈点。

Read More

解决JIT Deoptimization,ES插入性能提升7倍

背景描述

ES节点写入到5.7K,会产生比较多的reject,CPU利用率才30%,部分索引延迟在增加。如图所示:

CPU使用率在30%,但是Rejected记录达到8W行

原因分析

写入场景,CPU打不满且出现异常,根据之前Presto的经验,大概率是JVM问题。打下火焰图看下:

可以看到接近35%的CPU在做JIT Deoptimization。

JIT

为了提高热点代码(Hot Spot Code)的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(JIT)。

Read More

Presto Master JVM Core问题调研

背景

Presto master出现jvm coredump情况,排查问题,这里记录下排查过程。

排查过程

先看下JVM Coredump日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 12288 bytes for committing reserved memory.
# Possible reasons:
# The system is out of physical RAM or swap space
# In 32 bit mode, the process size limit was hit
# Possible solutions:
# Reduce memory load on the system
# Increase physical memory or swap space
# Check if swap backing store is full
# Use 64 bit Java on a 64 bit OS
# Decrease Java heap size (-Xmx/-Xms)
# Decrease number of Java threads
# Decrease Java thread stack sizes (-Xss)
# Set larger code cache with -XX:ReservedCodeCacheSize=
# This output file may be truncated or incomplete.
#
# Out of Memory Error (os_linux.cpp:2640), pid=22120, tid=0x00007ef9d2ed3700
#
# JRE version: Java(TM) SE Runtime Environment (8.0_144-b01) (build 1.8.0_144-b01)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode linux-amd64 )
# Core dump written. Default location: /data1/cluster-data/core or core.22120 (max size 52428800 kB). To ensure a full core dump, try "ulimit -c unlimited" before starting Java again
#


--------------- T H R E A D ---------------

Current thread (0x00007eff5eaf5000): JavaThread "IPC Client (1499102350) connection to presto-host.nmg01/data-node:8020 from user" daemon [_thread_new, id=28463, stack(0x00007ef9d2e93000,0x00007ef9d2ed4000)]

Stack: [0x00007ef9d2e93000,0x00007ef9d2ed4000], sp=0x00007ef9d2ed2760, free space=253k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V [libjvm.so+0xacb18a] VMError::report_and_die()+0x2ba
V [libjvm.so+0x4ff4db] report_vm_out_of_memory(char const*, int, unsigned long, VMErrorType, char const*)+0x8b
V [libjvm.so+0x927d23] os::Linux::commit_memory_impl(char*, unsigned long, bool)+0x103
V [libjvm.so+0x927dec] os::pd_commit_memory(char*, unsigned long, bool)+0xc
V [libjvm.so+0x9217ba] os::commit_memory(char*, unsigned long, bool)+0x2a
V [libjvm.so+0x9261df] os::pd_create_stack_guard_pages(char*, unsigned long)+0x7f
V [libjvm.so+0xa6ffce] JavaThread::create_stack_guard_pages()+0x5e
V [libjvm.so+0xa797b4] JavaThread::run()+0x34
V [libjvm.so+0x92a338] java_start(Thread*)+0x108
C [libpthread.so.0+0x7dc5] start_thread+0xc5

Read More