字节跳动时序存储引擎的探索和实践
如果无法正常显示,请先停止浏览器的去广告插件。
1. 字节跳动时序存储引擎的探索和实践
字节跳动基础架构研发工程师/陈骁
2.
3. 大纲
• 技术挑战
• 整体架构
• 热存Tsdc
• Khronos
• 未来展望
4. 时序数据模型
Field Values
•
时间序列数据是按照时间序列变化
的一组值,反映某个观测值随着时
Metric Tags Time CpuUsage MemUsage node.stat idc=1,host=0.0.0.0 2023.04.17 10:30:00 15 30 node.stat idc=1,host=0.0.0.1 2023.04.17 10:30:00 30 50 一的时间序列(e.g. 一个观测对
node.stat idc=1,host=0.0.0.0 2023.04.17 10:30:10 31 49 象)
node.stat idc=1,host=0.0.0.1 2023.04.17 10:30:10 50 50
间的变化
•
•
Metric Name + Tags标识一个唯
Field Values是具体的度量值,可
以有一个或者多个
5. • 写入点数每秒10亿+
• 查询QPS 100k+
• 指标名数量1亿+
• 活跃时间线1000亿+
字节时序数据库使用现状
1800 140000
1600 120000
1400
100000
1200
1000 80000
800 60000
600 40000
400
20000
200
0
0
2021
2022
2023
1
2021
2022
2023
6. Workload分析和对应的挑战
• 写远大于读,写入量非常大
- 线性扩展
• 查询以分析为主,点查为辅
- 面向分析查询优化的同时兼顾点查性能
• 超高维度
- 在单机亿级活跃维度情况下依然保证写入和查询性能
• Noisy Neighbours
- 租户间的隔离
- 防止个别超大metric影响整体可用性
7. 大纲
• 技术挑战
• 整体架构
• 热存Tsdc
• Khronos
• 未来展望
8. ByteTSD整体架构
9. 如何线性扩展
• 二级一致性Hash分区
- 先按照Metric做一次Hash分区
- 再按照序列做第二次Hash分区
• Metrics级别的动态分区
- 不同维度的Metric可以拥有不同的
二级Hash分片数
10. 如何保证隔离性
• ResourceGroup
- 被调度的对象
- ShardWeight表示需要多少资源
• Node
- 资源的容器,调度的目的地
- NodeWeight表示有多少资源
• 灵活设置Weight达成理想的资源分布
11. 大纲
• 技术挑战
• 整体架构
• 热存Tsdc
• Khronos
• 未来展望
12. 深入Tsdc
• 内存存储,提升热数据的读写性能
• 数据按时间分为多个Slot
- 最近的slot可修改
- 历史slot落盘释放内存
• 元数据只存一份
- TagKV字典化
- TagSet Varint编码后字典化
- 按需建索引
- 定时GC
13. 字典结构
• Dictionary = HashTable + Vector
• Vector = BlockIndex + Block
- O(1)的随机访问
- 对BlockIndex做快照,实现无锁的遍历
- 临界区很短,读写互不影响
• 异步Rehash
• 通过Epoch Based Reclamation机制回收
内存,避免无锁遍历时访问无效的内存
14. TagKeySet
Metric Tags Time CpuUsage MemUsage
node.stat idc=1,host=0.0.0.0 2023.04.17 10:30:00 15 30
node.stat idc=1,host=0.0.0.1 2023.04.17 10:30:00 30 50
node.stat idc=1,host=0.0.0.0 2023.04.17 10:30:10 31 49
node.stat idc=1,host=0.0.0.1 2023.04.17 10:30:10 50 50
TagKeySet: idc,host
• 观察数据特征
- 大部分序列拥有相同的TagKeys
• 每个序列的所有TagKeys称为
TagKeySet
• 直接编码整个TagKeySet
- TagSet中只存储一个id
- Encode时只做一次Hash
15. DatapointSet
6
5
7
8
9
Ringbuffer
5
Compressed Timestamp
• RingBuffer用于处理乱序写入,存储原始数据点
• 数据点划出RingBuffer后,写入TimeBuffer和ValueBuffer
• TimeBuffer使用delta of delta压缩
• ValueBuffer使用Gorilla压缩
16. 乱序写入优化
Question:
• RingBuffer容量有限
• Gorilla压缩算法只能append
OutOfOrder Index
15
2 17 18 19
Answer:
• 反向Gorilla压缩,能够Popback
• 乱序很久的点不写入ValueBuffer,查询
时合并
Ringbuffer
… 13 14 15
Compressed Timestamp
17. 查询优化
• 支持所有Filter下推,减少数据传输量
- 包括wildcard和regex,利用索引加速
• 自适应执行
- 根据结果集大小动态选择查询索引或者Scan
• 并行Scan
• 轻重查询隔离
- 轻重查询使用不同的线程池
- 根据维度和查询时长预估查询代价
18. 性能数据
• 实例规格24c 240G
• 平均活跃时间线1.2亿+,总时间线4亿+
• cpu使用率40%左右,内存使用率55%左右
• 平均写入量50w点每秒,单核吞吐8w/s
• 轻查询平均延时500us左右,p99 ms级
• 重查询平均延迟10ms左右,p99 百ms级
19. 大纲
• 技术挑战
• 整体架构
• 热存Tsdc
• Khronos
• 未来展望
20. 现有的问题
• 重启丢数据,运维负担大
Khronos
• 内存开销大,成本高
• 不支持单实例内单个Tenant多Shard,无法做负载均衡
• 冷热存消费两遍数据,成本高
• 三副本消费,数据容易发生不一致
待解决
21. Khronos存储引擎
目标:
• 降低内存使用,更低成本地支持单实例高维度数据
• 数据全部持久化,提升数据可靠性
• 保持高写入吞吐、低查询时延,提供高效的扫描,同时支持较好的点查性能
• 能够以较低的成本支持较长时间的存储,提供较高的压缩率以及对机械盘友好的存储格式
• 兼容Tsdc,最低成本接入现有集群
22. Inside a shard
• 每个Shard内部都是一棵独立的LSMT
• 一共分为三层
• 每一层都有一个虚拟的时间分区
- sstable文件不会跨时间分区
- Compaction在分区内调度
- 乱序写入的场景减少写放大
23. Memtable
• 基本延用了Tsdc的内存结构
• SeriesMap采用有序结构,Compaction依赖
Series有序
• SeriesKey = SeriesHashCode + TagSet
- 节省比较开销
- 快速拆分range,方便做分区内并行查询
24. SSTable格式
• 由于Metric数量非常多,所以将多个Metric
数据混合存储在一个文件中
• 文件尾部有Metric Index指向Metric的位置
- MetricIndex是一个Btree
- Page内部使用前缀压缩
25. Metric格式
• 类Parquet格式,行列混存
• 每行一个序列
• 大Metric会划分为多个SeriesGroup,减少内存占用
• 字典/Raw/Bitshuffle encoding
• Page索引加速查询
26. Flush优化
• 大量小Metric
- 存储格式Overhead大
- write次数太多,性能差
• BufferWrite
- 预先Fallocate一段空间然后mmap
- 数据通过mmap写入,减少syscall
• PaxLayout
- 所有Column写在一个Page
- 减少IO次数,减少元数据开销
27. SSTable查询优化
• 延迟投影
- 先读取带过滤条件的列
- 每过滤一个列都缩小下一个列的读取范围
- 最后投影非过滤列
- 数量级性能提升
• PageCache
- Cache中缓存解压后的Page,避免重复的解压和CRC校验
- 更进一步,直接Cache PageReader对象,节省构造开销
28. 大纲
• 技术挑战
• 整体架构
• 热存Tsdc
• Khronos
• 未来展望
29. 存算分离
• 基于分布式存储
- 提供大容量的存储,融合冷热存
- 多副本间做复制
- 分布式Compaction
- 快速负载均衡
30. 更多功能和优化
• 兼容社区
- String
- Bool
- 元数据查询
• 持续性能优化
- 更高效的数据传输协议
- 利用字典编码加速查询
- 算子下推
- ……
31. 加入我们
欢迎交流
32.