近日,TDSQL新敏態(tài)引擎重磅發(fā)布。該引擎可完美解決對(duì)于敏態(tài)業(yè)務(wù)發(fā)展過(guò)程中業(yè)務(wù)形態(tài)、業(yè)務(wù)量的不可預(yù)知性,實(shí)現(xiàn)PB級(jí)存儲(chǔ)的Online DDL,可以實(shí)現(xiàn)大幅提升表結(jié)構(gòu)變更過(guò)程中的數(shù)據(jù)庫(kù)吞吐量,有效應(yīng)對(duì)業(yè)務(wù)變化;其獨(dú)有的數(shù)據(jù)形態(tài)自動(dòng)感知特性,使數(shù)據(jù)能根據(jù)業(yè)務(wù)負(fù)載情況實(shí)現(xiàn)自動(dòng)遷移,打散熱點(diǎn),降低分布式事務(wù)比例,獲得極致的擴(kuò)展性和性能。
與此同時(shí),TDSQL新敏態(tài)引擎還具有對(duì)分布式事務(wù)完整支持的特性,支撐了上層計(jì)算引擎多主讀寫(xiě)架構(gòu)的實(shí)現(xiàn),并與計(jì)算引擎結(jié)合實(shí)現(xiàn)了計(jì)算下推、分布式事務(wù)一階段優(yōu)化等多維度優(yōu)化,進(jìn)一步實(shí)現(xiàn)分布式數(shù)據(jù)庫(kù)系統(tǒng)性能極致提升,有效適配企業(yè)新敏態(tài)業(yè)務(wù)需求。在騰訊內(nèi)部業(yè)務(wù)實(shí)踐中,TDSQL新敏態(tài)引擎可支撐業(yè)務(wù)在保持高性能且連續(xù)服務(wù)的基礎(chǔ)上,一個(gè)月內(nèi)完成高達(dá)1000次表結(jié)構(gòu)在線變更。
在高頻的表結(jié)構(gòu)變更過(guò)程中,如何減少對(duì)在線業(yè)務(wù)請(qǐng)求的影響,甚至使得用戶能夠以原生、不阻塞業(yè)務(wù)的方式進(jìn)行,這就成為了TDSQL新敏態(tài)引擎面對(duì)的技術(shù)挑戰(zhàn)。本期將由騰訊云數(shù)據(jù)庫(kù)高級(jí)工程師趙東志,為大家深度解讀TDSQL新敏態(tài)引擎OnlineDDL的原理與實(shí)現(xiàn)。以下是分享實(shí)錄:
Instant DDL
下圖所示為T(mén)DSQL新敏態(tài)引擎的核心架構(gòu)。SQLEngine是計(jì)算層,主要負(fù)責(zé)SQL的解析、分發(fā),包括數(shù)據(jù)查詢,將SQL轉(zhuǎn)為KV,再將KV收集的結(jié)果轉(zhuǎn)化為SQL能獲取到的結(jié)果,最后傳輸?shù)娇蛻舳说拳h(huán)節(jié)。其中,DDL也是計(jì)算層負(fù)責(zé)的部分之一。
過(guò)去在單機(jī)系統(tǒng)下DDL的執(zhí)行方式分為兩種:MySQL對(duì)支持的部分Online DDL,不支持的部分則通過(guò)外部組件pt工具對(duì)每個(gè)DB節(jié)點(diǎn)做DDL。在集群規(guī)模比較大時(shí),運(yùn)維會(huì)變得更加復(fù)雜,需要用外部工具保證多個(gè)節(jié)點(diǎn)之間DDL的原子性,每個(gè)節(jié)點(diǎn)還需要預(yù)留兩倍存儲(chǔ)空間。
基于上述原因,TDSQL新敏態(tài)引擎的設(shè)計(jì)目標(biāo)定為三個(gè)方面:
·保證Online性質(zhì),做到不阻塞業(yè)務(wù)讀寫(xiě)請(qǐng)求。
·保證多節(jié)點(diǎn)緩存一致性,使得Crash-safe等在TDSQL系統(tǒng)中自治。
·兼容MySQL,方便業(yè)務(wù)遷移。
我們以加列為例來(lái)介紹Instant DDL。下圖中的Client,包含一個(gè)表結(jié)構(gòu),是一條映射語(yǔ)句。在F1列的基礎(chǔ)上,插入一個(gè)pk=10、F1=1的數(shù)據(jù)行。插入后再進(jìn)行加列操作,加入F2列,加列后的表如下圖TDstore所示。這時(shí)如果需要讀取前述兩行數(shù)據(jù),就會(huì)遇到問(wèn)題。在讀取pk=11這行時(shí),可以用最新的表結(jié)構(gòu)直接解析數(shù)據(jù)行,但在讀取pk=10這行時(shí),我們需要知道該數(shù)據(jù)行里是否包含F(xiàn)2列。
為此我們?cè)诒斫Y(jié)構(gòu)中引入版本號(hào)概念。比如初始列表的版本為1,做加列操作后,schema變?yōu)?,插入時(shí)再將版本2寫(xiě)入到value字段中。讀取數(shù)據(jù)時(shí)需要先判斷數(shù)據(jù)行的版本,如果數(shù)據(jù)行版本為2,就用當(dāng)前的表結(jié)構(gòu)解析;如果數(shù)據(jù)行版本為1,比當(dāng)前版本小,確定F2列不在該版本的schema中后,可直接填充默認(rèn)值再返回到客戶端。
通過(guò)版本號(hào)概念的引入,在整個(gè)加列過(guò)程中,只需要更改元數(shù)據(jù),即schema的信息并未更改數(shù)據(jù)行,加列過(guò)程變得更加快速高效。同樣的方式也可作用于Varchar擴(kuò)展長(zhǎng)度等無(wú)損數(shù)據(jù)類型的轉(zhuǎn)換,Index invisible等其他DDL。但并不是所有的DDL都可以僅修改元數(shù)據(jù),部分DDL還需要生成部分?jǐn)?shù)據(jù)才能實(shí)現(xiàn),比如加索引操作。因?yàn)樗饕纳墒菑臒o(wú)到有的過(guò)程,因此必須要生成部分?jǐn)?shù)據(jù),無(wú)法通過(guò)直接修改表結(jié)構(gòu)來(lái)實(shí)現(xiàn)。
Add/Drop Index
以加索引為例,下圖左邊所示為T(mén)DSQL新敏態(tài)引擎索引數(shù)據(jù)庫(kù)存儲(chǔ)結(jié)構(gòu)。圖中有兩行數(shù)據(jù),有一個(gè)主鍵和一個(gè)索引。在TDStore中,每個(gè)索引都會(huì)有全局唯一的index ID,比如主鍵為index1,二級(jí)索引為index2。主鍵數(shù)據(jù)由index ID+pk組成,形成key,value為其他字段。在索引中,它的組成為索引indexID+索引信息+主鍵信息。
如果要進(jìn)行alter table、add index操作,從無(wú)索引狀態(tài)變?yōu)樗饕?,則需要掃主鍵數(shù)據(jù),組建索引的KV形式,插入index中進(jìn)行掃描,再修改元數(shù)據(jù),以完成索引的添加。如果在掃描主鍵、修改元數(shù)據(jù)的同時(shí),存在并發(fā)事務(wù)如delete或insert等操作,就會(huì)產(chǎn)生掃描回填的索引過(guò)程與用戶事務(wù)并發(fā)之間的問(wèn)題。
針對(duì)DDL和用戶請(qǐng)求的并發(fā)問(wèn)題我們可以將DML分為delete、insert、update三種來(lái)加以討論。
對(duì)于delete,我們可以scan任意一行數(shù)據(jù),再按索引形式將其插回到TDstore中。假設(shè)存在一個(gè)并發(fā),兩數(shù)據(jù)行為同一行,刪除操作相當(dāng)于插入一個(gè)類型為delete的key。目標(biāo)是在主鍵上刪除該數(shù)據(jù)行,在索引上也刪除該數(shù)據(jù)行。如果不計(jì)后果直接插入,就會(huì)遇到問(wèn)題。比如刪除后,又插入到該數(shù)據(jù)行后,最終的結(jié)果是,key被刪除后在索引上再次出現(xiàn)。
為解決上述問(wèn)題,我們引入了托馬斯寫(xiě)機(jī)制,在插入時(shí)先查看版本,看是否存在更加新的寫(xiě)入,如果有更加新的寫(xiě)入,則該條key就不能再被寫(xiě)入。這里采用時(shí)間戳的比較機(jī)制。在scan時(shí),基于TDStore提供的全局一致性讀,我們?cè)谧x取時(shí)會(huì)獲取一個(gè)時(shí)間戳,比如1。在事務(wù)中插入時(shí),其時(shí)間戳也通過(guò)TDStore來(lái)獲取,讀取數(shù)據(jù)所用時(shí)間戳也會(huì)帶進(jìn)去,即在該時(shí)間戳讀,寫(xiě)時(shí)也用同一時(shí)間戳,TS為1。在同一條key中,如果發(fā)現(xiàn)存在比自己更大的ts,說(shuō)明該key已被用戶更改過(guò),則put不生效,以此來(lái)解決并發(fā)問(wèn)題。
對(duì)于insert,如果插入一條新數(shù)據(jù),與當(dāng)前數(shù)據(jù)行無(wú)沖突,即當(dāng)前數(shù)據(jù)行無(wú)該條數(shù)據(jù),這時(shí)只需要在索引上也插入該行數(shù)據(jù)即可。update相當(dāng)于delete+insert的組合,在delete和insert問(wèn)題解決后,update問(wèn)題也會(huì)自然解決。我們通過(guò)托馬斯寫(xiě)規(guī)則機(jī)制解決回填索引與用戶事務(wù)的并發(fā)問(wèn)題。
在分布式系統(tǒng)中我們還會(huì)面臨另一個(gè)問(wèn)題,即多個(gè)計(jì)算節(jié)點(diǎn)之間的緩存一致性問(wèn)題。因?yàn)樵赥DSQL中,上層計(jì)算節(jié)點(diǎn)可以有很多個(gè),且每個(gè)計(jì)算節(jié)點(diǎn)還會(huì)有自身的緩存。以索引為例,假設(shè)某個(gè)DDL在SQLEngine1上執(zhí)行一個(gè)add index idx_f1,此時(shí)SQLEngine1上并發(fā)的執(zhí)行一個(gè)插入操作,則會(huì)在主鍵,索引上分別插入一行kv,如果這時(shí)另一個(gè)計(jì)算節(jié)點(diǎn)SQLEngine2由于緩存更新不及時(shí),獲取到的表結(jié)構(gòu)沒(méi)有idx_f1,如果接到刪除請(qǐng)求,在解析完該表結(jié)構(gòu)后,該計(jì)算節(jié)點(diǎn)只會(huì)刪除主鍵上的數(shù)據(jù),而不會(huì)刪除該條索引記錄,最終導(dǎo)致主鍵上和索引上的數(shù)據(jù)不一致。
單機(jī)系統(tǒng)一般不會(huì)出現(xiàn)上述問(wèn)題。假設(shè)將兩個(gè)節(jié)點(diǎn)想象成兩個(gè)線程,比如thread1、thread2,線程1想要進(jìn)行表的元數(shù)據(jù)修改,可以獲取一個(gè)的元數(shù)據(jù)鎖,將所有的請(qǐng)求先擋住,再到內(nèi)存中的表結(jié)構(gòu)??梢钥闯鰡螜C(jī)系統(tǒng)依靠mutex可以實(shí)現(xiàn)多線程互斥,不存在兩個(gè)線程使用不同版本的t1的情況。
一個(gè)簡(jiǎn)單的想法是將單機(jī)系統(tǒng)中的鎖擴(kuò)展成分布式鎖。這種做法在原理上可行,但會(huì)存在時(shí)耗不可控的問(wèn)題。以下圖為例,假設(shè)sqlengine1想發(fā)起申請(qǐng)鎖的請(qǐng)求,它可以在自身節(jié)點(diǎn)申請(qǐng),也可以在其他節(jié)點(diǎn)如sqlengine2、sqlengine3上申請(qǐng)。但由于分布式系統(tǒng)中網(wǎng)絡(luò)不太可控,sqlengine數(shù)量非常多,可能會(huì)存在網(wǎng)絡(luò)異常問(wèn)題,比如sqlengine3存在網(wǎng)絡(luò)異常,回復(fù)時(shí)間就會(huì)比較慢。網(wǎng)絡(luò)時(shí)間的延遲導(dǎo)致不可控問(wèn)題。如果等到所有節(jié)點(diǎn)都申請(qǐng)成功,再去做更改,用戶請(qǐng)求的阻塞時(shí)間就會(huì)被拉長(zhǎng)。
分布式鎖的實(shí)現(xiàn)還有很多方案,比如引入超時(shí)機(jī)制,但同樣也會(huì)存在其他問(wèn)題,例如超時(shí)時(shí)間定義為多長(zhǎng)?太長(zhǎng)對(duì)用戶業(yè)務(wù)會(huì)有影響,太短則可能存在誤判。我們進(jìn)一步思考,能否不依賴分布式鎖達(dá)到同樣的目的。
我們采用GoogleF1論文中引入的過(guò)渡態(tài)的思想。前述問(wèn)題出現(xiàn)的原因是有的計(jì)算節(jié)點(diǎn)無(wú)法感知到該索引,有的計(jì)算節(jié)點(diǎn)感知到該索引并去寫(xiě)索引,這就產(chǎn)生了數(shù)據(jù)不一致問(wèn)題。F1的基本思想是在分布式系統(tǒng)中,在沒(méi)有鎖的情況下,無(wú)法同時(shí)從某個(gè)狀態(tài)遷移到下一個(gè)狀態(tài),這時(shí)就可以引入中間狀態(tài)。比如某個(gè)節(jié)點(diǎn)可以先進(jìn)入到下一個(gè)狀態(tài),但該狀態(tài)與上一個(gè)狀態(tài)相互兼容。如圖所示,假設(shè)目前為v1狀態(tài),先進(jìn)入v2,但v2與v1可以兼容,相當(dāng)于還有部分節(jié)點(diǎn)處于v1狀態(tài),兩者可以并存一段時(shí)間,等所有節(jié)點(diǎn)都進(jìn)入v2后,再進(jìn)入v3,狀態(tài)兩兩兼容,最終推進(jìn)到完整的過(guò)程。但如何保證兩兩之間不超過(guò)兩個(gè)狀態(tài)也成為了一個(gè)新的問(wèn)題?假設(shè)有個(gè)節(jié)點(diǎn)1先進(jìn)入到v2,節(jié)點(diǎn)2在v1,過(guò)段時(shí)間后節(jié)點(diǎn)1想進(jìn)入v3,但要如何確定是否所有節(jié)點(diǎn)都進(jìn)入v2呢?
F1中還提到lease機(jī)制。假設(shè)sqlengine是一個(gè)執(zhí)行DDL的節(jié)點(diǎn),如果想進(jìn)入下一個(gè)狀態(tài),就需要等2t的時(shí)間。所有sqlengine節(jié)點(diǎn),每隔一個(gè)t周期,都會(huì)看自己的schema是否過(guò)期,如果過(guò)期就會(huì)重新加載,通過(guò)2t和t的交叉,保證推進(jìn)時(shí)其他節(jié)點(diǎn)必定將新schema加入進(jìn)來(lái)。如果部分節(jié)點(diǎn)加載不上來(lái)出現(xiàn)異常,就會(huì)主動(dòng)下線。但如果單純的lease還是不可靠的。
比如在下圖中,節(jié)點(diǎn)1間隔2t時(shí)間進(jìn)入v2,再間隔2t進(jìn)入v3。假設(shè)節(jié)點(diǎn)2在v1時(shí)進(jìn)行put key操作,但該請(qǐng)求在存儲(chǔ)層面執(zhí)行的時(shí)間較久,剛好遇到了io 100%,阻塞時(shí)間較長(zhǎng),比如阻塞5T的時(shí)間才把請(qǐng)求寫(xiě)下去。這時(shí)存在一個(gè)節(jié)點(diǎn),在間隔2t后誤以為其他節(jié)點(diǎn)都已經(jīng)進(jìn)入新?tīng)顟B(tài),因此進(jìn)入到v3。這就違反前述規(guī)則,即同一時(shí)刻不能有兩個(gè)相鄰版本以外的寫(xiě)入并存。即使v2知道自身超過(guò)lease選擇主動(dòng)下線也沒(méi)有用,因?yàn)閷?xiě)入請(qǐng)求已經(jīng)發(fā)到存儲(chǔ)層,該寫(xiě)入的生命周期已經(jīng)由存儲(chǔ)層來(lái)控制。對(duì)于上述問(wèn)題,F(xiàn)1中也提到可以引入deadline時(shí)間來(lái)控制,但是目前我們并沒(méi)有這種機(jī)制,而是采用了一種版本判定機(jī)制來(lái)解決這個(gè)問(wèn)題。
從本質(zhì)上來(lái)看,這個(gè)問(wèn)題屬于計(jì)算層與存儲(chǔ)層聯(lián)動(dòng)的問(wèn)題,因?yàn)樵撜?qǐng)求已經(jīng)發(fā)到TDStore,我們需要在推進(jìn)版本前讓TDStore感知到相關(guān)情況,具體流程如下:在進(jìn)入下一狀態(tài)前,需要先推一個(gè)版本下去。推下去后,存儲(chǔ)層會(huì)感知到該節(jié)點(diǎn)想要進(jìn)入v2。與此同時(shí),存儲(chǔ)層發(fā)現(xiàn)v1狀態(tài)下還有一個(gè)請(qǐng)求未完成,等該請(qǐng)求寫(xiě)完后存儲(chǔ)層再返回同意。如果存儲(chǔ)層中一旦存在舊版本請(qǐng)求沒(méi)有完成,它會(huì)等到完成后再反饋。
在這種約束機(jī)制下,只要push版本成功,說(shuō)明存儲(chǔ)層里已經(jīng)沒(méi)有比v2更小的寫(xiě)入,即此時(shí)任意節(jié)點(diǎn)都沒(méi)有過(guò)期版本正在寫(xiě)入,可以進(jìn)入v3狀態(tài)。同時(shí)在該機(jī)制下,存儲(chǔ)層不會(huì)接受后續(xù)請(qǐng)求中比v2小的讀寫(xiě)請(qǐng)求。在極端異常的場(chǎng)景中,假設(shè)某一節(jié)點(diǎn)在push已經(jīng)成功的情況下,發(fā)送仍處于v1狀態(tài)的請(qǐng)求,這時(shí)存儲(chǔ)層就會(huì)發(fā)現(xiàn)該請(qǐng)求比當(dāng)前版本的v2要小,只能拒絕。通過(guò)存儲(chǔ)層的版本校驗(yàn)機(jī)制,進(jìn)一步保證了系統(tǒng)中任意時(shí)刻的有效寫(xiě)入只能在兩個(gè)相鄰的狀態(tài)之間。
最后對(duì)緩存與執(zhí)行進(jìn)行總結(jié)。我們采用F1的思想引入過(guò)渡態(tài),將Add Index分成多個(gè)階段,每相鄰的兩個(gè)階段兩兩兼容,這樣就無(wú)需依賴全局的分布式鎖。在存儲(chǔ)層進(jìn)行該版本的有效性檢驗(yàn),進(jìn)一步保證每時(shí)每刻的有效寫(xiě)入只能位于兩個(gè)相鄰狀態(tài)之間。大多數(shù)情況下,我們可以認(rèn)為該版本檢驗(yàn)無(wú)效。因?yàn)槊總€(gè)節(jié)點(diǎn)都能加載新的表結(jié)構(gòu),且能用新的表結(jié)構(gòu)進(jìn)行讀寫(xiě),版本檢驗(yàn)僅適用于預(yù)防階段場(chǎng)景,為防止此類極端場(chǎng)景對(duì)數(shù)據(jù)造成一致性的破壞,保證整體算法運(yùn)行的正確性。整體過(guò)程為:由計(jì)算層直接向下推送版本,演變?yōu)橄认騎DStore push當(dāng)前版本,再進(jìn)入下一狀態(tài),通過(guò)此類方式來(lái)完成整體的變更操作。
刪索引則相對(duì)容易,可以看成加索引的反向操作,具體過(guò)程如下圖。
通用Online DDL
在Instant DDL中,僅需更改表結(jié)構(gòu)、修改元數(shù)據(jù)即可。在Varchar擴(kuò)展長(zhǎng)度等無(wú)損數(shù)據(jù)類型的轉(zhuǎn)換中,還需要生成部分?jǐn)?shù)據(jù)才能實(shí)現(xiàn)。要如何使得更廣泛的其他DDL通過(guò)Online方式執(zhí)行,這就成了新的挑戰(zhàn)。
為此我們結(jié)合了pt-online-schema-change的思想。pt的原理為:在執(zhí)行OnlineDDL時(shí),會(huì)生成一個(gè)新的表結(jié)構(gòu)即臨時(shí)表,再將舊表數(shù)據(jù)拷貝到新表中,過(guò)程中還會(huì)進(jìn)行建觸發(fā)器等操作,保證拷表過(guò)程中的增量同步。在TDSQL新敏態(tài)引擎的設(shè)計(jì)中我們借鑒了上述拷表思想??奖磉^(guò)程中的新表的過(guò)程可以想象成在原表上加一個(gè)特殊的索引,即回歸到托馬斯寫(xiě)問(wèn)題,針對(duì)拷表過(guò)程中的問(wèn)題我們也設(shè)計(jì)了過(guò)渡態(tài)問(wèn)題的解決方案。
以上圖為例,圖中的舊表為status0,建立一張臨時(shí)表為tmp1,狀態(tài)為delete only。我們會(huì)在內(nèi)部建立一張新表,將舊表與新表進(jìn)行關(guān)聯(lián),并且會(huì)將表status0上的刪除相關(guān)的操作同步臨時(shí)表tmp1,接下來(lái)進(jìn)入write only狀態(tài)。write only的過(guò)程與加索引過(guò)程相同,會(huì)在執(zhí)行過(guò)程中將delete、update、insert等新的增量同步到tmp1上。
準(zhǔn)備開(kāi)始thoma write回填數(shù)據(jù)之前,需要在存儲(chǔ)層推版本,確保當(dāng)前沒(méi)有處于delete only狀態(tài)的節(jié)點(diǎn),保證任何新的請(qǐng)求都會(huì)增量同步到新的臨時(shí)表中。之后再進(jìn)行thomas write操作按照加索引的方式,從MC獲取時(shí)間戳,再用時(shí)間戳掃數(shù)據(jù),從老表上將舊數(shù)據(jù)回遷到新表,thomas write機(jī)制可以保證整體回遷過(guò)程與原表事務(wù)并發(fā)的正確性,最后再進(jìn)行臨時(shí)表命名。
在此之前,我們還會(huì)進(jìn)行其他的檢查操作,比如檢查舊表與新表數(shù)據(jù)的一致性。因?yàn)樵谶@種拷表方式中,如果alter影響到主鍵,就容易引起數(shù)據(jù)方面的問(wèn)題。假設(shè)原表的主鍵為一個(gè)Varchar,屬于大小寫(xiě)敏感類型,上面有A和a兩條數(shù)據(jù)。如果變更字符序,將其變?yōu)榇笮?xiě)不敏感,在新表中A和a就會(huì)變成一條數(shù)據(jù),從而覆蓋掉原始數(shù)據(jù)。我們需要通過(guò)類似的二次檢查來(lái)確定是否存在該種情況,避免拷貝過(guò)程中的數(shù)據(jù)遺失。
檢查完成后,我們會(huì)進(jìn)行rename操作,更改舊表表名,再將新表替換成原表表名,相當(dāng)于將整個(gè)原表替換到新表的狀態(tài)。我們還會(huì)進(jìn)行反向同步操作,因?yàn)榭赡苡胁糠止?jié)點(diǎn)仍處于status2,此時(shí)原表上還有讀請(qǐng)求,我們需要將這些請(qǐng)求轉(zhuǎn)發(fā)到這張表上,保證處于該狀態(tài)的計(jì)算節(jié)點(diǎn)仍能讀到這些新增的數(shù)據(jù)請(qǐng)求。在這些請(qǐng)求轉(zhuǎn)移完成后,再取消關(guān)聯(lián),將版本推掉,最終將舊表用異步方式進(jìn)行清理。
結(jié)合pt-online-schema-change的思想,我們將拷表的過(guò)程想象成添加一個(gè)特殊的索引,從而進(jìn)一步推廣到支持MySQL所有類型的DDL。
Online DDL原子性
在TDSQL新敏態(tài)引擎中,所有計(jì)算節(jié)點(diǎn)為無(wú)狀態(tài),持久化操作通過(guò)存儲(chǔ)層來(lái)實(shí)現(xiàn),DDL的發(fā)起操作則在計(jì)算節(jié)點(diǎn)中進(jìn)行。如果某一計(jì)算節(jié)點(diǎn)在執(zhí)行DDL過(guò)程中掛掉,就會(huì)面臨中間狀態(tài)由誰(shuí)來(lái)負(fù)責(zé)推進(jìn)的問(wèn)題。
實(shí)際操作中,每個(gè)計(jì)算節(jié)點(diǎn)在執(zhí)行前,會(huì)在存儲(chǔ)層持久化一個(gè)DDL任務(wù)隊(duì)列。每次開(kāi)展DDL任務(wù)時(shí),就會(huì)將該DDL任務(wù)插入到DDL任務(wù)隊(duì)列中。如果正常結(jié)束,就會(huì)將該任務(wù)刪除。如果非正常結(jié)束如異步掛掉,其他的計(jì)算節(jié)點(diǎn),會(huì)感知到任務(wù)隊(duì)列中有未完成的任務(wù),根據(jù)該任務(wù)當(dāng)前執(zhí)行信息,再去界定該DDL任務(wù)的下一步操作,例如繼續(xù)推進(jìn)或回滾。
在上述過(guò)程中,恢復(fù)線程與工作線程之間通過(guò)MC的lock來(lái)互斥。這看似引入了分布式鎖,但實(shí)際上該鎖只作用于DDL之間。因?yàn)門(mén)DSQL新敏態(tài)引擎的整體設(shè)計(jì)原則是DML優(yōu)先,在DDL過(guò)程中盡量避免影響DML。
總結(jié)
綜上所述,TDSQL新敏態(tài)引擎Online DDL核心技術(shù)可以總結(jié)為四個(gè)方面:
·Instant DDL:通過(guò)多版本的解析規(guī)則,使得加列或varchar擴(kuò)展長(zhǎng)度等無(wú)損類型變更這些只需修改元數(shù)據(jù)的DDL瞬間完成。
·Add/Drop Index:通過(guò)托馬斯寫(xiě)機(jī)制,解決生成索引數(shù)據(jù)和用戶事務(wù)的并發(fā)問(wèn)題;采用F1過(guò)渡態(tài)+存儲(chǔ)層版本交驗(yàn)機(jī)制,解決多個(gè)節(jié)點(diǎn)間緩存一致性問(wèn)題。
·通用Online DDL:抽象出適用于所有DDL的copy table流程,進(jìn)一步將Online DDL推廣到可支持絕大多數(shù)MySQL的DDL。
·DDL原子性:通過(guò)任務(wù)隊(duì)列+恢復(fù)線程的工作機(jī)制,保證DDL整體的原子性。