閱讀908 返回首頁    go 技術社區[雲棲]


TokuDB · 引擎特性 · HybridDB for MySQL高壓縮引擎TokuDB 揭秘

HybridDB for MySQL(原名petadata)是麵向在線事務(OLTP)和在線分析(OLAP)混合場景的關係型數據庫。HybridDB采用一份數據存儲來進行OLTP和OLAP處理,解決了以往需要把一份數據多次複製來分別進行業務交易和數據分析的問題,極大地降低了數據存儲的成本,縮短了數據分析的延遲,使得實時分析決策稱為可能。

HybridDB for MySQL兼容MySQL的語法及函數,並且增加了對Oracle常用分析函數的支持,100%完全兼容TPC-H和TPC-DS測試標準,從而降低了用戶的開發、遷移和維護成本。

TokuDB是TokuTek公司(已被 Percona收購)研發的新引擎,支持事務/MVCC,有著出色的數據壓縮功能,支持異步寫入數據功能。

TokuDB索引結構采用fractal tree數據結構,是buffer tree的變種,寫入性能優異,適合寫多讀少的場景。除此之外,TokuDB還支持在線加減字段,在線創建索引,鎖表時間很短。

Percona Server和Mariadb支持TokuDB作為大數據場景下的引擎,目前官方MySQL還不支持TokuDB。ApsaraDB for MySQL從2015年4月開始支持TokuDB,在大數據或者高並發寫入場景下推薦使用。

TokuDB優勢

數據壓縮

TokuDB最顯著的優勢就是數據壓縮,支持多種壓縮算法,用戶可按照實際的資源消耗修改壓縮算法,生產環境下推薦使用zstd,實測的壓縮比是4:1。

目前HybridDB for MySQL支持6中壓縮算法:

  • lzma: 壓縮比最高,資源消耗高
  • zlib:Percona默認壓縮算法,最流行,壓縮比和資源消耗適中
  • quicklz:速度快,壓縮比最低
  • snappy:google研發的,壓縮比較低,速度快
  • zstd:壓縮比接近zlib,速度快
  • uncompressed:不壓縮,速度最快

Percona建議6核以下場景使用默認壓縮算法zlib,6核以上可以使用壓縮率更高的壓縮算法,大數據場景下推薦使用zstd壓縮算法,壓縮比高,壓縮和解壓速度快,也比較穩定。

用戶可以在建表時使用ROW_FORMAT子句指定壓縮算法,也可用使用ALTER TABLE修改壓縮算法。ALTER TABLE執行後新數據使用新的壓縮算法,老數據仍是老的壓縮格式。

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采用塊級壓縮,每個塊大小是4M,這是壓縮前的大小;假設壓縮比是4:1,壓縮後大小是1M左右。比較tricky地方是:TokuDB壓縮單位是partition,大小是64K。相比innodb16K的塊大小來說要大不少,更有利壓縮算法尋找重複串。

上麵提到,修改壓縮算法後新老壓縮格式的數據可以同時存在。如何識別呢?

每個數據塊在壓縮數據前預留一個字節存儲壓縮算法。從磁盤讀數據後,會根據那個字節的內容調用相應的解壓縮算法。

另外,TokuDB還支持並行壓縮,數據塊包含的多個partition可以利用線程池並行進行壓縮和序列化工作,極大加速了數據寫盤速度,這個功能在數據批量導入(import)情況下開啟。

在線增減字段

TokuDB還支持在輕微阻塞DML情況下,增加或刪除表中的字段或者擴展字段長度。

執行在線增減字段時表會鎖一小段時間,一般是秒級鎖表。鎖表時間短得益於fractal tree的實現。TokuDB會把這些操作放到後台去做,具體實現是:往root塊推送一個廣播msg,通過逐層apply這個廣播msg實現增減字段的操作。

需要注意的:
- 不建議一次更新多個字段
- 刪除的字段是索引的一部分會鎖表,鎖表時間跟數據量成正比
- 縮短字段長度會鎖表,鎖表時間跟數據量成正比

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

穩定高效寫入性能

TokuDB索引采用fractal tree結構,索引修改工作由後台線程異步完成。TokuDB會把每個索引更新轉化成一個msg,在server層上下文隻把msg加到root(或者某個internal)塊msg buffer中便可返回;msg應用到leaf塊的工作是由後台線程完成的,此後台線程被稱作cleaner,負責逐級apply msg直至leaf塊

DML語句被轉化成FT_INSERT/FT_DELETE,此類msg隻應用到leaf節點。

在線加索引/在線加字段被轉化成廣播msg,此類msg會被應用到每個數據塊的每個數據項。

實際上,fractal tree是buffer tree的變種,在索引塊內緩存更新操作,把隨機請求轉化成順序請求,縮短server線程上下文的訪問路徑,縮短RT。所以,TokuDB在高並發大數據量場景下,可以提供穩定高效的寫入性能。

除此之外,TokuDB實現了bulk fetch優化,range query性能也是不錯的。

在線增加索引

TokuDB支持在線加索引不阻塞更新語句 (insert, update, delete) 的執行。可以通過變量 tokudb_create_index_online 來控製是否開啟該特性, 不過遺憾的是目前隻能通過 CREATE INDEX 語法實現在線創建;如果用ALTER TABLE創建索引還是會鎖表的。

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寫是異步完成的。每個寫請求被轉化成FT_insert類型的msg,記錄著要寫入的<key,value>和事務信息用於跟蹤。

Server上下文的寫路徑很短,隻要把寫請求對應的msg追加到roo數據塊的msg buffer即可,這是LSM數據結構的核心思想,把隨機寫轉換成順序寫,LevelDB和RocksDB也是采用類似實現。

由於大家都在root數據塊緩存msg,必然造成root塊成為熱點,也就是性能瓶頸。

為了解決這個問題,TokuDB提出promotion概念,從root數據塊開始至多往下看2層。如果當前塊數據塊是中間塊並且msg buffer是空的,就跳過這層,把msg緩存到下一層中間塊。

下麵我們舉例說明write過程。

假設,insert之qiafractal tree狀態如下圖所示:

image.png

  • insert 300

root數據塊上300對應的msg buffer為空,需要進行inject promotion,也就是說會把msg存儲到下麵的子樹上。下一級數據塊上300對應的msg buffer非空(msg:291),不會繼續promotion,msg被存儲到當前的msg buffer。

image.png

  • insert 100

root數據塊上100對應的msg buffer為空,需要進行inject promotion,也就是說會把msg存儲到下麵的子樹上。下一級數據塊上100對應的msg buffer也為空,需要繼續promotion。再下一級數據塊上100對應的msg buffer非空(msg:84),不會繼續promotion,msg被存儲到當前的msg buffer。

image.png

  • insert 211

root數據塊上211對應的msg buffer為空,需要進行inject promotion,也就是說會把msg存儲到下麵的子樹上。下一級數據塊上211對應的msg buffer也為空,需要繼續promotion。再下一級數據塊上211對應的msg buffer也為空,但是不會繼續promotion,msg被存儲到當前的msg buffer。這是因為promotion至多向下看2層,這麼做是為了避免dirty的數據塊數量太多,減少checkpoint刷髒的壓力。

image.png

行級鎖

TokuDB提供行級鎖處理並發讀寫數據。

所有的INSERT、DELETE或者SELECT FOR UPDATE語句在修改索引數據結構fractal tree之前,需要先拿記錄(也就是key)對應的行鎖,獲取鎖之後再去更新索引。與InnoDB行鎖實現不同,InnoDB是鎖記錄數據結構的一個bit。

由此可見,TokuDB行鎖實現導致一些性能問題,不適合大量並發更新的場景。

為了緩解行鎖等待問題,TokuDB提供了行鎖timeout參數(缺省是4秒),等待超時會返回失敗。這種處理有助於減少deadlock發生。

讀過程

由於中間數據塊(internal block)會緩存更新操作的msg,讀數據時需要先把上層msg buffer中的msg apply到葉子數據塊(leaf block)上,然後再去leaf上把數據讀上來。

image.png

3,4,5,6,7,8,9是中間數據塊,10,11,12,13,14,15,16,17是葉子數據塊;

上圖中,每個中間數據塊的fanout是2,表示至多有2個下一級數據塊;中間節點的msg buffer用來緩存下一級數據塊的msg,橘黃色表示有數據,黃綠色表示msg buffer是空的。

如果需要讀block11的數據,需要先把數據塊3和數據塊6中的msg apply到葉子數據塊11,然後去11上讀數據。

Msg apply的過程也叫合並(merge),所有基於LSM原理的k-v引擎(比方LevelDB,RocksDB)讀數據時都要先做merge,然後去相應的數據塊上讀數據。

讀合並

image.png

如上圖所示,綠色是中間數據塊,紫色是葉數據塊;中間數據塊旁邊的黃色矩形是msg buffer。

如要要query區間[5-18]的數據
- 以5作為search key從root到leaf搜索>=5的數據,每個數據塊內部做binary search,最終定位到第一個leaf塊。讀數據之前,判斷第一個leaf塊所包含的[5,9]區間存在需要apply的msg(上圖中是6,7,8),需要先做msg apply然後讀取數據(5,6,7,8,9);
- 第一個leaf塊讀取完畢,以9作為search key從root到leaf搜索>9的數據,每個數據塊內部做binary search,最終定位到第二個leaf塊。讀數據之前,判斷第二個leaf塊所包含的[10,16]區間存在需要apply的msg(上圖中是15),需要先做msg apply然後讀取數據(10,12,15,16);
- 第二個leaf塊讀取完畢,以16作為search key從root到leaf搜索>16的數據,每個數據塊內部做binary search,最終定位到第三個leaf塊。第三個數據塊所包含的[17,18]區間不存在需要apply的msg,直接讀取數據(17,18)。

優化range query

為了減少merge代價,TokuDB提供bulk fetch功能:每個basement node大小64K(這個是數據壓縮解壓縮的單位)隻要做一次merge操作;並且TokuDB的cursor支持批量讀,一個batch內讀取若幹行數據緩存在內存,之後每個handler::index_next先去緩存裏取下一行數據,隻有當緩存數據全部被消費過之後發起下一個batch讀,再之後handler::index_next操作還是先去緩存裏取下一行數據。

image.png

Batch讀過程由cursor的callback驅動,直接把數據存到TokuDB handler的buffer中,不僅減少了merge次數,也減少了handler::index_next調用棧深度。

異步合並

TokuDB支持後台異步合並msg,把中間數據塊中緩存的msg逐層向下刷,直至leaf數據塊。

這過程是由周期運行的cleaner線程完成的,cleaner線程每秒被喚醒一次。每次執行掃描一定數目的數據塊,尋找緩存msg最多的中間數據塊;掃描結束後,把msg buffer中的msg刷到(merge)下一層數據塊中。

image.png

前麵提到,大部分寫數據並不會把msg直接寫到leaf,而是把msg緩存到root或者某一級中間數據塊上。雖然promotion緩解了root塊熱點問題,局部熱點問題依然存在。

假設某一個時間段大量並發更新某範圍的索引數據,msg buffer短時間內堆積大量msg;由於cleaner線程是單線程順序掃描,很可能來不及處理熱點數據塊,導致熱點數據msg堆積,並且數據塊讀寫鎖爭搶現象越來越嚴重。

為了解決這個問題,TokuDB引入了專門的線程池來幫助cleaner線程快速處理熱點塊。大致處理是:如果msg buffer緩存了過多的msg,寫數據上下文就會喚醒線程池中的線程幫助cleaner快速合並當前數據塊。

刷髒

為了加速數據處理過程,TokuDB在內存緩存數據塊,所有數據塊組織成一個hash表,可以通過hash計算快速定位,這個hash表被稱作cachetable。InnoDB也有類似緩存機製,叫做buffer pool(簡記bp)。

內存中數據塊被修改後不會立即寫回磁盤,而是被標記成dirty狀態。Cachetable滿會觸發evict操作,選擇一個victim數據塊釋放內存。如果victim是dirty的,需要先把數據寫回。Evict操作是由後台線程evictor處理的,缺省1秒鍾運行一次,也可能由於緩存滿由server上下文觸發。

TokuDB采用激進的緩存策略,盡量把數據保留在內存中。除了evictor線程以外,還有一個定期刷髒的checkpoint線程,缺省60每秒運行一次把內存中所有髒數據回刷到磁盤上。Checkpoint結束後,清理redo log文件。

TokuDB采用sharp checkpoint策略,checkpoint開始時刻把cachetable中所有數據塊遍曆一遍,對每個數據塊打上checkpoint_pending標記,這個過程是拿著client端exclusive鎖的,所有INSERT/DELETE操作會被阻塞。標記checkpoint_pending過程結束後,釋放exclusive鎖,server的更新請求可以繼續執行。

隨後checkpoint線程會對每個標記checkpoint_pending的髒頁進行回寫。為了減少I/O期間數據塊讀寫鎖衝突,先把數據clone一份,然後對cloned數據進行回寫;clone過程是持有讀寫鎖的write鎖,clone結束後釋放讀寫鎖,數據塊可以繼續提供讀寫服務。Cloned數據塊寫回時,持有讀寫I/O的mutex鎖,保證on-going的I/O至多隻有一個。

更新數據塊發現是checkpoint_pending並且dirty,那麼需要先把老數據寫盤。由於checkpoint是單線程,可能來不及處理這個數據塊。為此,TokuDB提供一個專門的線程池,server上下文隻要把數據clone一份,然後把回寫cloned數據的任務扔給線程池處理。

Cachetable

所有緩存在內存的數據塊按照首次訪問(cachemiss)時間順序組織成clock_list。TokuDB沒有維護LRU list,而是使用clock_list和count(可理解成age)來模擬數據塊使用頻率。

Evictor,checkpoint和cleaner線程(參見異步合並小結)都是掃描clock_list,每個線程維護自己的head記錄著下次掃描開始位置。

image.png

如上圖所示,hash中黑色連線表示bucket鏈表,藍色連線表示clock_list。Evictor,checkpoint和cleaner的header分別是m_clock_head,m_checkpoint_head和m_cleaner_head。

數據塊被訪問,count遞增(最大值15);每次evictor線程掃到數據塊count遞減,減到0整個數據塊會被evict出去。

TokuDB塊size比較大,缺省是4M;所以按照塊這個維度去做evict不是特別合理,有些partition數據比較熱需要在內存多呆一會,冷的partition可以盡早釋放。

為此,TokuDB還提供partial evict功能,數據塊被掃描時,如果count>0並且是clean的,就把冷partition釋放掉。Partial evict對中間數據塊(包含key分布信息)做了特殊處理,把partition轉成壓縮格式減少內存使用,後續訪問需要先解壓縮再使用。Partial evict對leaf數據塊的處理是:把partition釋放,後續訪問需要調用pf_callback從磁盤讀數據,讀上來的數據也是先解壓縮的。

寫優先

這裏說的寫優先是指並發讀寫數據塊時,寫操作優先級高,跟行級鎖無關。

image.png

假設用戶要讀區間[210, 256],需要從root->leaf每層做binary search,在search之前要把數據塊讀到內存並且加readlock。

如上圖所示,root(height 3)和root子數據塊(height 2)嚐試讀鎖(try_readlock)成功,但是在root的第二級子數據塊(height 1)嚐試讀鎖失敗,這個query會把root和root子數據塊(height 2)讀鎖釋放掉,退回到root重新嚐試讀鎖。

日誌

TokuDB采用WAL(Write Ahead Log),每個INSERT/DELETE/CREATE INDEX/DROP INDEX操作之前會記redo log和undo log,用於崩潰恢複和事務回滾。

TokuDB的redo log是邏輯log,每個log entry記錄一個更新事件,主要包含:
- 長度1
- log command(標識操作類型)
- lsn
- timestamp
- 事務id
- crc
- db
- key
- val
- 長度2

其中,db,key和val不是必須的,比如checkpoint就沒有這些信息。

長度1和長度2一定是相等的,記兩個長度是為了方便前向(backward)和後向(forward)掃描。

Recory過程首先前向掃描,尋找最後一個有效的checkpoint;從那個checkpoint開始後向掃描回放redo log,直至最後一個commit事務。然後把所有活躍事務abort掉,最後做一個checkpoint把數據修改同步到磁盤上。

TokuDB的undo日誌是記錄在一個單獨的文件上,undo日誌也是邏輯的,記錄的是更新的逆操作。獨立的undo日誌,避免老數據造成數據空間膨脹問題。

事務和MVCC

相對RocksDB,TokuDB最顯著的優勢就是支持完整事務,支持MVCC。

TokuDB還支持事務嵌套,可以用來實現savepoint功能,把一個大事務分割成一組小事務,小事務失敗隻要重試它自己就好了,不用回滾整個事務。

ISOLATION LEVEL

TokuDB支持隔離級別:READ UNCOMMITTED, READ COMMITTED (default), REPEATABLE READ, SERIALIZABLE。SERIALIZABLE是通過行級鎖實現的;READ COMMITTED (default),和REPEATABLE READ是通過snapshot實現。

TokuDB支持多版本,多版本數據是記錄在頁數據塊上的。每個leaf數據塊上的<key,value>二元組,key是索引的key值(其實是拚了pk的),value是MVCC數據。這與oracle和InnoDB不同,oracle的多版本是通過undo segment計算構造出來的。InnoDB MVCC實現原理與oracle近似。

事務的可見性

每個寫事務開始時都會獲得一個事務id(TokuDB記做txnid,InnoDB記做trxid)。其實,事務id是一個全局遞增的整數。所有的寫事務都會被加入到事務mgr的活躍事務列表裏麵。

所謂活躍事務就是處於執行中的事務,對於RC以上隔離界別,活躍事務都是不可見的。前麵提到過,SERIALIZABLE是通過行級鎖實現的,不必考慮可見性。

一般來說,RC可見性是語句級別的,RR可見性是事務級別的。這在TokuDB中是如何實現的呢?

每個語句執行開始都會創建一個子事務。如果是RC、RR隔離級別,還會創建snapshot。Snapshot也有活躍事務列表,RC隔離級別是複製事務mgr在語句事務開始時刻的活躍事務列表,RR隔離級別是複製事務mgr在server層事務開始時刻的活躍事務列表。

Snapshot可見性就是事務id比snapshot的事務id更小,意味著更早開始執行;但是不在snapshot活躍事務列表的事務。

GC

隨著事務提交snapshot結束,老版本數據不在被訪問需要清理,這就引入了GC的問題。

為了判斷寫事務的更新是否被其他事務訪問,TokuDB的事務mgr維護了reference_xids數組,記錄事務提交時刻,係統中處於活躍狀態snapshot個數,作用相當於reference_count。

以上描述了TokuDB如何跟蹤寫事務的引用者。那麼GC是何時執行的呢?

可以調用OPTIMIZE TABLE顯式觸發,也可以在後續訪問索引key時隱式觸發。

典型業務場景

以上介紹了TokuDB引擎內核原理,下麵我們從HybridDB for MySQL產品的角度談一下業務場景和性能。

HybridDB for MySQL設計目標是提供低成本大容量分布式數據庫服務,一體式處理OLTP和OLAP混合業務場景,提供存儲和計算能力;而存儲和計算節點在物理上是分離的,用戶可以根據業務特點定製存儲計算節點的配比,也可以單獨購買存儲和計算節點。

HybridDB for MySQL數據隻存儲一份,減少數據交換成本,同時也降低了存儲成本;所有功能集成在一個實例之中,提供統一的用戶接口,一致的數據視圖和全局統一的SQL兼容性。

HybridDB for MySQL支持數據庫分區,整體容量和性能隨分區數目增長而線性增長;用戶可先購買一個基本配置,隨業務發展後續可以購買更多的節點進行擴容。HybridDB for MySQL提供在線的擴容和縮容能力,水平擴展/收縮存儲和計算節點拓撲結構;在擴展過程中,不影響業務對外提供服務,優化數據分布算法,減少重新分布數據量;采用流式遷移,備份數據不落地。

除此之外,HybridDB for MySQL還支持高可用,複用鏈路高可用技術,采用一主多備方式實現三副本。HybridDB for MySQL複用ApsaraDB for MySQL已有技術框架,部署、升級、鏈路管理、資源管理、備份、安全、監控和日誌複用已有功能模塊,技術風險低,驗證周期短,可以說是站在巨人肩膀上的創新。

image.png

低成本大容量存儲場景

HybridDB for MySQL使用軟硬件整體方案解決大容量低成本問題。

軟件方麵,HybridDB for MySQL是分布式數據庫,擺脫單機硬件資源限製,提供橫向擴展能力,容量和性能隨節點數目增加而線性增加。存儲節點MySQL實例選擇使用TokuDB引擎,支持塊級壓縮,壓縮算法以表單位進行配置。用戶可根據業務自身特點選擇使用壓縮效果好的壓縮算法比如lzma,也可以選擇quicklz這種壓縮速度快資源消耗低的壓縮算法,也可以選擇像zstd這種壓縮效果和壓縮速度比較均衡的壓縮算法。如果選用zstd壓縮算法,線上實測的壓縮比是3~4。

硬件方麵,HybridDB for MySQL采用分層存儲解決方案,大量冷數據存儲在SATA盤上,少量溫數據存儲在ssd上,熱數據存儲在數據庫引擎的內存緩存中(TokuDB cachetable)。SATA盤和ssd上數據之間的映射關係通過bcache驅動模塊來管理,bcache可以配置成WriteBack模式(寫路徑數據寫ssd後即返回,ssd中更新數據由bcache負責同步到SATA盤上),可加速數據庫checkpoint寫盤速度;也可以配置成WriteThrough模式(寫路徑數據同時寫到ssd和SATA上,兩者都ack寫才算完成)。

持續高並發寫入場景

TokuDB采用fractal tree(中文譯作分型樹)數據結構,優化寫路徑,大部分二級索引的寫操作是異步的,寫被緩存到中間數據塊即返回。寫操作同步到葉數據塊可以通過後台cleaner線程異步完成,也可能由後續的讀操作同步完成(讀合並)。Fractal tree在前麵的內核原理部分有詳盡描述,這裏就不贅述了。

細心的朋友可能會發現,我們在異步寫前加了個前綴:大部分二級索引。那麼大部分是指那些情況呢?這裏大部分是指不需要做quickness檢查的索引,寫請求直接扔給fractal tree的msg buffer即可返回。如果二級索引包含unique索引,必須先做唯一性檢查保證不存在重複鍵值。否則,異步合並(或者讀合並)無法通知唯一性檢查失敗,也無法回滾其他索引的更新。Pk字段也有類似的唯一性語義,寫之前會去查詢pk鍵值是否已存在,順便做了root到leaf數據塊的預讀和讀合並。所以,每條新增數據執行INSERT INTO的過程不完全是異步寫。

ApsaraDB for MySQL對於日誌場景做了優化,利用INSERT IGNORE語句保證pk鍵值唯一性,並且通過把二級索引鍵值1-1映射到pk鍵值空間的方法保證二級索引唯一性,將寫操作轉換成全異步寫,大大降低了寫延遲。由於省掉唯一性檢查的讀過程,引擎在內存中緩存的數據量大大減少,緩存寫請求的數據塊受讀幹擾被釋放的可能性大大降低,進而寫路徑上發生cachetable miss的可能性降低,寫性能更加穩定。

分布式業務場景

HybridDB for MySQL同時提供單分區事務和分布式事務支持,支持跨表、跨引擎、跨數據庫、跨MySQL實例,跨存儲節點的事務。HybridDB for MySQL使用兩階段提交協議支持分布式事務,提交階段proxy作為協調者將分布式事務狀態記錄到事務元數據庫;分區事務恢複時,proxy從事務元數據庫取得分布式事務狀態,並作為協調者重新發起失敗分區的事務。

HybridDB for MySQL還可以通過判斷WHERE條件是否包含分區鍵的等值條件,決定是單分區事務還是分布式事務。如果是單分區事務,直接發送給分區MySQL實例處理。

在線擴容/縮容場景

HybridDB for MySQL通過將存儲分區無縫遷移到更多(或更少的)MySQL分區實例上實現彈性數據擴展(收縮)的功能,分區遷移完成之後proxy層更新路由信息,把請求切到新分區上,老分區上的數據會自動清理。Proxy切換路由信息時會保持連接,不影響用戶業務。

數據遷移是通過全量備份+增量備份方式實現,全量備份不落地直接流式上傳到oss。增量備份通過binlog方式同步,HybridDB for MySQL不必自行實現binlog解析模塊,而是利用ApsaraDB for MySQL優化過的複製邏輯完成增量同步,通過並行複製提升性能,並且保證數據一致性。

image.png

聚合索引提升讀性能

TokuDB支持一個表上創建多個聚合索引,以空間代價換取查詢性能,減少回pk取數據。阿裏雲ApsaraDB for MySQL在優化器上對TokuDB聚合索引做了額外支持,在cost接近時可以優先選擇聚合索引;存在多個cost接近的聚合索引,可以優先選擇與WHERE條件最匹配的聚合索引。

與單機版ApsaraDB for MySQL對比

image.png

與阿裏雲OLTP+OLAP混合方案對比

image.png

性能報告

高並發業務

壓測配置:
- 4節點,每節點8-core,32G,12000 iops,ssd盤

image.png

高吞吐業務

壓測配置:
- 8節點,每節點16-core,48G,12000 iops,ssd盤

image.png

最後,HybridDB for MySQL目前處於快速發展階段,正在承接阿裏集團內外各種日誌和分析報表業務。歡迎大家使用,歡迎多提寶貴意見!

最後更新:2017-07-21 09:02:58

  上一篇:go  MSSQL · 實現分析 · Extend Event實現審計日誌對SQL Server性能影響
  下一篇:go  AliSQL · 特性介紹 · 支持 Invisible Indexes