摘要:
在多线程并行处理对元组的访问时,必然要对元组的读取有深刻的理解,从而保证在做架构设计时能正确的处理。
这里有个误区, 理解现有代码的设计, 其目的并非是要顺着现有代码的设计思想走下去,而是要明白,按照新的设计思路做下去,所做出的改动对原有设计的破坏程度。要小心破窗效应。
对pack读取的记录在 2022-09-02 mysql/stonedb-读取Pack数据流程记录_帝尊悟世的博客
本文分析在理解读取pack所作的处理时的思路。
读取pack的模块设计分析:
设计和架构不能脱离目的而存在,这个目的的直观表现便是对需求的分析,需求分析确定了功能性和非功能性的边界,这是设计所要达到的目标。
功能性需求:
- 向量化读取磁盘io, 也就是一次读取一个pack长度的数据。
- 对比innodb的按页读取, 目的在于减少磁盘IO, 更快的加载更多的数据
- 同样采取该策略的数据库可以参考clickhouse
- 存在缓存机制, 避免每一次获取pack都从磁盘读取
- 参考innodb的buffer缓存
- 实现四种事务隔离级别,保持事务的ACID特性
- 要处理脏读和幻读
- 要在保持事务的同时,保持性能.(具体参见非功能需求)
- 读取pack数据的接口能达到线程安全
- 线程安全主要影响数据分布的内存可见性, 此处主要涉及控制属性和状态属性
- 将pack读取后的数据的内存可见性视为共享,各个线程可见
- pack的数据为自渎, 避免对pack内的数据加锁
- 每个线程对pack的读取的状体属性私有, 避免线程安全问题
非功能需求:
非功能需求包含了性能需求, 安全性需求, 可维护性需求,部署运维升级需求等等, 不过这里主要关注性能需求指标
性能需求:
- 设读取一次pack需要耗时D的磁盘io和耗时S的解压时间, 但线程顺序遍历n个pack,需要总耗时T=n * (D + S)
- 多线程并行处理,设分割的线程数为p,简单的将n个pack分成p等份, 由p个线程分别处理,不考虑线程创建开销和锁保护的开销, 同时设定磁盘io速度未达到硬件上限, 那么最好的情况下,总耗时被p个线程均分, 为 T=(n/p)*(D+S)
- 性能指标上, 以cpu为主要处理目标, 在达到磁盘IO平静前, CPU的利用率达到90%
- 磁盘IO向量化处理的pack, 在涉及多个线程读取同一文件时, 不同线程读取不同的seek位置引发的文件锁机制,以及如何避免
- 工作线程之间的交互模型,主要是为了数据安全引入的锁机制导致并行能力下降的问题
- 此阶段先不进行SIMD/AVX的cpu指令集优化, 原因有下
- 对物理层和逻辑层的数据交互未彻底掌控
- cpu多指令集涉及对循环内多步骤的优化, 而clickhouse以列的不可变性为代价来进行更高维度的向量化优化, 一些优化策略涉及到底层数据结构的实现无法直接使用
- 利用多核cpu和cpu的多指令集可以都做, 但是精力有限, 一个一个做.
现有读取pack的模块设计:
如果从整体上看, 那么读取pack数据的模块是和整个架构的设计自洽的。但是也仅达到了数学上的自洽, 也就是可以正确运行。有以下几个问题需要考虑:
- 虚拟列和物理列, 交互规则是什么? 如何从虚拟列转换到物理列
- 列的元数据, 是从何时加载到内存的? 元数据是否线程安全?
- 读取pack后的缓存, 更新机制是什么?
- 如何保证数据不过期?
- 检测pack缓存的数据过期的机制是什么?
- pack缓存数据过期后, 是如何处理的?销毁缓存数据时候如何与使用方交互?
- 用了哪些锁? 锁保护的数据有哪些? 为什么一定要保护这些数据?
- 为什么一定要有VCPackGardian类, 除了对引用表做了包装,还做了什么? 对解耦物理列的处理列的访问, 减少了哪些不必要的接口交互
- 是否有必要分离出一种只读的pack属性? 专门应对读取?
- 可以避免哪些锁
- 会产生多少冗余数据?
- 对使用方的接口影响范围有多少?