支持大事务简要 notes

2019-12-01

我们为什么需要大事务

用户是有大事务的需求的

开启 batch dml 以后,无法保证事务的完整性

为什么当前不支持大事务

  • tikv 扛不住?
titan 对单个大的 key-value 的支持能力已经大大增强
prewrite 是按 region 的,单个 region 并没多大
我们可以先假设底层引擎是扛得住的
  • TTL 太小事务被干掉
事务的 primary lock 里面记录了事务状态,有一个 TTL 用于异常处理
TTL 的值太小,在 prewrite 过程中,事务就会被清锁干掉
TTL 设置的太大,会阻塞其它事务
如果事务异常挂掉,锁会很久清不掉,会一直 block 导致其它事务超时失败
解决方案:自动更新 TTL
  • 事务的阻塞
写写冲突,没法解决
读写冲突,**只要让大事务不阻塞读操作,就可以接受**
解决方案:新的事务模型
  • 事务冲突代价太高
不在解决范围之内
或者可以考虑悲观事务

TTL 自动更新

初始设置一个比较短的事务 TTL,然后在事务存活期间,自动更新 primary lock 的 TTL 值

这样就可以解决大事务的 TTL 问题

  • TTL 自动更新导致 ResolveLock 需要调整
  • TTL 自动更新需要引入死锁检测

死锁检测

乐观锁里面不需要死锁检测,因为 TTL 不会更新,即使发生了死锁,过了 TTL 锁也能清掉

自动更新 TTL 之后,需要引入死锁检测

ResolveLock

以前检查 secondary lock 跟检查 primary lock 都一样

操作步骤:

  • 检查到 secondary(primary) lock
  • backoff TTL 那么久
  • 调用 CleanUp 将 primary lock 给 Rollback
  • 调用 ResolveLock 清理 secondary lock

现在 secondary lock 里面信息无效,只有 primary lock 里面是真实的

  • 检查到 secondary(primary) lock
  • 去检查 primary lock 判定事务状态 (CheckTxnStatus)
  • 如果事务 TTL 过期,则清锁
  • 如果事务 TTL 没过期,则 backoff 之后重新检查

CheckTxnStatus 两种异常场景

遇到 secondary lock 了,再去检查 primary lock,发现 primary lock 不存在。

什么情况下会遇到?

  • 由于 prewrite region 并发,secondary lock 先写成功,primary lock 还没写
  • 由于悲观锁回滚不留下 tombstone

非阻塞读

为什么遇锁会阻塞读操作

一个事务正在写入,另一个事务去读取。能不能读正在写的内容?

  • 读了,事务回滚了 – read uncommitted
  • 不读,事务成功了 – lost update

读不读都有问题,所以要:等。

等写操作执行完了再读,就没这个麻烦了。

事务顺序

从第二个点想想办法,我们讨论不读

如果写事务在前面,读事务在后面,不读就会出现写丢失。

所以 关键点是重排序,让读事务在前面,写事务到后面,这样读事务就可以不读到写事务

如何调整事务顺序呢? 事务顺序是由 ts 决定的。

一个事务1的 start ts 大于另一个事务2的 commit ts,那么事务1是在事务2 后面的

假设每个 ts 都唯一,并且不考虑 +-1 问题,我们可以换一种说法:

一个事务1的 start ts 小于另一个事务2的 commit ts,那么事务1是在事务2 前面的

所以让读事务,把写事务的 commit ts 往后推,保证读事务 start ts 小于写事务 commit ts

即可达成顺序调整,从而实现重排序

min commit ts

  • 在 primary lock 里面,记录一个 minCommitTS 字段
  • 每次读者遇到锁的时候,调整 minCommitTS,使它大于 reader ts
  • commit 的时候,最终 commit ts 要至少大于 minCommitTS

即:每次读事务,遇到写事务,读事务会把写事务推到自己后面

测试中遇到哪些问题

并发压力过大,tikv 被打死了

prewrite 的时候,按 region 数量并发

大事务需要控制并发量,否则 tikv 会被打死

insert 执行慢

coprocessor 是并行读的

insert 的时候是串行的

在 insert 那里会形成一个瓶颈点,大事务比较明显

内存问题

memdb 是大块连续内存,在大事务下会分配失败 => 改造成分块的,每块大小上限 128M

insert 过程中,table 接口是用的 []Datum 表示,非常占内存 => 改成尽快刷到 memdb 里面

2pc 里面 mutation 的内存占用高

纯 KV 数据量 2-3G 的大事务,在 mutation 里面的表示大概 7-8G

加上其它包括 dirty table 和进程本身之类会到 10G

由于 Go 的 GC 策略,向系统申请的内存量会在 20G

大概 6 倍的放大

预计支持 10G 事务需要 60G+ 的内存

2-3G 的事务完成时间大概是 10min

transaction

HNS.to is a highly insecure way of browsing Handshake domains and should only be used for demo or educational purposes. Click to see preferable resolutions methods