近日,TDSQL新敏态引擎重磅公布。**该引擎可完满解决对于敏态业务倒退过程中业务状态、业务量的不可预知性,实现PB级存储的Online DDL,能够实现大幅晋升表构造变更过程中的数据库吞吐量,有效应对业务变动;其独有的数据状态主动感知个性,使数据能依据业务负载状况实现主动迁徙,打散热点,升高分布式事务比例,取得极致的扩展性和性能。
**
与此同时,TDSQL 新敏态引擎还具备对分布式事务残缺反对的个性,撑持了下层计算引擎多主读写架构的实现,并与计算引擎联合实现了计算下推、分布式事务一阶段优化等多维度优化,进一步实现分布式数据库系统性能极致晋升,无效适配企业新敏态业务需要。在腾讯外部业务实际中,TDSQL新敏态引擎可撑持业务在放弃高性能且间断服务的根底上,一个月内实现高达1000次表构造在线变更。
在高频的表构造变更过程中,如何缩小对在线业务申请的影响,甚至使得用户可能以原生、不阻塞业务的形式进行,这就成为了TDSQL新敏态引擎面对的技术挑战。本期将由腾讯云数据库高级工程师赵东志,为大家深度解读TDSQL新敏态引擎OnlineDDL的原理与实现。以下是分享实录:
Instant DDL
TDSQL新敏态引擎的外围架构。SQLEngine是计算层,次要负责SQL的解析、散发,包含数据查问,将SQL转为KV,再将KV收集的后果转化为SQL能获取到的后果,最初传输到客户端等环节。其中,DDL也是计算层负责的局部之一。
过来在单机零碎下DDL的执行形式分为两种:MySQL对反对的局部Online DDL,不反对的局部则通过内部组件pt工具对每个DB节点做DDL。在集群规模比拟大时,运维会变得更加简单,须要用内部工具保障多个节点之间DDL的原子性,每个节点还须要预留两倍存储空间。
基于上述起因,TDSQL新敏态引擎的设计指标定为三个方面:
● 保障Online性质,做到不阻塞业务读写申请。
● 保障多节点缓存一致性,使得Crash-safe等在TDSQL零碎中自治。
● 兼容MySQL,不便业务迁徙。
咱们以加列为例来介绍Instant DDL。下图中的Client,蕴含一个表构造,是一条映射语句。在F1列的根底上,插入一个pk=10、F1=1的数据行。插入后再进行加列操作,退出F2列,加列后的表如下图TDstore所示。这时如果须要读取前述两行数据,就会遇到问题。在读取pk=11这行时,能够用最新的表构造间接解析数据行,但在读取pk=10这行时,咱们须要晓得该数据行里是否蕴含F2列。
为此咱们在表构造中引入版本号概念。比方初始列表的版本为1,做加列操作后,schema变为2,插入时再将版本2写入到value字段中。读取数据时须要先判断数据行的版本,如果数据行版本为2,就用以后的表构造解析;如果数据行版本为1,比以后版本小,确定F2列不在该版本的Scheme中后,可间接填充默认值再返回到客户端。
通过版本号概念的引入,在整个加列过程中,只须要更改元数据,即Scheme的信息并未更改数据行,加列过程变得更加疾速高效。同样的形式也可作用于Varchar扩大长度等无损数据类型的转换,Index invisible等其余DDL。但并不是所有的DDL都能够仅批改元数据,局部DDL还须要生成局部数据能力实现,比方加索引操作。因为索引的生成是从无到有的过程,因而必须要生成局部数据,无奈通过间接批改表构造来实现。
Add/Drop Index
以加索引为例,下图右边所示为TDSQL新敏态引擎索引数据库存储构造。图中有两行数据,有一个主键和一个索引。在TDStore中,每个索引都会有全局惟一的index ID,比方主键为index1,二级索引为index2。主键数据由index ID+pk组成,造成key,value为其余字段。在索引中,它的组成为索引indexID+索引信息+主键信息。
如果要进行alter table、add index操作,从无索引状态变为索引,则须要扫主键数据,组建索引的KV模式,插入index中进行扫描,再批改元数据,以实现索引的增加。如果在扫描主键、批改元数据的同时,存在并发事务如delete或insert等操作,就会产生扫描回填的索引过程与用户事务并发之间的问题。
针对DDL和用户申请的并发问题咱们能够将DML分为delete、insert、update三种来加以探讨。
对于delete,咱们能够scan任意一行数据,再按索引模式将其插回到TDstore中。假如存在一个并发,两数据行为同一行,删除操作相当于插入一个类型为delete的key。指标是在主键上删除该数据行,在索引上也删除该数据行。如果不计后果直接插入,就会遇到问题。比方删除后,又插入到该数据行后,最终的后果是,key被删除后在索引上再次出现。
为解决上述问题,咱们引入了托马斯写机制,在插入时先查看版本,看是否存在更新的写入,如果有更新的写入,则该条key就不能再被写入。这里采纳工夫戳的比拟机制。在scan时,基于TDStore提供的全局一致性读,咱们在读取时会获取一个工夫戳,比方1。在事务中插入时,其工夫戳也通过TDStore来获取,读取数据所用工夫戳也会带进去,即在该工夫戳读,写时也用同一时间戳,TS为1。在同一条key中,如果发现存在比本人更大的ts,阐明该key已被用户更改过,则put不失效,以此来解决并发问题。
对于insert,如果插入一条新数据,与以后数据行无抵触,即以后数据行无该条数据,这时只须要在索引上也插入该行数据即可。update相当于delete+insert的组合,在delete和insert问题解决后,update问题也会天然解决。咱们通过托马斯写规定机制解决回填索引与用户事务的并发问题。
在分布式系统中咱们还会面临另一个问题,即多个计算节点之间的缓存一致性问题。因为在TDSQL中,下层计算节点能够有很多个,且每个计算节点还会有本身的缓存。以索引为例,假如某个DDL在SQLEngine1上执行一个add index idx_f1,此时SQLEngine1上并发的执行一个插入操作,则会在主键,索引上别离插入一行kv,如果这时另一个计算节点SQLEngine2因为缓存更新不及时,获取到的表构造没有idx_f1,如果接到删除申请,在解析完该表构造后,该计算节点只会删除主键上的数据,而不会删除该条索引记录,最终导致主键上和索引上的数据不统一。
单机零碎个别不会呈现上述问题。假如将两个节点设想成两个线程,比方thread1、thread2,线程1想要进行表的原数据批改,能够获取一个的元数据锁,将所有的申请先挡住,再到内存中的表构造。能够看出单机零碎依附mutex能够实现多线程互斥,不存在两个线程应用不同版本的t1的状况。
一个简略的想法是将单机零碎中的锁扩大成分布式锁。这种做法在原理上可行,但会存在时耗不可控的问题。以下图为例,假如sqlengine1想发动申请锁的申请,它能够在本身节点申请,也能够在其余节点如sqlengine2、sqlengine3上申请。但因为分布式系统中网络不太可控,sqlengine数量十分多,可能会存在网络异样问题,比方sqlengine3存在网络异样,回复工夫就会比较慢。网络工夫的提早导致不可控问题。如果等到所有节点都申请胜利,再去做更改,用户申请的阻塞工夫就会被拉长。
分布式锁的实现还有很多计划,比方引入超时机制,但同样也会存在其余问题,例如超时工夫定义为多长?太长对用户业务会有影响,太短则可能存在误判。咱们进一步思考,是否不依赖分布式锁达到同样的目标。
咱们采纳GoogleF1论文中引入的过渡态的思维。前述问题呈现的起因是有的计算节点无奈感知到该索引,有的计算节点感知到该索引并去写索引,这就产生了数据不统一问题。F1的根本思维是在分布式系统中,在没有锁的状况下,无奈同时从某个状态迁徙到下一个状态,这时就能够引入中间状态。比方某个节点能够先进入到下一个状态,但该状态与上一个状态互相兼容。如图所示,假如目前为v1状态,先进入v2,但v2与v1能够兼容,相当于还有局部节点处于v1状态,两者能够并存一段时间,等所有节点都进入v2后,再进入v3,状态两两兼容,最终推动到残缺的过程。但如何保障两两之间不超过两个状态也成为了一个新的问题?假如有个节点1先进入到v2,节点2在v1,过段时间后节点1想进入v3,但要如何确定是否所有节点都进入v2呢?
F1中还提到lease机制。假如sqlengine是一个执行DDL的节点,如果想进入下一个状态,就须要等2t的工夫。所有sqlengine节点,每隔一个t周期,都会看本人的scheme是否过期,如果过期就会从新加载,通过2t和t的穿插,保障推动时其余节点必然将新scheme退出进来。如果局部节点加载不上来出现异常,就会被动下线。但如果单纯的lease还是不牢靠的。比方在下图中比方,节点1距离2t工夫进入v2,再距离2t进入v3。假如节点2在v1时进行put key操作,但该申请在存储层面执行的工夫较久,刚好遇到了io 100%,阻塞工夫较长,比方阻塞5T的工夫才把申请写下去。这时存在一个节点,在距离2t后误以为其余节点都曾经进入新状态,因而进入到v3。这就违反前述规定,即同一时刻不能有两个相邻版本以外的写入并存。即便v2晓得本身超过lease抉择被动下线也没有用,因为写入申请曾经发到存储层,该写入的生命周期曾经由存储层来管制。对于上述问题,F1中也提到能够引入deadline工夫来管制,然而目前咱们并没有这种机制,而是采纳了一种版本断定机制来解决这个问题。
从实质上来看,这个问题属于计算层与存储层联动的问题,因为该申请曾经发到TDStore,咱们须要在推动版本前让TDStore感知到相干状况,具体流程如下:在进入下一状态前,须要先推一个版本上来。推下去后,存储层会感知到该节点想要进入v2。与此同时,存储层发现v1状态下还有一个申请未实现,等该申请写完后存储层再返回批准。如果存储层中一旦存在旧版本申请没有实现,它会等到实现后再反馈。
在这种束缚机制下,只有push版本胜利,阐明存储层里曾经没有比v2更小的写入,即此时任意节点都没有过期版本正在写入,能够进入v3状态。同时在该机制下,存储层不会承受后续申请中比v小的读写申请。在极其异样的场景中,假如某一节点在push曾经胜利的状况下,发送仍处于v1状态的申请,这时存储层就会发现该申请比以后版本的v要小,只能回绝。通过存储层的版本校验机制,进一步保障了零碎中任意时刻的无效写入只能在两个相邻的状态之间。
最初对缓存与执行进行总结。咱们采纳F1的思维引入过渡态,将Add Index分成多个阶段,每相邻的两个阶段两两兼容,这样就无需依赖全局的分布式锁。在存储层进行该版本的有效性测验,进一步保障每时每刻的无效写入只能位于两个相邻状态之间。大多数状况下,咱们能够认为该版本测验有效。因为每个节点都能加载新的表构造,且能用新的表构造进行读写,版本测验仅实用于预防阶段场景,为避免此类极其场景对数据造成一致性的毁坏,保障整体算法运行的正确性。整体过程为:由计算层间接向下推送版本,演变为先向TDStore push以后版本,再进入下一状态,通过此类形式来实现整体的变更操作。
删索引则绝对容易,能够看成加索引的反向操作,具体过程如下图。
通用Online DDL
在Instant DDL中,仅需更改表构造、批改元数据即可。在Varchar扩大长度等无损数据类型的转换中,还须要生成局部数据能力实现。要如何使得更宽泛的其余DDL通过Online形式执行,这就成了新的挑战。
为此咱们联合了pt-online-scheme-change的思维。pt的原理为:在执行OnlineDDL时,会生成一个新的表构造即长期表,再将旧表数据拷贝到新表中,过程中还会进行建触发器等操作,保障拷表过程中的增量同步。在TDSQL新敏态引擎的设计中咱们借鉴了上述拷表思维。拷表过程中的新表的过程能够设想成在原表上加一个非凡的索引,即回归到托马斯写问题,针对拷表过程中的问题咱们也设计了过渡态问题的解决方案。
旧表为status0,建设一张长期表为tmp1,状态为delete only。咱们会在外部建设一张新表,将旧表与新表进行关联,并且会将表status0上的删除相干的操作同步长期表tmp1,接下来进入write only状态。write only的过程与加索引过程雷同,会在执行过程中将delete、update、insert等新的增量同步到tmp1上。
筹备开始thoma write回填数据之前,须要在存储层推版本,确保以后没有处于delete only状态的节点,保障任何新的申请都会增量同步到新的长期表中。之后再进行thomas write操作依照加索引的形式,从MC获取工夫戳,再用工夫戳扫数据,从老表上将旧数据回迁到新表,thomas write机制能够保障整体回迁过程与原表事务并发的正确性,最初再进行长期表命名。
在此之前,咱们还会进行其余的查看操作,比方查看旧表与新表数据的一致性。因为在这种拷表形式中,如果alter影响到主键,就容易引起数据方面的问题。假如原表的主键为一个Varchar,属于大小写敏感类型,下面有A和a两条数据。如果变更字符序,将其变为大小写不敏感,在新表中A和a就会变成一条数据,从而笼罩掉原始数据。咱们须要通过相似的二次查看来确定是否存在该种状况,防止拷贝过程中的数据遗失。
查看实现后,咱们会进行rename操作,更改旧表表名,再将新表替换成原表表名,相当于将整个原表替换到新表的状态。咱们还会进行反向同步操作,因为可能有局部节点仍处于status2,此时原表上还有读申请,咱们须要将这些申请转发到这张表上,保障处于该状态的计算节点仍能读到这些新增的数据申请。在这些申请转移实现后,再勾销关联,将版本推掉,最终将旧表用异步形式进行清理。
联合pt-online-scheme-change的思维,咱们将拷表的过程设想成增加一个非凡的索引,从而进一步推广到反对MySQL所有类型的DDL。
Online DDL原子性
在TDSQL新敏态引擎中,所有计算节点为无状态,长久化操作通过存储层来实现,DDL的发动操作则在计算节点中进行。如果某一计算节点在执行DDL过程中挂掉,就会面临中间状态由谁来负责推动的问题。
实际操作中,每个计算节点在执行前,会在存储层长久化一个DDL工作队列。每次发展DDL工作时,就会将该DDL工作插入到DDL工作队列中。如果失常完结,就会将该工作删除。如果非正常完结如异步挂掉,其余的计算节点,会感知到工作队列中有未实现的工作,依据该工作以后执行信息,再去界定该DDL工作的下一步操作,例如持续推动或回滚。
在上述过程中,复原线程与工作线程之间通过MC的lock来互斥。这看似引入了分布式锁,但实际上该锁只作用于DDL之间。因为TDSQL新敏态引擎的整体设计准则是DML优先,在DDL过程中尽量避免影响DML。
总结
综上所述,TDSQL新敏态引擎Online DDL核心技术能够总结为四个方面:
● Instant DDL:通过多版本的解析规定,使得加列或varchar扩大长度等无损类型变更这些只需批改元数据的DDL霎时实现。
● Add/Drop Index:通过托马斯写机制,解决生成索引数据和用户事务的并发问题;采纳F1过渡态+存储层版本交验机制,解决多个节点间缓存一致性问题。
● 通用Online DDL:形象出实用于所有DDL的copy table流程,进一步将Online DDL推广到可反对绝大多数MySQL的DDL。
● DDL原子性:通过工作队列+复原线程的工作机制,保障DDL整体的原子性。