摘要:
此前的文章中介绍了使用多核cpu去分子集的访问元组并解析数据,以提升访问性能的一般性思想。但是在工程实践中遇到了很多现实性的问题。
本文对遇到的问题做追踪。
数据结构决定算法
采用何种数据结构,决定了所能执行的操作。或者说,就是在设计程序时的数据模型,甚至于说是数学模型,决定了其上的操作。
重提这句话是目的, 在于说明做多线程并行时,困难点不在于线程的任务的包装,而在于更底层的模块中包含的数据,这些数据被封装在不同的类之后,模块的抽象级别和内聚程度。
这决定了在物理存储引擎之上的逻辑层,被限制到了何种程度,以及以何种粒度来使用物理引擎的数据结构。
依赖倒置: 下层决定了上层的逻辑,而非上层决定了下层的逻辑
设计模式的这个原则,目的首先是为了从横向对功能进行分解,其实也很好理解,更下层面临更多的要处理的细节,给上层提供的接口,必然是本层抽象之后的。必须首先对更底层的进行建模,然后逐层抽象,如果直接由上层的逻辑来决定下层模块的内部逻辑,表面上看起来符合问题分解和分治原则,但是现实情况是更上层由于受业务需求影响更大,导致承载的需求变动距离,架构设计层面更多考虑灵活性,这种灵活对于底层的数据模型而言是灾难性的。难以想象由于逻辑层的需求调整,导致物理层的存储实现方式的修改。
在理解了一些设计的常识之后,就可以理解下面所面临的困境了。
直接修改为多线程访问不同的元组子集所产生的问题:
一. 多线程切换后, 出现上一次本线程加载的pack数据无法访问,导致需要重新加载
问题现象:
可以参考: 2022-09-07 mysql/stonedb-多线程遍历元组问题分析_帝尊悟世的博客
- 单个线程访问没问题
- 开启多个线程,但是其他线程不去处理,仅留一个线程处理,也没问题
- 开启多个线程并行处理, 切换到本线程之后,上一次加载的pack数据丢失
- 开启多线程并行处理,在每一次处理元组时都强制加载一次pack数据,处理结果正确。但是由于每次处理元组多加载pack数据,总耗时大于单线程时的处理
问题分析:
开启多个线程,但是只有一个线程去处理任务,不发生问题,那么
- 排除元组分割和任务数据的问题
- 排除对pack数据读取和解压的问题。
线程切换导致本线程已经读取的pack无法再读取, 有以下原因:
- 其他线程读取的pack数据覆盖了本线程读取的pack数据
- 排查思路是: pack数据读取后存放的位置 -> 锁定cache -> cache缓冲区的修改逻辑
- 需要思考是否此前的读取逻辑中,如何设计读取数据后的缓冲区
- 其他线程对本线程的数据产生了更多的临界区干扰
- MIterator指向了MulIndex, 与DimenGroup存在引用关系
- 对迭代器做深复制,只是复制了对底层数据的引用,下层数据还是位于临界区。采取类似并行HashJoin的任务模型做更多的模块关系的拷贝
- 这里存在可行性问题,并行HashJoin模块从输入到输出,以及其中的处理都与直接遍历迥异。需要更多的时间理解并行HashJoin对于模块的数据拷贝和单个任务中如何存放单个任务的数据缓存。
- 更下层的虚拟列和物理列与Pack和DPN的交互
- 提出这点主要是dump的位置正是在物理列中通过DPN拿到对Pack的引用发现没有数据,而对Pack数据的提取在于LockPack时。时序上位于遍历到Pack的开始时,调用LockPack加载下一次Pack的数据。
- 线程切换后,发生已经加载的Pack的数据丢失。具体为通过rc_attr拿数据时,是从物理列提取,此时Pack的数据是应位于cache内被使用。
问题解决:
问题的解决不是那么清晰,原因在于难以在不破坏其他模块的情况下,仅修改逻辑层的处理,就达到目的
- 线程读取Pack后的Cache与其他线程存在覆盖问题(需要更多的定位)
- 对虚拟列和物理列的所有逻辑,并未掌握全部细节
- 即使掌握了全部细节,如果该设计不符合需求,也面临被改造,那么就必须掌握所有相关模块的逻辑,从而保证改造不会出现未知风险。
- 同时面临AP领域业务知识的缺乏和数据库理论的具体实现的实践的缺乏
要解决这些问题,有一些做法,但是都需要耗费精力的投入和模块逻辑变动引发的连锁反应的风险,更别说所要面临的投入时间上的损耗
- 耗时难以评估
- 拆解出,单个线程,读取一次元组数据,所涉及的所有的模块的变量的修改
- cache缓冲区
- status统计信息
- SQL被yacc编译后保留的谓词信息
- DimenGroup (维度表)
- IndexTable (索引表)
- Mulindex/MIterator
- 在逻辑层, 对读取所涉及到的模块进行深拷贝, 对数据结构进行拷贝
- 可以对照并行HashJoin的处理
- 搞这么蛋疼的原因,就在于上面提到的,数据结构决定算法,底层模块决定了上层模块的逻辑。
- 作个对比, clickhouse用了个简单粗暴的做法,保持列的不变性,任何读取和插入都创建一个新的列来存放结果,原有的列保持只读。当做到了列的不变性时,就无所谓数据安全和并发的问题。毛子的暴力美学确实有一手。