HybridDB for MySQL(原名petadata)是面向在線事務(wù)(OLTP)和在線分析(OLAP)混合場景的關(guān)系型數(shù)據(jù)庫。HybridDB采用一份數(shù)據(jù)存儲來進(jìn)行OLTP和OLAP處理,解決了以往需要把一份數(shù)據(jù)多次復(fù)制來分別進(jìn)行業(yè)務(wù)交易和數(shù)據(jù)分析的問題脐往,極大地降低了數(shù)據(jù)存儲的成本,縮短了數(shù)據(jù)分析的延遲,使得實(shí)時(shí)分析決策稱為可能漆弄。
HybridDB for MySQL兼容MySQL的語法及函數(shù),并且增加了對Oracle常用分析函數(shù)的支持造锅,100%完全兼容TPC-H和TPC-DS測試標(biāo)準(zhǔn)撼唾,從而降低了用戶的開發(fā)、遷移和維護(hù)成本哥蔚。
TokuDB是TokuTek公司(已被 Percona收購)研發(fā)的新引擎倒谷,支持事務(wù)/MVCC,有著出色的數(shù)據(jù)壓縮功能糙箍,支持異步寫入數(shù)據(jù)功能渤愁。
TokuDB索引結(jié)構(gòu)采用fractal tree數(shù)據(jù)結(jié)構(gòu),是buffer tree的變種深夯,寫入性能優(yōu)異抖格,適合寫多讀少的場景诺苹。除此之外,TokuDB還支持在線加減字段雹拄,在線創(chuàng)建索引收奔,鎖表時(shí)間很短。
Percona Server和Mariadb支持TokuDB作為大數(shù)據(jù)場景下的引擎滓玖,目前官方MySQL還不支持TokuDB坪哄。ApsaraDB for MySQL從2015年4月開始支持TokuDB,在大數(shù)據(jù)或者高并發(fā)寫入場景下推薦使用势篡。
TokuDB優(yōu)勢
數(shù)據(jù)壓縮
TokuDB最顯著的優(yōu)勢就是數(shù)據(jù)壓縮翩肌,支持多種壓縮算法,用戶可按照實(shí)際的資源消耗修改壓縮算法殊霞,生產(chǎn)環(huán)境下推薦使用zstd摧阅,實(shí)測的壓縮比是4:1。
目前HybridDB for MySQL支持6中壓縮算法:
- lzma: 壓縮比最高绷蹲,資源消耗高
- zlib:Percona默認(rèn)壓縮算法棒卷,最流行,壓縮比和資源消耗適中
- quicklz:速度快祝钢,壓縮比最低
- snappy:google研發(fā)的比规,壓縮比較低,速度快
- zstd:壓縮比接近zlib拦英,速度快
- uncompressed:不壓縮蜒什,速度最快
Percona建議6核以下場景使用默認(rèn)壓縮算法zlib,6核以上可以使用壓縮率更高的壓縮算法疤估,大數(shù)據(jù)場景下推薦使用zstd壓縮算法灾常,壓縮比高,壓縮和解壓速度快铃拇,也比較穩(wěn)定钞瀑。
用戶可以在建表時(shí)使用ROW_FORMAT子句指定壓縮算法,也可用使用ALTER TABLE修改壓縮算法慷荔。ALTER TABLE執(zhí)行后新數(shù)據(jù)使用新的壓縮算法雕什,老數(shù)據(jù)仍是老的壓縮格式。
mysql> CREATE TABLE t_test (column_a INT NOT NULL PRIMARY KEY, column_b INT NOT NULL) ENGINE=TokuDB ROW_FORMAT=tokudb_zstd;
mysql> SHOW CREATE TABLE t_test\G
Table: t_test
Create Table: CREATE TABLE `t_test` (
`column_a` int(11) NOT NULL,
`column_b` int(11) NOT NULL,
PRIMARY KEY (`column_a`)
) ENGINE=TokuDB DEFAULT CHARSET=latin1 ROW_FORMAT=TOKUDB_ZSTD
mysql> ALTER TABLE t_test ROW_FORMAT=tokudb_snappy;
mysql> SHOW CREATE TABLE t_test\G
Table: t_test
Create Table: CREATE TABLE `t_test` (
`column_a` int(11) NOT NULL,
`column_b` int(11) NOT NULL,
PRIMARY KEY (`column_a`)
) ENGINE=TokuDB DEFAULT CHARSET=latin1 ROW_FORMAT=TOKUDB_SNAPPY
TokuDB采用塊級壓縮显晶,每個(gè)塊大小是4M贷岸,這是壓縮前的大小磷雇;假設(shè)壓縮比是4:1偿警,壓縮后大小是1M左右。比較tricky地方是:TokuDB壓縮單位是partition唯笙,大小是64K户敬。相比innodb16K的塊大小來說要大不少落剪,更有利壓縮算法尋找重復(fù)串。
上面提到尿庐,修改壓縮算法后新老壓縮格式的數(shù)據(jù)可以同時(shí)存在。如何識別呢呢堰?
每個(gè)數(shù)據(jù)塊在壓縮數(shù)據(jù)前預(yù)留一個(gè)字節(jié)存儲壓縮算法抄瑟。從磁盤讀數(shù)據(jù)后,會根據(jù)那個(gè)字節(jié)的內(nèi)容調(diào)用相應(yīng)的解壓縮算法枉疼。
另外皮假,TokuDB還支持并行壓縮,數(shù)據(jù)塊包含的多個(gè)partition可以利用線程池并行進(jìn)行壓縮和序列化工作骂维,極大加速了數(shù)據(jù)寫盤速度惹资,這個(gè)功能在數(shù)據(jù)批量導(dǎo)入(import)情況下開啟。
在線增減字段
TokuDB還支持在輕微阻塞DML情況下航闺,增加或刪除表中的字段或者擴(kuò)展字段長度褪测。
執(zhí)行在線增減字段時(shí)表會鎖一小段時(shí)間,一般是秒級鎖表潦刃。鎖表時(shí)間短得益于fractal tree的實(shí)現(xiàn)侮措。TokuDB會把這些操作放到后臺去做,具體實(shí)現(xiàn)是:往root塊推送一個(gè)廣播msg乖杠,通過逐層apply這個(gè)廣播msg實(shí)現(xiàn)增減字段的操作分扎。
需要注意的:
- 不建議一次更新多個(gè)字段
- 刪除的字段是索引的一部分會鎖表,鎖表時(shí)間跟數(shù)據(jù)量成正比
- 縮短字段長度會鎖表胧洒,鎖表時(shí)間跟數(shù)據(jù)量成正比
mysql> ALTER TABLE t_test ADD COLUMN column_c int(11) NOT NULL;
mysql> SHOW CREATE TABLE t_test\G
Table: t_test
Create Table: CREATE TABLE `t_test` (
`column_a` int(11) NOT NULL,
`column_b` int(11) NOT NULL,
`column_c` int(11) NOT NULL,
PRIMARY KEY (`column_a`),
KEY `ind_1` (`column_b`)
) ENGINE=TokuDB DEFAULT CHARSET=latin1 ROW_FORMAT=TOKUDB_SNAPPY
mysql> ALTER TABLE t_test DROP COLUMN column_b;
mysql> SHOW CREATE TABLE t_test\G
Table: t_test
Create Table: CREATE TABLE `t_test` (
`column_a` int(11) NOT NULL,
`column_c` int(11) NOT NULL,
PRIMARY KEY (`column_a`)
) ENGINE=TokuDB DEFAULT CHARSET=latin1
穩(wěn)定高效寫入性能
TokuDB索引采用fractal tree結(jié)構(gòu)畏吓,索引修改工作由后臺線程異步完成。TokuDB會把每個(gè)索引更新轉(zhuǎn)化成一個(gè)msg卫漫,在server層上下文只把msg加到root(或者某個(gè)internal)塊msg buffer中便可返回菲饼;msg應(yīng)用到leaf塊的工作是由后臺線程完成的,此后臺線程被稱作cleaner汛兜,負(fù)責(zé)逐級apply msg直至leaf塊
DML語句被轉(zhuǎn)化成FT_INSERT/FT_DELETE巴粪,此類msg只應(yīng)用到leaf節(jié)點(diǎn)。
在線加索引/在線加字段被轉(zhuǎn)化成廣播msg粥谬,此類msg會被應(yīng)用到每個(gè)數(shù)據(jù)塊的每個(gè)數(shù)據(jù)項(xiàng)肛根。
實(shí)際上,fractal tree是buffer tree的變種漏策,在索引塊內(nèi)緩存更新操作派哲,把隨機(jī)請求轉(zhuǎn)化成順序請求,縮短server線程上下文的訪問路徑掺喻,縮短RT芭届。所以储矩,TokuDB在高并發(fā)大數(shù)據(jù)量場景下,可以提供穩(wěn)定高效的寫入性能褂乍。
除此之外持隧,TokuDB實(shí)現(xiàn)了bulk fetch優(yōu)化,range query性能也是不錯的逃片。
在線增加索引
TokuDB支持在線加索引不阻塞更新語句 (insert, update, delete) 的執(zhí)行屡拨。可以通過變量 tokudb_create_index_online 來控制是否開啟該特性, 不過遺憾的是目前只能通過 CREATE INDEX 語法實(shí)現(xiàn)在線創(chuàng)建褥实;如果用ALTER TABLE創(chuàng)建索引還是會鎖表的呀狼。
mysql> SHOW CREATE TABLE t_test\G
Table: t_test
Create Table: CREATE TABLE `t_test` (
`column_a` int(11) NOT NULL,
`column_b` int(11) NOT NULL,
PRIMARY KEY (`column_a`)
) ENGINE=TokuDB DEFAULT CHARSET=latin1 ROW_FORMAT=TOKUDB_SNAPPY
mysql> SET GLOBAL tokudb_create_index_online=ON;
mysql> CREATE INDEX ind_1 ON t_test(column_b);
mysql> SHOW CREATE TABLE t_test\G
Table: t_test
Create Table: CREATE TABLE `t_test` (
`column_a` int(11) NOT NULL,
`column_b` int(11) NOT NULL,
PRIMARY KEY (`column_a`),
KEY `ind_1` (`column_b`)
) ENGINE=TokuDB DEFAULT CHARSET=latin1 ROW_FORMAT=TOKUDB_SNAPPY
寫過程
如果不考慮unique constraint檢查,TokuDB寫是異步完成的损离。每個(gè)寫請求被轉(zhuǎn)化成FT_insert類型的msg哥艇,記錄著要寫入的<key,value>和事務(wù)信息用于跟蹤。
Server上下文的寫路徑很短僻澎,只要把寫請求對應(yīng)的msg追加到roo數(shù)據(jù)塊的msg buffer即可貌踏,這是LSM數(shù)據(jù)結(jié)構(gòu)的核心思想,把隨機(jī)寫轉(zhuǎn)換成順序?qū)懺趵猓琇evelDB和RocksDB也是采用類似實(shí)現(xiàn)哩俭。
由于大家都在root數(shù)據(jù)塊緩存msg,必然造成root塊成為熱點(diǎn)拳恋,也就是性能瓶頸凡资。
為了解決這個(gè)問題,TokuDB提出promotion概念谬运,從root數(shù)據(jù)塊開始至多往下看2層隙赁。如果當(dāng)前塊數(shù)據(jù)塊是中間塊并且msg buffer是空的,就跳過這層梆暖,把msg緩存到下一層中間塊伞访。
下面我們舉例說明write過程。
假設(shè)轰驳,insert之qiafractal tree狀態(tài)如下圖所示:
- insert 300
root數(shù)據(jù)塊上300對應(yīng)的msg buffer為空厚掷,需要進(jìn)行inject promotion,也就是說會把msg存儲到下面的子樹上级解。下一級數(shù)據(jù)塊上300對應(yīng)的msg buffer非空(msg:291)冒黑,不會繼續(xù)promotion,msg被存儲到當(dāng)前的msg buffer勤哗。
- insert 100
root數(shù)據(jù)塊上100對應(yīng)的msg buffer為空抡爹,需要進(jìn)行inject promotion,也就是說會把msg存儲到下面的子樹上芒划。下一級數(shù)據(jù)塊上100對應(yīng)的msg buffer也為空冬竟,需要繼續(xù)promotion欧穴。再下一級數(shù)據(jù)塊上100對應(yīng)的msg buffer非空(msg:84),不會繼續(xù)promotion泵殴,msg被存儲到當(dāng)前的msg buffer涮帘。
- insert 211
root數(shù)據(jù)塊上211對應(yīng)的msg buffer為空,需要進(jìn)行inject promotion袋狞,也就是說會把msg存儲到下面的子樹上焚辅。下一級數(shù)據(jù)塊上211對應(yīng)的msg buffer也為空,需要繼續(xù)promotion苟鸯。再下一級數(shù)據(jù)塊上211對應(yīng)的msg buffer也為空,但是不會繼續(xù)promotion棚点,msg被存儲到當(dāng)前的msg buffer早处。這是因?yàn)閜romotion至多向下看2層,這么做是為了避免dirty的數(shù)據(jù)塊數(shù)量太多瘫析,減少checkpoint刷臟的壓力砌梆。
行級鎖
TokuDB提供行級鎖處理并發(fā)讀寫數(shù)據(jù)。
所有的INSERT贬循、DELETE或者SELECT FOR UPDATE語句在修改索引數(shù)據(jù)結(jié)構(gòu)fractal tree之前咸包,需要先拿記錄(也就是key)對應(yīng)的行鎖,獲取鎖之后再去更新索引杖虾。與InnoDB行鎖實(shí)現(xiàn)不同烂瘫,InnoDB是鎖記錄數(shù)據(jù)結(jié)構(gòu)的一個(gè)bit。
由此可見奇适,TokuDB行鎖實(shí)現(xiàn)導(dǎo)致一些性能問題坟比,不適合大量并發(fā)更新的場景。
為了緩解行鎖等待問題嚷往,TokuDB提供了行鎖timeout參數(shù)(缺省是4秒)葛账,等待超時(shí)會返回失敗。這種處理有助于減少deadlock發(fā)生皮仁。
讀過程
由于中間數(shù)據(jù)塊(internal block)會緩存更新操作的msg籍琳,讀數(shù)據(jù)時(shí)需要先把上層msg buffer中的msg apply到葉子數(shù)據(jù)塊(leaf block)上,然后再去leaf上把數(shù)據(jù)讀上來贷祈。
3,4,5,6,7,8,9是中間數(shù)據(jù)塊趋急,10,11,12,13,14,15,16,17是葉子數(shù)據(jù)塊;
上圖中付燥,每個(gè)中間數(shù)據(jù)塊的fanout是2宣谈,表示至多有2個(gè)下一級數(shù)據(jù)塊;中間節(jié)點(diǎn)的msg buffer用來緩存下一級數(shù)據(jù)塊的msg键科,橘黃色表示有數(shù)據(jù)闻丑,黃綠色表示msg buffer是空的漩怎。
如果需要讀block11的數(shù)據(jù),需要先把數(shù)據(jù)塊3和數(shù)據(jù)塊6中的msg apply到葉子數(shù)據(jù)塊11嗦嗡,然后去11上讀數(shù)據(jù)勋锤。
Msg apply的過程也叫合并(merge),所有基于LSM原理的k-v引擎(比方LevelDB侥祭,RocksDB)讀數(shù)據(jù)時(shí)都要先做merge叁执,然后去相應(yīng)的數(shù)據(jù)塊上讀數(shù)據(jù)。
讀合并
如上圖所示矮冬,綠色是中間數(shù)據(jù)塊谈宛,紫色是葉數(shù)據(jù)塊;中間數(shù)據(jù)塊旁邊的黃色矩形是msg buffer胎署。
如要要query區(qū)間[5-18]的數(shù)據(jù)
- 以5作為search key從root到leaf搜索>=5的數(shù)據(jù)吆录,每個(gè)數(shù)據(jù)塊內(nèi)部做binary search,最終定位到第一個(gè)leaf塊琼牧。讀數(shù)據(jù)之前恢筝,判斷第一個(gè)leaf塊所包含的[5,9]區(qū)間存在需要apply的msg(上圖中是6,7,8),需要先做msg apply然后讀取數(shù)據(jù)(5,6,7,8,9)巨坊;
- 第一個(gè)leaf塊讀取完畢撬槽,以9作為search key從root到leaf搜索>9的數(shù)據(jù),每個(gè)數(shù)據(jù)塊內(nèi)部做binary search趾撵,最終定位到第二個(gè)leaf塊侄柔。讀數(shù)據(jù)之前,判斷第二個(gè)leaf塊所包含的[10,16]區(qū)間存在需要apply的msg(上圖中是15)鼓寺,需要先做msg apply然后讀取數(shù)據(jù)(10,12,15,16);
- 第二個(gè)leaf塊讀取完畢勋拟,以16作為search key從root到leaf搜索>16的數(shù)據(jù),每個(gè)數(shù)據(jù)塊內(nèi)部做binary search妈候,最終定位到第三個(gè)leaf塊敢靡。第三個(gè)數(shù)據(jù)塊所包含的[17,18]區(qū)間不存在需要apply的msg,直接讀取數(shù)據(jù)(17,18)苦银。
優(yōu)化range query
為了減少merge代價(jià)啸胧,TokuDB提供bulk fetch功能:每個(gè)basement node大小64K(這個(gè)是數(shù)據(jù)壓縮解壓縮的單位)只要做一次merge操作;并且TokuDB的cursor支持批量讀幔虏,一個(gè)batch內(nèi)讀取若干行數(shù)據(jù)緩存在內(nèi)存纺念,之后每個(gè)handler::index_next先去緩存里取下一行數(shù)據(jù),只有當(dāng)緩存數(shù)據(jù)全部被消費(fèi)過之后發(fā)起下一個(gè)batch讀想括,再之后handler::index_next操作還是先去緩存里取下一行數(shù)據(jù)陷谱。
Batch讀過程由cursor的callback驅(qū)動,直接把數(shù)據(jù)存到TokuDB handler的buffer中,不僅減少了merge次數(shù)烟逊,也減少了handler::index_next調(diào)用棧深度渣窜。
異步合并
TokuDB支持后臺異步合并msg,把中間數(shù)據(jù)塊中緩存的msg逐層向下刷宪躯,直至leaf數(shù)據(jù)塊乔宿。
這過程是由周期運(yùn)行的cleaner線程完成的,cleaner線程每秒被喚醒一次访雪。每次執(zhí)行掃描一定數(shù)目的數(shù)據(jù)塊详瑞,尋找緩存msg最多的中間數(shù)據(jù)塊;掃描結(jié)束后臣缀,把msg buffer中的msg刷到(merge)下一層數(shù)據(jù)塊中坝橡。
前面提到,大部分寫數(shù)據(jù)并不會把msg直接寫到leaf精置,而是把msg緩存到root或者某一級中間數(shù)據(jù)塊上驳庭。雖然promotion緩解了root塊熱點(diǎn)問題,局部熱點(diǎn)問題依然存在氯窍。
假設(shè)某一個(gè)時(shí)間段大量并發(fā)更新某范圍的索引數(shù)據(jù),msg buffer短時(shí)間內(nèi)堆積大量msg蹲堂;由于cleaner線程是單線程順序掃描狼讨,很可能來不及處理熱點(diǎn)數(shù)據(jù)塊,導(dǎo)致熱點(diǎn)數(shù)據(jù)msg堆積柒竞,并且數(shù)據(jù)塊讀寫鎖爭搶現(xiàn)象越來越嚴(yán)重政供。
為了解決這個(gè)問題,TokuDB引入了專門的線程池來幫助cleaner線程快速處理熱點(diǎn)塊朽基。大致處理是:如果msg buffer緩存了過多的msg布隔,寫數(shù)據(jù)上下文就會喚醒線程池中的線程幫助cleaner快速合并當(dāng)前數(shù)據(jù)塊。
刷臟
為了加速數(shù)據(jù)處理過程稼虎,TokuDB在內(nèi)存緩存數(shù)據(jù)塊衅檀,所有數(shù)據(jù)塊組織成一個(gè)hash表,可以通過hash計(jì)算快速定位霎俩,這個(gè)hash表被稱作cachetable哀军。InnoDB也有類似緩存機(jī)制,叫做buffer pool(簡記bp)打却。
內(nèi)存中數(shù)據(jù)塊被修改后不會立即寫回磁盤杉适,而是被標(biāo)記成dirty狀態(tài)。Cachetable滿會觸發(fā)evict操作柳击,選擇一個(gè)victim數(shù)據(jù)塊釋放內(nèi)存猿推。如果victim是dirty的,需要先把數(shù)據(jù)寫回捌肴。Evict操作是由后臺線程evictor處理的蹬叭,缺省1秒鐘運(yùn)行一次藕咏,也可能由于緩存滿由server上下文觸發(fā)。
TokuDB采用激進(jìn)的緩存策略具垫,盡量把數(shù)據(jù)保留在內(nèi)存中侈离。除了evictor線程以外,還有一個(gè)定期刷臟的checkpoint線程筝蚕,缺省60每秒運(yùn)行一次把內(nèi)存中所有臟數(shù)據(jù)回刷到磁盤上卦碾。Checkpoint結(jié)束后,清理redo log文件起宽。
TokuDB采用sharp checkpoint策略洲胖,checkpoint開始時(shí)刻把cachetable中所有數(shù)據(jù)塊遍歷一遍,對每個(gè)數(shù)據(jù)塊打上checkpoint_pending標(biāo)記坯沪,這個(gè)過程是拿著client端exclusive鎖的绿映,所有INSERT/DELETE操作會被阻塞。標(biāo)記checkpoint_pending過程結(jié)束后腐晾,釋放exclusive鎖叉弦,server的更新請求可以繼續(xù)執(zhí)行。
隨后checkpoint線程會對每個(gè)標(biāo)記checkpoint_pending的臟頁進(jìn)行回寫藻糖。為了減少I/O期間數(shù)據(jù)塊讀寫鎖沖突淹冰,先把數(shù)據(jù)clone一份,然后對cloned數(shù)據(jù)進(jìn)行回寫巨柒;clone過程是持有讀寫鎖的write鎖樱拴,clone結(jié)束后釋放讀寫鎖,數(shù)據(jù)塊可以繼續(xù)提供讀寫服務(wù)洋满。Cloned數(shù)據(jù)塊寫回時(shí)晶乔,持有讀寫I/O的mutex鎖,保證on-going的I/O至多只有一個(gè)牺勾。
更新數(shù)據(jù)塊發(fā)現(xiàn)是checkpoint_pending并且dirty正罢,那么需要先把老數(shù)據(jù)寫盤。由于checkpoint是單線程禽最,可能來不及處理這個(gè)數(shù)據(jù)塊腺怯。為此,TokuDB提供一個(gè)專門的線程池川无,server上下文只要把數(shù)據(jù)clone一份呛占,然后把回寫cloned數(shù)據(jù)的任務(wù)扔給線程池處理。
Cachetable
所有緩存在內(nèi)存的數(shù)據(jù)塊按照首次訪問(cachemiss)時(shí)間順序組織成clock_list懦趋。TokuDB沒有維護(hù)LRU list晾虑,而是使用clock_list和count(可理解成age)來模擬數(shù)據(jù)塊使用頻率。
Evictor,checkpoint和cleaner線程(參見異步合并小結(jié))都是掃描clock_list帜篇,每個(gè)線程維護(hù)自己的head記錄著下次掃描開始位置糙捺。
如上圖所示,hash中黑色連線表示bucket鏈表笙隙,藍(lán)色連線表示clock_list洪灯。Evictor,checkpoint和cleaner的header分別是m_clock_head,m_checkpoint_head和m_cleaner_head竟痰。
數(shù)據(jù)塊被訪問签钩,count遞增(最大值15);每次evictor線程掃到數(shù)據(jù)塊count遞減坏快,減到0整個(gè)數(shù)據(jù)塊會被evict出去铅檩。
TokuDB塊size比較大,缺省是4M莽鸿;所以按照塊這個(gè)維度去做evict不是特別合理昧旨,有些partition數(shù)據(jù)比較熱需要在內(nèi)存多呆一會,冷的partition可以盡早釋放祥得。
為此兔沃,TokuDB還提供partial evict功能,數(shù)據(jù)塊被掃描時(shí)级及,如果count>0并且是clean的粘拾,就把冷partition釋放掉。Partial evict對中間數(shù)據(jù)塊(包含key分布信息)做了特殊處理创千,把partition轉(zhuǎn)成壓縮格式減少內(nèi)存使用,后續(xù)訪問需要先解壓縮再使用入偷。Partial evict對leaf數(shù)據(jù)塊的處理是:把partition釋放追驴,后續(xù)訪問需要調(diào)用pf_callback從磁盤讀數(shù)據(jù),讀上來的數(shù)據(jù)也是先解壓縮的疏之。
寫優(yōu)先
這里說的寫優(yōu)先是指并發(fā)讀寫數(shù)據(jù)塊時(shí)殿雪,寫操作優(yōu)先級高,跟行級鎖無關(guān)锋爪。
假設(shè)用戶要讀區(qū)間[210, 256]丙曙,需要從root->leaf每層做binary search,在search之前要把數(shù)據(jù)塊讀到內(nèi)存并且加readlock其骄。
如上圖所示亏镰,root(height 3)和root子數(shù)據(jù)塊(height 2)嘗試讀鎖(try_readlock)成功,但是在root的第二級子數(shù)據(jù)塊(height 1)嘗試讀鎖失敗拯爽,這個(gè)query會把root和root子數(shù)據(jù)塊(height 2)讀鎖釋放掉索抓,退回到root重新嘗試讀鎖。
日志
TokuDB采用WAL(Write Ahead Log),每個(gè)INSERT/DELETE/CREATE INDEX/DROP INDEX操作之前會記redo log和undo log逼肯,用于崩潰恢復(fù)和事務(wù)回滾耸黑。
TokuDB的redo log是邏輯log,每個(gè)log entry記錄一個(gè)更新事件篮幢,主要包含:
- 長度1
- log command(標(biāo)識操作類型)
- lsn
- timestamp
- 事務(wù)id
- crc
- db
- key
- val
- 長度2
其中大刊,db,key和val不是必須的三椿,比如checkpoint就沒有這些信息缺菌。
長度1和長度2一定是相等的,記兩個(gè)長度是為了方便前向(backward)和后向(forward)掃描赋续。
Recory過程首先前向掃描男翰,尋找最后一個(gè)有效的checkpoint;從那個(gè)checkpoint開始后向掃描回放redo log纽乱,直至最后一個(gè)commit事務(wù)蛾绎。然后把所有活躍事務(wù)abort掉,最后做一個(gè)checkpoint把數(shù)據(jù)修改同步到磁盤上鸦列。
TokuDB的undo日志是記錄在一個(gè)單獨(dú)的文件上租冠,undo日志也是邏輯的,記錄的是更新的逆操作薯嗤。獨(dú)立的undo日志顽爹,避免老數(shù)據(jù)造成數(shù)據(jù)空間膨脹問題。
事務(wù)和MVCC
相對RocksDB骆姐,TokuDB最顯著的優(yōu)勢就是支持完整事務(wù)镜粤,支持MVCC。
TokuDB還支持事務(wù)嵌套玻褪,可以用來實(shí)現(xiàn)savepoint功能肉渴,把一個(gè)大事務(wù)分割成一組小事務(wù),小事務(wù)失敗只要重試它自己就好了带射,不用回滾整個(gè)事務(wù)同规。
ISOLATION LEVEL
TokuDB支持隔離級別:READ UNCOMMITTED, READ COMMITTED (default), REPEATABLE READ, SERIALIZABLE。SERIALIZABLE是通過行級鎖實(shí)現(xiàn)的窟社;READ COMMITTED (default),和REPEATABLE READ是通過snapshot實(shí)現(xiàn)券勺。
TokuDB支持多版本学赛,多版本數(shù)據(jù)是記錄在頁數(shù)據(jù)塊上的激况。每個(gè)leaf數(shù)據(jù)塊上的<key,value>二元組忘古,key是索引的key值(其實(shí)是拼了pk的)离唬,value是MVCC數(shù)據(jù)攻晒。這與oracle和InnoDB不同踱阿,oracle的多版本是通過undo segment計(jì)算構(gòu)造出來的辆它。InnoDB MVCC實(shí)現(xiàn)原理與oracle近似刨啸。
事務(wù)的可見性
每個(gè)寫事務(wù)開始時(shí)都會獲得一個(gè)事務(wù)id(TokuDB記做txnid,InnoDB記做trxid)侣灶。其實(shí)甸祭,事務(wù)id是一個(gè)全局遞增的整數(shù)。所有的寫事務(wù)都會被加入到事務(wù)mgr的活躍事務(wù)列表里面褥影。
所謂活躍事務(wù)就是處于執(zhí)行中的事務(wù)池户,對于RC以上隔離界別,活躍事務(wù)都是不可見的凡怎。前面提到過校焦,SERIALIZABLE是通過行級鎖實(shí)現(xiàn)的,不必考慮可見性统倒。
一般來說寨典,RC可見性是語句級別的,RR可見性是事務(wù)級別的房匆。這在TokuDB中是如何實(shí)現(xiàn)的呢耸成?
每個(gè)語句執(zhí)行開始都會創(chuàng)建一個(gè)子事務(wù)。如果是RC浴鸿、RR隔離級別井氢,還會創(chuàng)建snapshot。Snapshot也有活躍事務(wù)列表岳链,RC隔離級別是復(fù)制事務(wù)mgr在語句事務(wù)開始時(shí)刻的活躍事務(wù)列表花竞,RR隔離級別是復(fù)制事務(wù)mgr在server層事務(wù)開始時(shí)刻的活躍事務(wù)列表。
Snapshot可見性就是事務(wù)id比snapshot的事務(wù)id更小掸哑,意味著更早開始執(zhí)行约急;但是不在snapshot活躍事務(wù)列表的事務(wù)。
GC
隨著事務(wù)提交snapshot結(jié)束苗分,老版本數(shù)據(jù)不在被訪問需要清理烤宙,這就引入了GC的問題。
為了判斷寫事務(wù)的更新是否被其他事務(wù)訪問俭嘁,TokuDB的事務(wù)mgr維護(hù)了reference_xids數(shù)組,記錄事務(wù)提交時(shí)刻服猪,系統(tǒng)中處于活躍狀態(tài)snapshot個(gè)數(shù)供填,作用相當(dāng)于reference_count。
以上描述了TokuDB如何跟蹤寫事務(wù)的引用者罢猪。那么GC是何時(shí)執(zhí)行的呢近她?
可以調(diào)用OPTIMIZE TABLE顯式觸發(fā),也可以在后續(xù)訪問索引key時(shí)隱式觸發(fā)膳帕。
典型業(yè)務(wù)場景
以上介紹了TokuDB引擎內(nèi)核原理粘捎,下面我們從HybridDB for MySQL產(chǎn)品的角度談一下業(yè)務(wù)場景和性能薇缅。
HybridDB for MySQL設(shè)計(jì)目標(biāo)是提供低成本大容量分布式數(shù)據(jù)庫服務(wù),一體式處理OLTP和OLAP混合業(yè)務(wù)場景攒磨,提供存儲和計(jì)算能力泳桦;而存儲和計(jì)算節(jié)點(diǎn)在物理上是分離的,用戶可以根據(jù)業(yè)務(wù)特點(diǎn)定制存儲計(jì)算節(jié)點(diǎn)的配比娩缰,也可以單獨(dú)購買存儲和計(jì)算節(jié)點(diǎn)灸撰。
HybridDB for MySQL數(shù)據(jù)只存儲一份,減少數(shù)據(jù)交換成本拼坎,同時(shí)也降低了存儲成本浮毯;所有功能集成在一個(gè)實(shí)例之中,提供統(tǒng)一的用戶接口泰鸡,一致的數(shù)據(jù)視圖和全局統(tǒng)一的SQL兼容性债蓝。
HybridDB for MySQL支持?jǐn)?shù)據(jù)庫分區(qū),整體容量和性能隨分區(qū)數(shù)目增長而線性增長盛龄;用戶可先購買一個(gè)基本配置饰迹,隨業(yè)務(wù)發(fā)展后續(xù)可以購買更多的節(jié)點(diǎn)進(jìn)行擴(kuò)容。HybridDB for MySQL提供在線的擴(kuò)容和縮容能力讯嫂,水平擴(kuò)展/收縮存儲和計(jì)算節(jié)點(diǎn)拓?fù)浣Y(jié)構(gòu)蹦锋;在擴(kuò)展過程中欧芽,不影響業(yè)務(wù)對外提供服務(wù)莉掂,優(yōu)化數(shù)據(jù)分布算法,減少重新分布數(shù)據(jù)量千扔;采用流式遷移憎妙,備份數(shù)據(jù)不落地。
除此之外曲楚,HybridDB for MySQL還支持高可用厘唾,復(fù)用鏈路高可用技術(shù),采用一主多備方式實(shí)現(xiàn)三副本龙誊。HybridDB for MySQL復(fù)用ApsaraDB for MySQL已有技術(shù)框架抚垃,部署、升級趟大、鏈路管理鹤树、資源管理、備份逊朽、安全罕伯、監(jiān)控和日志復(fù)用已有功能模塊,技術(shù)風(fēng)險(xiǎn)低叽讳,驗(yàn)證周期短追他,可以說是站在巨人肩膀上的創(chuàng)新坟募。
低成本大容量存儲場景
HybridDB for MySQL使用軟硬件整體方案解決大容量低成本問題。
軟件方面邑狸,HybridDB for MySQL是分布式數(shù)據(jù)庫懈糯,擺脫單機(jī)硬件資源限制,提供橫向擴(kuò)展能力推溃,容量和性能隨節(jié)點(diǎn)數(shù)目增加而線性增加昂利。存儲節(jié)點(diǎn)MySQL實(shí)例選擇使用TokuDB引擎,支持塊級壓縮铁坎,壓縮算法以表單位進(jìn)行配置蜂奸。用戶可根據(jù)業(yè)務(wù)自身特點(diǎn)選擇使用壓縮效果好的壓縮算法比如lzma,也可以選擇quicklz這種壓縮速度快資源消耗低的壓縮算法硬萍,也可以選擇像zstd這種壓縮效果和壓縮速度比較均衡的壓縮算法扩所。如果選用zstd壓縮算法,線上實(shí)測的壓縮比是3~4朴乖。
硬件方面祖屏,HybridDB for MySQL采用分層存儲解決方案,大量冷數(shù)據(jù)存儲在SATA盤上买羞,少量溫?cái)?shù)據(jù)存儲在ssd上袁勺,熱數(shù)據(jù)存儲在數(shù)據(jù)庫引擎的內(nèi)存緩存中(TokuDB cachetable)。SATA盤和ssd上數(shù)據(jù)之間的映射關(guān)系通過bcache驅(qū)動模塊來管理畜普,bcache可以配置成WriteBack模式(寫路徑數(shù)據(jù)寫ssd后即返回期丰,ssd中更新數(shù)據(jù)由bcache負(fù)責(zé)同步到SATA盤上),可加速數(shù)據(jù)庫checkpoint寫盤速度吃挑;也可以配置成WriteThrough模式(寫路徑數(shù)據(jù)同時(shí)寫到ssd和SATA上钝荡,兩者都ack寫才算完成)。
持續(xù)高并發(fā)寫入場景
TokuDB采用fractal tree(中文譯作分型樹)數(shù)據(jù)結(jié)構(gòu)舶衬,優(yōu)化寫路徑埠通,大部分二級索引的寫操作是異步的,寫被緩存到中間數(shù)據(jù)塊即返回逛犹。寫操作同步到葉數(shù)據(jù)塊可以通過后臺cleaner線程異步完成端辱,也可能由后續(xù)的讀操作同步完成(讀合并)。Fractal tree在前面的內(nèi)核原理部分有詳盡描述虽画,這里就不贅述了舞蔽。
細(xì)心的朋友可能會發(fā)現(xiàn),我們在異步寫前加了個(gè)前綴:大部分二級索引狸捕。那么大部分是指那些情況呢?這里大部分是指不需要做quickness檢查的索引众雷,寫請求直接扔給fractal tree的msg buffer即可返回灸拍。如果二級索引包含unique索引做祝,必須先做唯一性檢查保證不存在重復(fù)鍵值。否則鸡岗,異步合并(或者讀合并)無法通知唯一性檢查失敗混槐,也無法回滾其他索引的更新。Pk字段也有類似的唯一性語義轩性,寫之前會去查詢pk鍵值是否已存在声登,順便做了root到leaf數(shù)據(jù)塊的預(yù)讀和讀合并。所以揣苏,每條新增數(shù)據(jù)執(zhí)行INSERT INTO的過程不完全是異步寫悯嗓。
ApsaraDB for MySQL對于日志場景做了優(yōu)化,利用INSERT IGNORE語句保證pk鍵值唯一性卸察,并且通過把二級索引鍵值1-1映射到pk鍵值空間的方法保證二級索引唯一性脯厨,將寫操作轉(zhuǎn)換成全異步寫,大大降低了寫延遲坑质。由于省掉唯一性檢查的讀過程合武,引擎在內(nèi)存中緩存的數(shù)據(jù)量大大減少,緩存寫請求的數(shù)據(jù)塊受讀干擾被釋放的可能性大大降低涡扼,進(jìn)而寫路徑上發(fā)生cachetable miss的可能性降低稼跳,寫性能更加穩(wěn)定。
分布式業(yè)務(wù)場景
HybridDB for MySQL同時(shí)提供單分區(qū)事務(wù)和分布式事務(wù)支持吃沪,支持跨表汤善、跨引擎、跨數(shù)據(jù)庫巷波、跨MySQL實(shí)例萎津,跨存儲節(jié)點(diǎn)的事務(wù)。HybridDB for MySQL使用兩階段提交協(xié)議支持分布式事務(wù)抹镊,提交階段proxy作為協(xié)調(diào)者將分布式事務(wù)狀態(tài)記錄到事務(wù)元數(shù)據(jù)庫锉屈;分區(qū)事務(wù)恢復(fù)時(shí),proxy從事務(wù)元數(shù)據(jù)庫取得分布式事務(wù)狀態(tài)垮耳,并作為協(xié)調(diào)者重新發(fā)起失敗分區(qū)的事務(wù)颈渊。
HybridDB for MySQL還可以通過判斷WHERE條件是否包含分區(qū)鍵的等值條件,決定是單分區(qū)事務(wù)還是分布式事務(wù)终佛。如果是單分區(qū)事務(wù)俊嗽,直接發(fā)送給分區(qū)MySQL實(shí)例處理。
在線擴(kuò)容/縮容場景
HybridDB for MySQL通過將存儲分區(qū)無縫遷移到更多(或更少的)MySQL分區(qū)實(shí)例上實(shí)現(xiàn)彈性數(shù)據(jù)擴(kuò)展(收縮)的功能铃彰,分區(qū)遷移完成之后proxy層更新路由信息绍豁,把請求切到新分區(qū)上,老分區(qū)上的數(shù)據(jù)會自動清理牙捉。Proxy切換路由信息時(shí)會保持連接竹揍,不影響用戶業(yè)務(wù)敬飒。
數(shù)據(jù)遷移是通過全量備份+增量備份方式實(shí)現(xiàn),全量備份不落地直接流式上傳到oss芬位。增量備份通過binlog方式同步无拗,HybridDB for MySQL不必自行實(shí)現(xiàn)binlog解析模塊,而是利用ApsaraDB for MySQL優(yōu)化過的復(fù)制邏輯完成增量同步昧碉,通過并行復(fù)制提升性能英染,并且保證數(shù)據(jù)一致性。
聚合索引提升讀性能
TokuDB支持一個(gè)表上創(chuàng)建多個(gè)聚合索引被饿,以空間代價(jià)換取查詢性能四康,減少回pk取數(shù)據(jù)。阿里云ApsaraDB for MySQL在優(yōu)化器上對TokuDB聚合索引做了額外支持锹漱,在cost接近時(shí)可以優(yōu)先選擇聚合索引箭养;存在多個(gè)cost接近的聚合索引,可以優(yōu)先選擇與WHERE條件最匹配的聚合索引哥牍。
與單機(jī)版ApsaraDB for MySQL對比
與阿里云OLTP+OLAP混合方案對比
性能報(bào)告
高并發(fā)業(yè)務(wù)
壓測配置:
- 4節(jié)點(diǎn)毕泌,每節(jié)點(diǎn)8-core,32G嗅辣,12000 iops撼泛,ssd盤
高吞吐業(yè)務(wù)
壓測配置:
- 8節(jié)點(diǎn),每節(jié)點(diǎn)16-core澡谭,48G愿题,12000 iops,ssd盤