10倍性能提升-SLS Prometheus 时序存储技术演进
阿里妹导读
可观测近年来一直是非常火热的话题,围绕着可观测国内外诞生了非常多的创业公司,包括老牌的监控、APM、日志厂商也纷纷进入,追求在一个产品上同时支持日志、监控、Trace等多种可观测能力。
回归到可观测概念的本质,是针对各类海量数据(Log/Trace/Metric...)进行的业务创新。而支撑数据创新的核心是一套稳定、强大、普惠的存储计算引擎。
相比业界使用多套方案(例如日志用ES、Trace用ClickHouse、Metric用Prometheus)实现统一的可观测上层能力,SLS则从数据引擎层开始针对所有可观测数据进行统一的架构设计。因此,我们在2018年在Log模型的基础上,发布第一版的PromQL语法支持,验证了PromQL的可行性。随后对存储引擎进行重新设计,能够在一套架构、一个进程内实现对Log/Trace/Metric的统一存储。
SLS时序引擎正式对外收费3年多来,我们对时序的整体架构进行了多次升级,本文将介绍近期SLS Prometheus存储引擎的技术更新,在兼容 PromQL 的基础上实现 10 倍以上的性能提升。同时技术升级带来的成本红利也将回馈给使用SLS 时序引擎的上万内外部客户。
技术挑战
在上篇的文章《拥抱云原生-SLS支持Prometheus协议》中,我们介绍了SLS针对Prometheus时序场景的一些技术方案,包括:
存储层针对时序场景优化格式,减少IO数量和压缩率 在SLS集群Hold Prometheus计算引擎,用户不需要自己准备计算资源 存储和计算层使用时序格式进行数据传输,提升传输效率
同时借助SLS纯分布式、存储计算分离架构的能力,Prometheus计算性能相比原生Prometheus能够支撑更大的数据规模,包括对于时间线膨胀问题也可以完美解决。
但随着越来越多的客户和大规模场景应用(例如超大规模K8s集群监控、高QPS业务平台告警、大规模指标训练等),众多业务方的整体写入、查询量级越来越大,对SLS 时序存储的整体性能和资源消耗提出了更高的要求:
快:数据规模超大的情况下,也能让点查的QPS足够高,同时范围查询的延迟进一步降低; 简:使用上更加简单,不需要过多的手动优化,例如HashKey聚合写入、手动降采样、跨Store数据汇总等; 省:降本增效同时进行,希望成本能够进一步下降,快的同时能够再省省;
为此我们近一年来对Prometheus时序引擎的整体方案进行了多轮优化,实现查询十倍性能提升的同时,还能让整体成本更低。
SLS 时序引擎技术升级
更智能的聚合写入
由于时序数据具有高度的聚合特性,同一时间线的数据存储在一起会具备超高的压缩效率,因此在写入时,如果能够将相同时间线的数据放到同一Shard存储,无论是存储效率还是读取效率都会有较大提升。
最开始阶段,我们要求用户使用SLS的Producer在客户端按照时间线进行聚合,再指定Shard Hash Key写入到特定的Shard,这种方式对于客户端的计算和内存要求较高,对于RemoteWrite、iLogtail等方式不具备使用条件。而当前线上使用iLogtail、RemoteWrite、数据分发场景比例越来越高,导致SLS集群整体的资源效率变低。
为此我们在SLS网关侧实现了一个可以针对所有MetricStore的聚合写入方案,客户端无需使用SDK聚合(当然不影响当前SDK侧聚合写入的场景),数据随机写入到一个SLS的网关节点,网关内部会进行自动聚合,保证一条时间线的数据存储在一个Shard上。相比客户端的SDK聚合写入,优劣比较如下:
盯屏救星:全局Cache
时序场景的一个重要应用,同时也是对查询压力最大的场景是Dashboard,尤其是压测、大促、故障排查场景,顺时会有多位同学同时发起针对某个Dashboard的访问。多人访问热点的问题,大家想到的第一点一定是Cache,但在PromQL中,每次发起请求都有可能精确到秒或毫秒,而且PromQL的逻辑也会使每一个Step的计算都会从请求的精确时间进行数据查找,如果直接缓存计算结果,即使1秒内同时发起的请求也很难直接命中。
为此我们尝试使用Step对齐的方式,把PromQL查询的Range按照Step进行对齐,这样内部的结果每个Step都可以复用,可以大大提高缓存的命中效果。整体策略如下:
用户请求进入到任意一个计算节点时,若启动了Cache Feature,会根据Step进行Range修正; 以修正后的Range访问SLS Cache Server,获取命中的Cache Range; 对于没有命中的Range,向SLS后端查询数据并进行计算; 拼接结果返回给客户端,同时将增量的计算结果更新到Cache中;
需要注意的是,这种方式相比标准的PromQL行为有一定的修改(只是会做Range对齐,其他计算逻辑没有修改),实际测试中Range是否对齐对于结果趋势几乎没有影响。我们在MetricStore的配置中,同时支持按照MetricStore开启或按需开启(请求的URL Param控制,例如)。
PromQL也可以分布式并行计算
在开源的Prometheus中,对于PromQL的计算是完全单机、单协程的,这种方式在一些小型企业场景中较为试用。当集群变大时,参与计算的时间线会剧烈膨胀,这时单机、单协程的计算完全无法满足需求(通常对于一个几十万的时间线,查询几小时都会有十多秒的延迟)。
为此我们在PromQL的计算逻辑上,引入了一层并行计算架构,将大部分的计算量分布到Worker节点,Master节点只做最终的结果聚合,同时计算并发数和Shard数解耦,存储和计算都可以独立缩扩容。整体的计算逻辑如下:
用户请求进入到任意一个计算节点时,会根据一些策略(例如Query是否支持、用户指定开起、历史查询时间线较多等)决定是否使用并行计算; 若判断使用并行计算,则这个计算节点升级为Master(虚拟角色),将Query进行并行拆分; Master节点将子Query发送到其他Worker节点(虚拟角色)执行; Worker节点执行子Query并将结果返回给Master; Master最终汇总所有结果并进行最终的结果计算;
受限于PromQL的特性,并不是所有的Query都支持并行计算,也并不是所有支持并行计算的Query都能得到很好的效果。但我们分析了实际线上的请求,90%以上都能支持且得到加速。
极致性能:计算下推
上述并行方案解决了计算并发度的问题,但对于存储节点而言,无论是单机还是并行,发送到计算节点的数据量并没有变化,序列化、网络传输、反序列化的开销并没有消失,反而并行计算方案还会稍多了一些开销,因此对于整体集群的资源消耗并没有本质上的变化。同时,我们针对当前SLS存储计算分离架构下的Prometheus性能进行分析,其中绝大部分的开销都在PromQL计算、Golang GC和反序列化处。
为此我们开始尝试,能否把部分的PromQL下推到SLS的Shard上去做计算,将Shard当做是Prometheus Worker。下推计算部分的选择也有两种:
使用标准的Prometheus Golang计算引擎,支持的Query最丰富,但还是避免不了序列化、反序列化开销; 使用C++实现Prometheus的部分算子,直接在C++侧执行,避免序列化/反序列化的同时,还可以减少GC开销;
相比之下,方案2可以得到更优的效果,但可能工作量较大。为此我们再次分析了线上所有用户的Query,发现80%+场景下使用的都是有限的几种常见Query,相对实现代价很低。因此最终我们选择方案2:手写一个支持常见算子的C++ PromQL Engine(性能对比参见后文)。
需要注意的是,计算下推的前提是同一时间线的数据必须存储在一个Shard上,即需要开启聚合写入的功能或者使用SDK手动写入。
计算下推和并行计算方式的优劣对比如下:
万众期待的内置降采样
用户使用ScheduledSQL功能,定期查询原始指标库,保留最新的/平均值作为降采样的值,存储在新的MetricStore; 查询时,判断查询区间适合的MetricStore,如果是降采样的MetricStore,在查询前还需进行一定的Query改写; 例如降采样的精度是10min,查询是 avg(cpu_util),由于默认是3分钟的LookBackDelta,10分钟的精度可能查询不出来,因此需要改成 avg(last_over_time(cpu_util[15m]));
这种方式无论从配置还是使用来讲,门槛非常高,需要有专业的研发来参与才有可能完整。因此我们在SLS后端支持了内置降采样的能力:
用户对降采样库,只需要配置降采样的间隔和指标存储时间即可; 内部SLS会定期将数据按照配置进行降采样(保留降采样范围内最新的点,相当于last_over_time)并存储到新的指标库; 查询时,会根据查询的Step和时间范围,自动选择适配的指标库;
例如指标精度分别为1m、10m、1h,Step为30m则会选择10m的库; 例如原始指标库只存3天,降采样存30天,查询7天则会选择降采样库;
如果查询降采样库,SLS计算引擎会自动对Query进行改写或对数据进行拟合计算,无需手动修改Query;
UnionMetricStore
SLS的众多内外部客户业务遍及全国各地,通常数据都会本地存储,而在做全局性的大盘、查询时,往往需要到多个地方手动查询或者自己开发网关进行多Project、Store的查询、聚合。得益于SLS分布式计算、计算下推等能力的技术实现,我们能够在引擎层支持高性能、低资源消耗的跨Project甚至跨Region的查询能力----Union MetricStore:
Union MetricStore 支持关联同一账号下多个Project、MetricStore; Union MetricStore 支持完整的PromQL;
注意:由于合规问题,UnionStore并不支持跨国的Project。
用户体感
上述是在4万条左右时间线下,SLS不同计算模式的性能对比;
受限于本地浏览器->Grafana后端 -> SLS后端的网络延迟,带cache的功能看起来还是有略微延迟,实际如果命中缓存时后端延迟在微秒级别;
上述是在最高200万+时间线下,SLS不同计算模式的性能对比; 200万+时间线情况下,SLS 普通模式报错原因:防止一个Query直接把标准Prometheus计算节点打OOM;
和开源Prometheus的性能对比
注意:测试时,我们并没有打开全局Cache和降采样的功能,主要是这两个场景在命中时的性能提升过于巨大,放到同一象限进行对比意义不大。
注意:上述SLS的并行计算和计算下推的PromQL还是完全兼容PromQL语法,兼容性测试通过率为100%,下述是市面上开源、商业版Prometheus的兼容性结果
开源数据源:Prometheus Benchmark
Prometheus:单机部署在一台32C128G ECS,存储PL1 ESSD
Thanos:相关组件部署在5台32C128G ECS,存储PL1 ESSD + OSS
VictoriaMetrics:相关组件部署在5台32C128G ECS,存储PL1 ESSD
SLS:三个不同场景,分别为4、16、64个Shard
100 Target:同一时刻时间线上限 2.56W,汰换率 1%(每10分钟更新一次)
1000 Target:同一时刻时间线上限:25.6W,汰换率 40%(每10分钟更新一次)
5000 Target:同一时刻时间线上线:128W,汰换率 80%(每10分钟更新一次) 注意:汰换率用于表示指标更新速度,在传统场景通常变化不大,但在微服务、云原生、机器学习等场景,由于Pod、服务、Job 等生命周期短且会动态变化(每次生成一个新的 ID,也即新的时间线),指标汰换速度都会特别快(相关概念和时间线膨胀基本一致)
sls-normal:SLS Prometheus引擎基础版本,即完全使用开源Prometheus的计算引擎,存储用SLS;
sls-parallel-32:SLS Prometheus并行计算版本,32个并发度的并行计算(计算引擎还是开源Prometheus);
sls-pushdown:SLS 计算下推版本,常见Query使用C++实现的Prometheus计算引擎;
得益于使用自有逻辑实现了PromQL(兼容性较低),VictoriaMetrics整体的性能都还不错,尤其是时间线少的情况下,延迟很低;
sls-normal版本由于和开源Prometheus使用相同计算引擎且都是单协程计算,两者性能基本一致;
thanos相对更关注在分布式的解决方案,PromQL计算性能较一般,大Query会超时或者OOM掉;
sls-pushdown在超大规模时间线下提升较为明显,整体相比sls-normal版本都有10倍以上的提升(完全下推场景下实际会有百倍的性能提升);
后续会有详细的测试报告发出,本文就不多占篇幅
省
聚合写入:通过聚合写入,大大节省了SLS后端存储、计算资源,单位数据下的成本更低;
降采样:通过降采样配置,高精度时序数据无需长期保存,整体成本下降明显;
单GB价格下降
注意:价格调整相关工作正在进行中,敬请期待~
降采样成本优化
总结
联系我们
参考:
https://promlabs.com/promql-compliance-tests/
https://github.com/prometheus/compliance
https://github.com/VictoriaMetrics/prometheus-benchmark
https://ata.alibaba-inc.com/articles/168893
https://github.com/promlabs/promql-compliance-tester
https://github.com/prometheus/prometheus
https://github.com/VictoriaMetrics/VictoriaMetrics
https://github.com/thanos-io/thanos
欢迎加入【阿里云开发者公众号】读者群
微信扫码关注该文公众号作者