baiwfg2 / awesome-readings

记录看各种文章、论文的心得
2 stars 0 forks source link

transaction, journal in wiredtiger #6

Open baiwfg2 opened 3 years ago

baiwfg2 commented 3 years ago

最近在研究 wt 的wal, transaction 这块内容,相关信息先buffer 在这里。

References

[1] https://blog.csdn.net/yuanrxdu/article/details/78339295, 事务原理,袁荣喜, 2017 [2] https://engineering.mongodb.com/post/breaking-the-wiredtiger-logjam-the-write-ahead-log-1-2 , wal 扩展性问题,Susan LoVerso ,2017 [3] https://engineering.mongodb.com/post/breaking-the-wiredtiger-logjam-the-wait-free-solution-2-2, Susan [4] https://zhuanlan.zhihu.com/p/42985707, 丁凯,介绍 wal 数据结构 [5] https://zhuanlan.zhihu.com/p/42986111, 丁凯,WAL 关键流程 [6] https://github.com/y123456yz/reading-and-annotate-wiredtiger-3.0.0 , 杨亚洲解析wt 3.0 [7] https://infoscience.epfl.ch/record/170505/files/aether-smpfulltext.pdf, [1] 中引入的论文,增强WAL 在多核上的扩展性 [8] https://jira.mongodb.org/browse/WT-1989, 与[2] 相关的Jira [9] https://blog.csdn.net/daaikuaichuan/article/details/97893552, wt 事务详解,不错

baiwfg2 commented 3 years ago

[2], [3], [7], [8]

问题来源是Susan 同事 Bruce Lucas 发现了 (他是使用 Poor man's profiler 发现的,这工具可试试)她以前实现的WAL 扩展性问题

Susan 原有设计

WT 内维护一个slot pool,默认大小 128个。当前活跃的叫 active slot. slot 是一块内存buffer,有一个重要成员是 int64_t slot_state 表示buffer里的字节数。 它被分成了三部分,最高位是状态,取值有READY, CLOSED。当我们说 a thread joins a slot that is READY是指线程在slot 中申请一块区域(offset, offset+size) 用以后来填充log data。Join 后,线程得等待直到 slot 变成CLOSED

offset=0的那个线程,我们称为leader,它负责等待slot 变成CLOSED,但它不仅仅只忙等。 还会从pool 中准备一个新的slot,用以后来的JOIN & WRITEs. 这个结束后,leader 将 slot_state 最高位置 1(这样成了负数),变成 CLOSED 状态。正在等这个slot 变成CLOSED 的线程就可以并行将数据拷贝到slot,它们都知道自己的offset,所以是无锁的;与其同时,各个log record size 被加到 slot_state 上,这样 slot_state 越加越小,直至变成 0. 这意味着所有的拷贝都线束了,可以供后台sync 线程刷盘了。

最初的设计就是这样的,运行完美,只要单核上的线程数不是很多。但当单核数线程变得很多时,像在mongo 场景中,一个连接一个线程,此时瓶颈就来了。此时很多线程就会在忙等一个state change,耗尽CPU

Susan 同事 Bruce Lucas 从一个全新的角度去解决问题,这很令人惊讶,他没有受到 Susan 原来代码逻辑的制约,他通过一个原型就验证了方案的可行性。

Bruce 的思路:消除线程等待

他仔细检查了这个算法的基础组件:

  1. 声明slot 区域与拷贝数据是可解耦的
  2. 申请区域,可以通过对index 变量做compare-and-swap来完成
  3. 这个index 变量是申请的总字节数,称为 join counter
  4. 所有线程都拷贝完才能写slot,所以得跟踪 slot 何时能写
  5. 拷贝期间,过来的字节数是 release counter,当 join == release,则表明 slot 可写进OS了
  6. 还需要跟踪slot 状态,判断何时能join,何时能写等

问题的关键是,为了安全写slot,线程必须原子性决定 state 是否允许,join值,release 值。而CPU 不支持超过2个寄存器的原子操作(Ref ?) . 所以想到把单变量 state 进行复用。 另外,也不要在线程join时自增slot_state,在release 时自减slot_state,原因再看看

因为最终分成两个阶段,join phase,线程申请空间,但不能立刻copy,得等待 release phase 开始; 在 release phase,线程写空间标识写字节数。这个方法也消除了leader 线程,线程之间不用区分责任。

有个特别的考虑:当系统空闲时,unfilled buffer 里的records 不应该不刷进系统,即使没有多余的写过来或者 j:true,所以增加了一个 50ms idle timeout,用以将 buffer 推到 OS,减小进程挂掉的风险。

最后实现由 Susan 完成。

Susan 最后的话我很欣赏:

像这种显眼的性能提升的机会不多见,毕竟是存储引擎,影响往往被上层给掩盖了。当你想让代码适应新环境时,有一位不受你的预设想法限制的伙伴是有帮助的。

baiwfg2 commented 3 years ago

http://mysql.taobao.org/monthly/2018/07/03/

3.x 没有向用户暴露事务,写操作内部还是由WT事务完成,提交由mongodb 控制; 4.0 引入 事务,提交由应用控制,可能很长时间没提交,造成cache压力

baiwfg2 commented 3 years ago

eviction

mysql 的自适应策略:https://leviathan.vip/2020/05/19/mysql-understand-adaptive-flushing/ 它最后选出的是要flush 多少个page ?

wt的策略不是自适应,而是简单的阀值判断 比如它未考虑WAL 的生成速度,eviction 的历史速率 https://www.bookstack.cn/read/aliyun-rds-core/443245c0392bf725.md