暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

​gh-ost核心原理:数据一致性和cut-over

神谕的暗影长廊 2020-10-20
1128

研发同学问,gh-ost做DDL时,业务可对原表做DML,同时一边apply binlog event,一边rowcopy到新表,那么问题来了,这仨操作存在先后顺序,不按顺序会产生数据不一致吗?

既然是说gh-ost,那么先放个图镇楼。


继续说正文)首先,举个栗子:

id  |  balance
--------------
1   |  100

变更过程中,因为不阻塞DML
,业务先对原始表+100
同时产生了该UPDATE
binlog event

id  |  balance
--------------
1   |  200

这行再被INSERT
到新表,此时id=1
balance
已经是200

最后解析binlog apply
到新表时,将UPDATE
语句再次apply
balance
会变为300
吗?


所以,不一样的顺序是否会造成数据不一致?

DDL
更变的过程中,涉及三种操作:

A、rowcopy(即数据copy到新表)
B、正常新增的DML(指对原表执行DML)
C、binlog apply到新表(指分析binlog event后应用到新表)

首先明确一点,C
一定要在B
之后,先生成了binlog event
,才会有对新表的apply



所以更变时可能出现三种组合,如下:

1)A->B->C,原始数据先被copy到新表,原表产生binlog,再解析binlog去对新表做apply。
2)B->C->A,主库产生binlog,再应用binlog,copyrow到新表。
3)B->A->C,主库产生binlog,copyrow到新表,再应用binlog。


首先想一下gh-ost
的对表的两个核心限制:

1)必须有PK(或不存在NULL的UK),可以精准定位到某一行。
2)binlog_format=ROW,可以拿到整行数据。

有了这两个前提,gh-ost
会认为binlog events
的数据是最准的,于是讨论一致性,就简单了:

分析一下rowcopy和DML的实现

  • rowcopy): 
    INSERT
    操作,其实就对原表做SELECT
    ,对新表INSERT INTO IGNORE

  • 对于原始binlog events
    binlog apply
    时的几个改写):

对于INSERT的DML,apply时改为REPLACE INTO
对于DELETE的DML,还是DELETE
对于UPDATE的DML,更新完整一行


  • INSERT):

如果apply先,rowcopy后:  
    比如对于PK=1的,自然以binlog为准,rowcopy遇到PK=1,IGNORE了  
如果rowcopy先,apply后:  
    比如对于PK=1的,REPLACE INTO掉,依然以binlog为准,盖掉整行  
所以rowcopy和binlog events相遇,binlog为准。


  • DELETE):

1)A->B->C,这种组合始终不影响,先前rowcopy的数据在整个过程中产生了B和
C,删掉了这一行很正常。  
2)B->C->A,BC先做,数据删掉了,A再rowcopy,这个时候对原表执行
SELECT,等于没读到数据,什么都没查到,没所谓。  
3)B->A->C,B先产生DELETE语句,A再rowcopy,对原表执行SELECT,什么数
据没读到,依然没所谓。C在做apply的时候等于没做。  


  • UPDATE):

1)A->B->C,A先rowcopy,B再生成binlog更新,最后apply,因为binlog记录整行数据,
所以也更新整行数据就好,不会出现重复更新的情况。  
2)B->C->A,BC先做,因为A还没做,实际上执行C的时候,新表是找不到PK=1
这行数据的,更新了个空气,此时再做A,就等于把整行INSERT进来了,安全。  
3)B->A->C,B先产生一条UPDATE,A做rowcopy的时候,INSERT的是最新的数
据,最后C做apply的时候,以binlog为准,因为还是更新整行数据,所以依然也不会出现重复更新的情况。  


有没有一点像Redis
AOF
,无论中间出现什么,重写时就以最后一条为准。 唯一的KEY
对应唯一的VALUE
,无论整个过程中发生了些什么。


btw隔壁pt-osc
也是有类似的SQL
改写逻辑。 
所以可以一边copy
原始数据到新表,一边用触发器捕获对原表的变更,并写到新表。


再康康cut-over

cut-over
简单理解为就是最后原表和新表做交换的整个过程。或者叫切换。

简单来说,gh-ost
用两个连接(以下用A和B替代)做了一些事,按时间顺序如下:

A)、CREATE TABLE tbl_old (id int primary keyCOMMENT='magic-be-here'
然后
LOCK TABLES tbl WRITE, tbl_old WRITE

B)、RENAME TABLE tbl TO tbl_old, ghost TO tbl。
这个时候B会话会hang着。

A)、检查B的被blocked的RENAME(通过SHOW PROCESSLIST),
继续做DROP TABLE tbl_old,UNLOCK TABLES;,

整个过程,依然是有很多其他会话尝试进行DML操作的。


那么这套流程是如何保证原子性的呢?

看看任何一步失败的影响:

1)如果A做CREATE tbl_old时挂,没所谓,不影响业务。
2)如果A做LOCK TABLE tbl/tbl_old时挂了,不影响业务。
3)如果A在CREATELOCK都成功后,连接断开,不影响业务:
①,此时B还没执行:A断开,写锁释放,准备做RENAME时,会报表tbl_old存在。
②,此时B等待执行(被hang住):A断开,写锁释放,终于可以RENAME了,
还是会报表tbl_old存在。
-- 所以,tbl_old这个空表的作用就出来了。
4)如果B在RENAME前(也就是hang住时)挂了,gh-ost会捕获这个错误,
并且继续按原计划执行DROPUNLOCK,这样也不影响业务
5)如果B在A做DROP TABLE时挂了,A一样会按原计划执行DROPUNLOCK,依旧不影响业务。

总之,上面的任何一步出现问题,gh-ost
都会检查下新表在不在,如果不在了,说明已经成功了。如果还在,那就说明最后的cut-over
过程失败了。


对了,如果LOCK TABLE
时加不上,也可以控制加锁等待时长的,默认应该是3s
,参考(由cut-over-lock-timeout-seconds
控制),重试次数由default-retries
控制,默认60
次。

整个过程可以大概如下:

  • 会话A
    tbl_old
    表和对原表、tbl_old
    加锁,当然也有可能加不上锁,因为可能之前的会话还未提交等

  • 对该表的新读写请求被阻塞

  • 会话B
    执行RENAME
    ,被阻塞

  • 对该表的新增的读写请求继续被阻塞

  • 会话A
    检查是否有被阻塞的RENAME

  • 会话A
    删除tbl_old
    和释放写锁

  • 会话B RENAME
    成功

  • 所有被阻塞的读写请求于新表


我是个憨憨,看到这个过程想吐槽,锁表操作是不是多此一举
该项目issues
也有老哥问了这个问题:

Shawn001 commented on 25 Apr

hello
why can't use rename t1 to t1_del, t1_gho to t1 directly,
are there some problems?

其实是为了避免产生新的binlog event
,如果不锁,其他会话可能会继续做DML


附录:

1)RENAME TABLE还是相对安全的,MySQL手册里也有描述:
If any errors occur during a RENAME TABLE, the statement fails and no changes are made.

2)UNLOCK TABLES后,被hang住的DML比RENAME插队执行怎么办?  
这个问题gh-ost
开发者就是一句话告诉大家:A blocked RENAME is always prioritized over a blocked INSERT/UPDATE/DELETE, no matter who came first
。大概意思就是无论DML
还是RENAME
先到,RENAME
总是第一个先跑的,这样就可以保证不会产生新的binlog events

至于原因,google了一下,理由在这里  
https://github.com/mysql/mysql-server/blob/a533e2c786164af9bd276660b972d93649434297/sql/mdl.cc#L2312

cut-over参考如下

https://github.com/github/gh-ost/issues/82  
https://github.com/github/gh-ost/blob/master/doc/cut-over.md


反正对于我而言,安全,可控就很好了。至于性能,夜晚还很长,慢慢来。

我更喜欢gh-ost 



-- the end --





阅读原文查看历史推送。

文章转载自神谕的暗影长廊,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论