海量可观测性时序数据库的分布式查询演进之路 演讲人:字节跳动高英举 目录 1.可观测性TSDB演进路线 2.可观测性TSDB整体架构 3.可观测性TSDB查询性能稳定性优化经历 4.可观测性TSDB的过去、现在、未来 1亿+ metrics数量 百万 8000 6000 4000 每秒打点(datapoint)量 150000 100000 查询QPS 2000 50000 0 打点量 0 查询QPS 202120222023202120222023 线上服务种类、规模越来越大; 性能、稳定性要求越来越高 观测服务用途的时序数据写入、查询量数倍增长 完全依赖开源技术到大部分能 力自研 单节点到分布式的存储、查询 多架构(OpenTSDB,InfluxDBPrometheus)走向统一 cpu_loadmem_usagedisk_iops 写入SDK: Golang/Java/C++/Python 数据类型: Timer/Counter/Gauge/Histogram 查询: 实现了绝大部分OpenTSDB查询语法,可通过Grafana、Bosun等接入,亦可用OpenAPI查询数据 可观测性TSDB整体架构-整体架构 支持跨机房、跨节点的两级Scatter-Gather架构 [跨机房] [单机房跨节点] 内] [单节点 分布式查询计划(DAGPlan) 1.一次查询的逻辑执行计划最终会转换成一个DAG 表示的物理执行计划。 2.数据分片的IO,聚合计算,RPC请求都是DAG上的一个节点。 3.整个DAG被拆分成多个部分(如查询拆分机房、时间分片),调度到多个节点上。调度器根据节点间依赖的拓扑顺序来调度节点保证计算的正确性。 QPS峰值 ATB LatencyP99 LatencyP50 报警规则执行 110K 99.9% 1360ms 29ms 可观测数据开放API 27K 99.9% 1660ms 45ms 监控看板、大盘 0.6K 98.2% 10000ms 230ms 图注: (1)ATB=2xx/(2xx+5xx)*100% (2)Latency的统计排除了瞬时毛刺 (3)执行时间超过10000ms的查询主动timeout返回504 用户视角: (1)查询慢 (2)查询忽快忽慢不稳定,查询超时失败 (3)无法根据查询pattern做封禁/熔断重查询,只能封禁整个metric (4)单个请求查询7/14/30天及以上的数据,容易超时不返回数据 可观测性TSDB视角: (1)使用姿势问题:部分用户打点的tagset维度数动辄高达3000w+、2亿+维度 (2)使用姿势问题:部分用户单个请求中查询过去半年、一年的数据 (3)服务自身问题:在Planner层、调度层、执行层上存在不合理的设计实现 (4)服务自身问题:服务节点不稳定、内存资源瓶颈显著、GC开销较大,IO读放大 内存瓶颈:某cpuusage>60%的节点,其中超过25%的cpu用来做GC QLParser Planner Scheduler Execution Storage (1)SubQuery个数削减 (1)分布式执行模型 (1)轻重查询隔离 (1)查询限速熔断 (1)自适应动态分片 (2)多SubQuery合并 (2)自动物化视图选择 (2)亲和力调度 (2)Cache (2)物化视图构建 数据上报源头 (3)Splits划分 (4)推测执行 (3)filter算子下推 (4)JDK升级 (5)Java编码优化 (6)RPCcodec优化 (包括存储、计算) (3)数据模型优化 (4)基于数据特征做优化 (5)索引 (6)更精准的CBO数据 可观测性TSDB查询性能稳定性优化-轻重查询隔离 查询 客户端 单机房内的查询架构 queryproxy roundrobin的方式将请求调度到query节点 nodegroup0 nodegroup1 query query query 热存 冷存 以前:热存查询、冷存查询分开调度 热存查询:查询涉及的数据在热存冷存查询:查询涉及的数据在冷存 跨冷热存查询:涉及的数据在跨越了热存、冷存 问题: (1)轻查询、重查询混在一起执行,相互影响 (2)少量重查询导致大量轻查询Latency不稳定 (3)查询性能不仅受到存储介质的IO效率影响,与查询涉及的数据量也有关系 热存是基于纯内存构建的一套存储服务,存储最近28小时的数据冷存是基于HDFS构建的一套存储服务,存储28小时以前的数据 可观测性TSDB查询性能稳定性优化-轻重查询隔离 现在:轻查询、重查询分开调度 查询 客户端 单机房内的查询架构 queryproxy 将请求先发到query轻 查询执行节点,如果query认定其为重查询,则返回307Redirect,将请求重定向到query重查询执行节点 nodegroup0 307Redirect [Location] nodegroup1 query query query 热存 冷存 轻查询:查询涉及的数据量较少重查询:查询涉及的数据量较多 获取查询涉及数据量的手段 (1)通过存储服务对外暴露的API,访问延迟在2ms~10ms (2)存储服务内置统计数据(series数、dps数、bytes数) (3)考虑了filterpushdown的情况,存储服务可查询内存中的索引快速统计 收益: (1)重查询不影响轻查询 (2)轻查询Latency低且稳定 (3)不合理的重查询的执行受到限制 问题: (1)重查询可用资源更少,性能可能劣化 轻查询重查询 思考: (1)重查询是否是伪需求? (2)能否既要保障轻查询,还要保障重查询? 跨机房的查询架构 queryproxy 查询客户端 dc2 跨机房调度: 对于跨机房的查询,如果查询涉及的数据不在本机房,则通 过307Redirect到对应数据所在的机房 dc1 (1) (2) 307Redirect[Location] (3) dc3 (4) 收益: (1)避免了querynode作为多一跳的中转节点带来的数据序列化反序列化、networkio开销 (2)避免了作为中间跳板的节点内存的GC压力 具体查询案例: sum:service.node_stats[cpu_usage][dc=dc2|dc3] 如果要查询所有dc的数据,并且按dc分组,查询pattern如下: sum:service.node_stats[cpu_usage][dc=*] timeslot0timeslot1timeslot2 shard0 writer routingbyhash shard1 shard2 bucket0bucket0bucket0 背景知识:一个metric在存储的数据分布 假设 (1)存储有4个shard (2)某个metric在存储中有2个bucket bucket1bucket1bucket1 shard3 bigmetric数据分布情况smallmetric数据分布情况 bucket0 bucket0 以前:数据分布bucket数固定,与数据量无关 bigmetric:数据量较大的metricsmallmetric:数据量较小的metric bucket1 query bucket1 query bucket47 bucket47 问题: (1)smallmetric容易出现数据分布过度分散导 致存储效率低,查询时RPC过多并行度过大,产生惊群效应,进一步导致集群整体并发量上不去。 (1)bigmetric容易出现数据分布不够分散导致数据写入存储时处理堆积,查询时不好提高数据读取并行度 思考: (1)能不能仅通过查询的分片(Splits)划分策略来解决问题? bigmetric数据分布情况smallmetric数据分布情况 query bucket0 bucket0 bucket1 bucket96 query bucket1 现在:根据数据量做数据分布,决定bucket数 (1)自适应:根据metric数据量自动决策bucket数 (2)动态:可随时对bucket数扩容、缩容,不需要重启服务。 (3)生效延迟:<10min 收益: (1)显著增加了查询集群并发能力,降低了查询延迟。 (2)从存储源头根本上解决数据分布给查询带来的影响,使查询的数据分片逻辑可以更简单。 (3)对于数据的写入链路的性能、稳定性也产生了非常好的收益——数据倾斜的减少,处理资源与数据量更匹配。 程序如何做自动bucket决策? (1)写入链路消费上游数据时实时统计数据量 smallmetric数据分布情况 query Split3=shard1[timeslot2] Split2=shard1[timeslot0,timeslot1] Split1=shard0[timeslot2] Split0=shard0[timeslot0,timeslot1] 适用场景:查询涉及的metric是smallmetric timeslot0 timeslot1 timeslot2 bucket0 bucket0 bucket0 shard0 bucket1 bucket1 bucket1 shard1 收益: (1)支持bucket内的跨timeslot合并, 以便提高与存储的传输效率(RPC、序列化size) 如何确定查询的是smallmetric: 现在:多值的数据模型,存储模型、查询执行模型 以前:单值的数据模型,存储模型、查询执行模型 想要知道平均cpuusage,memusage,disk_iops需要3个查询avg:cpu.load[idc=1] avg:mem.usage[idc=1]avg:disk_iops[idc=1] 收益: (1)共享tagkey,tagvalue,显著减少写入链路流量 (2)显著减少存储对tagkey,tagvalue的字典化编码开销 (3)显著减少存储size (4)想要知道平均cpuusage,memusage,disk_iops只需要1个查询 avg:node.stats[cpu_load,mem_usage,disk_iops][idc=1] 等价SQL: SELECTavg(cpu_load),avg(mem_usage),avg(disk_iops)FROMmetric 现在:Counter类型广泛适用 逻辑模型 未来:推广DeltaCounter,减少Counter的适用 CounterValue RateValue DeltaValue CounterValue t0t1t2t3Counter:从0开始的持续累加值。 物理存储t0t1t2t3物理存储 逻辑模型 DeltaCounter:逻辑上同义,物理上存储3个Value。 适用场景:在可观测性领域常用于表示累加值(Counter)、变化量 (Delta)、变化率(Rate),后两者需要根据Counter与时间计算得到 问题: (1)由于只在客户端维护Counter累加值,客户端重启会导致 Counter值跌0,导致计算结果不符合预期。 (2)80%+的Counter类型打点,其查询实际上都是为了计算变化量、变化率,而不是累加值 (3)80%+的累计值计算场景,是为了获取某段时间的累加值, 而不是从0开始的累加值 适用场景:代替Counter类型。 收益: (1)几乎不再受CounterValue跌0问题困扰。 (2)DeltaValue即变化量、RateValue即变化率,相关计算可直接给出结果,不再是复杂的分布式有状态计算。 (3)考虑到历史兼容性与用户使用习惯,DeltaCounter保留了CounterValue。可通过查询优化技术自动将查询中对CounterValue的计算,改写为对DeltaValue,RateVal