2.0解析系列 | OceanBase 2.0 之 索引實時生效

螞蟻金服OceanBase資料庫團隊發表於2019-01-16

OB君:本文是 “OceanBase 2.0 技術解析系列” 的第七篇文章。今天我們來聊聊資料的持續可用,說說2.0的索引實時生效功能。更多精彩歡迎關注OceanBase公眾號持續訂閱本系列內容!

2.0解析系列 | OceanBase 2.0 之 索引實時生效

引言

隨著業務的快速發展,其對資料庫的資料訪問規則是不斷變化的,在資料庫中新建索引來加速業務查詢是很常見的需求。

網際網路的業務規模和發展速度對資料庫的索引構建提出了更高的要求,一方面,在海量的業務規模下,非故障導致的停機是不可接受的,這意味著索引構建的同時,正常業務的讀寫請求不能被影響;另一方面,業務的快速發展和迭代,對索引構建的效率也有著更高的要求,索引更快速的生效,能加速新業務的開發和迭代過程。

傳統單機關聯式資料庫經過幾十年的發展,逐漸實現了索引實時生效功能,這些資料庫主要解決的問題是在索引構建的時候,避免長時間的鎖表影響正常的業務請求。而分散式資料庫由於其分散式的特性,在實現索引實時生效時,面臨和單機資料庫不同的問題.

OceanBase 1.x中通過把索引構建放在合併流程中避免了這些問題,但並沒有做到索引實時生效,從使用者執行建立索引表語句到索引表生效需要經過一或兩次合併,OceanBase 2.0中解決了其中的問題,索引構建不再與合併耦合,使用者執行建立索引後,能立即進入索引構建流程,較大地縮短了索引構建生效時間。

本文首先介紹了關係型資料庫索引構建的發展現狀,接著描述了OceanBase 1.x中索引構建遇到的問題,最後分析了OceanBase 2.0的索引構建設計,並給出瞭解決這些問題的方法。

索引構建的現狀

根據架構不同,關係型資料庫分為單機資料庫和分散式資料庫。不同的架構下,索引構建的方案有所不同,本節將分別介紹業界單機資料庫和分散式資料庫的索引構建方案。

單機資料庫

我們以MySQL為例,來描述單機資料庫的索引構建方案。MySQL從5.6開始支援索引實時生效,首先,執行完建立索引語句之後,新事務中新索引表的資料會寫入到Row Log中,與此同時,會等待未往索引表中寫入過資料的事務都結束。當所有的事務都結束後,開始索引構建流程,主要是處理兩部分資料,一部分是等事務結束之後的主錶快照點資料,此部分資料在索引表中是不存在的,需要通過主表資料構建出來,另一部分資料是記錄在Row Log中的資料,需要應用到最終的索引表中。構建完成之後,將索引設定成可讀寫狀態,進而優化器能使用該索引來優化使用者的查詢。

MySQL索引構建的特點如下:

  1. 是為In Place Update的儲存引擎設計的,為了避免索引構建和使用者事務對索引表更新的併發問題,索引構建過程中的更新資料會記錄到Row Log的特殊儲存中,這部分資料需要重新寫入到索引表中,寫入的時候會有一定的加鎖時間;
  2. 基於快照點的構建流程是序列的,面對大資料量場景下效能可能存在不足;
  3. 後設資料只有單版本,在更新相關後設資料時,會加鎖。

分散式資料庫

和單機資料庫不同的是,分散式資料庫的資料分佈和請求執行可能是分佈在多臺機器上的,導致索引構建方案也有所不同。本節將介紹Google研發的分散式資料庫F1的索引構建方案。

2.0解析系列 | OceanBase 2.0 之 索引實時生效

如上圖,Google F1採用的是儲存計算分離的架構,架構上總共分為三層,最底層是分散式Key-Value儲存引擎,第二層是無狀態的計算層F1 Server,第三層為代理層,應用程式通過代理層和F1互動。

在此種架構下,事務執行時,有可能出現不同的SQL語句在不同的F1 Server執行的情況,那麼不同的語句可能使用了不同版本的關係型後設資料(為了設計和實現簡單,F1只允許系統中同時出現兩種不同版本的後設資料),這會導致如下問題。

假設後設資料版本S1 < S2,且S2比S1多了一張索引表,有如下執行過程。

  1. 在S2版本的F1 Server上執行INSERT語句,由於S2版本包括索引表,因此會生成索引表相關的KV記錄;
  2. 在S1版本的F1 Server上執行DELETE語句,且和1中INSERT語句使用相同的主鍵,由於S1版本不包括索引表,因此索引表相關的KV記錄不會被刪除。

當上述事務執行完成後,索引表將會有多餘的中間資料,導致資料表和索引表的資料不一致。

F1為了解決這個問題,引入了中間狀態和最終狀態,其中中間狀態包括,delete-only和write-only,delete-only表示索引表只能被delete和update,而write-only表示索引表只能被insert、delete和update。最終狀態包括absent和public,分別表示索引表不存在和索引表生效。在上述出問題的場景中,新增索引表的變更經過了absent->write-only->public的過程,由於absent狀態時,索引表不存在,導致無法刪除索引表的資料,因此,F1將新增索引的流程變成了absent->delete-only->write-only->public,這樣就能保證索引構建完成後,資料和主表保持一致。

總體來看,Google F1的索引構建方案有如下特點:

F1 Server中最多存在兩個不同版本的後設資料,這意味著如果有機器在一個後設資料更新租約時間內沒有重新整理到新版本後設資料,那麼F1 Server必須要自動退出以保證這個約束,使得這種方案比較適用於計算儲存分離的架構,另外,為了避免F1 Server頻繁因網路抖動主動退出,後設資料更新租約時間一般是分鐘級別,因此,對於資料量較小的表格構建索引,也需要分鐘級別才能生效。

OceanBase 索引構建方案

本節先簡單介紹下OceanBase的整體架構、儲存引擎特點以及索引表的寫入流程,接著討論OceanBase 1.x索引構建中碰到的問題,最後描述了OceanBase 2.0中的索引構建方案。

整體架構

OceanBase的一個叢集通常由多個zone組成,一個zone由一個或多個ObServer組成的,每個ObServer都具有計算和儲存的功能。在ObServer中有一個較為特殊,負責總控服務的節點稱為RootService,負責管理叢集的後設資料和路由資訊,其中,後設資料是按照多版本方式管理的。OceanBase按照分割槽的方式管理資料,一張表包含一個或多個分割槽,每個分割槽的資料會儲存在多個zone中,每個zone都是一份完整的資料拷貝(副本)。每個分割槽的副本中會有一個Leader副本,負責處理該分割槽的讀寫請求。

2.0解析系列 | OceanBase 2.0 之 索引實時生效

儲存引擎

OceanBase的儲存引擎是按照Log Structured Merge Tree(LSM Tree)方式組織的,分為基線資料和增量資料兩部分。基線資料儲存在基線SSTable中,增量資料儲存在Memtable和轉儲SSTable中。基線SSTable按照版本遞增的方式來管理,某個版本的基線SSTable一旦生成後就變成只讀狀態,修改的資料會儲存在Memtable中,當達到一定記憶體閾值後會先進行minor compaction(轉儲)轉換成轉儲SSTable,當轉儲SSTable達到一定數量時,會將基線SSTable和轉儲SSTable做major compaction(合併),生成更高版本的基線SSTable。

在SSTable和Memtable中資料都是按照表的主鍵排序的,例如,有一張資料表test,包含列c1、c2、c3、c4和c5,其中主鍵為c1,那麼test的資料是按照列c1的升序方式儲存的;如果在資料表test上建立一個索引index,假設包含列c3和c2,那麼index是按照主鍵c3、c2和c1的升序方式儲存的,其中儲存c1列是為了方便回表查詢。

索引表的寫入流程

在介紹索引表寫入流程之前,先來看看OceanBase索引表的分割槽管理方式。OceanBase中索引表分為區域性索引和全域性索引。

區域性索引是指分割槽規則和主表相同的索引,由於分割槽規則相同,區域性索引和主表共用相應的分割槽,因此,區域性索引分割槽和主表是在同一臺機器上的。

全域性索引是指分割槽規則和主表不同的索引,分割槽規則的不同導致了全域性索引和主表無法共用分割槽,而分割槽是OceanBase管理的基本單位,分割槽不同意味著全域性索引和主表的分割槽是可能不在同一臺機器上的。

索引表的寫入通常是由主表驅動的,對主表的寫入操作一般分為INSERT、DELETE和UPDATE,以上面的test主表和index索引表為例,對於INSERT,假如對test主表執行INSERT INTO test values(a,b,c,d,e),會根據索引表的列生成索引行(c,b,a),寫入到索引表中;對於DELETE,假如對test主表執行DELETE FROM test where c1 = `a`,會通過主表中的資料,獲取索引列c3和c2,假設值為c和b,拼成完整的索引行(c,b,a)並刪除索引表中對應的行;對於UPDATE,假如對test主表執行UPDATE SET c3 = c` where c1 = `a`,首先會先獲取主表中的資料,獲取索引列c3和c2的資料,假設值為c和b,拼成完整的行(c,b,a)並刪除索引表中對應的行,然後生成新的行(c`,b,a)並寫入索引表。對於唯一索引,更新時會檢查寫入的資料是否滿足唯一性約束,具體地,需要檢查索引表已有的資料中是否存在將要寫入的行,如果存在,則會報唯一性衝突。

OceanBase 1.x 索引構建面臨的問題

在OceanBase 1.x中,使用者執行建立索引語句後,會等到叢集下次合併時開始構建,構建過程中先等待主表合併到新版本後,再基於主表的最新版本的基線SSTable資料,構建出索引表的資料。OceanBase 1.x索引構建有如下問題:

  1. OceanBase 1.x缺少指定快照點讀取資料的功能,索引構建依賴合併的快照點;
  2. OceanBase 1.x中只有區域性索引,能通過同臺機器的主表資料構建索引表的資料,而OceanBase 2.0中新增了全域性索引,構建過程變成了分散式排序,如何以較小代價實現分散式排序呢?
  3. OceanBase 1.x中主表和索引表SSTable版本號是統一管理的,兩者的版本號需要統一推進,而如果索引實時構建的話,可能在索引構建的同時,主表資料已經更新了多個版本,OceanBase 1.x的SSTable版本管理方法無法滿足該需求。

OceanBase 2.0 索引構建方案

本節通過描述整個索引構建的流程,來介紹索引構建的方案設計以及如何解決相關問題的。總體上,OceanBase 2.0中,索引構建分為準備、構建、拷貝和收尾共四個階段。

1)準備階段

在索引構建準備階段主要做了兩件事情:

  1. 生成索引表的後設資料資訊,其中索引表設定成只寫狀態,根據LSM Tree儲存引擎的特點,索引表構建期間的資料直接寫入到索引表的Memtable中,帶來的好處是這部分資料直接成為索引表的一部分,後面無須再將這部分資料插入到索引表中,並且可以複用前面小節描述的索引表寫入流程的程式碼;
  2. 等待之前未往索引表插入過資料的事務結束,當所有的事務都結束後,獲取構建快照點,構建階段將基於此快照點掃描主表資料,並寫入到索引表基線SSTable中,而使用者事務產生的資料寫入到Memtable中,這樣就無須處理索引表構建和使用者事務同時對索引表寫入導致的併發更新問題。

步驟1中涉及到後設資料的變更,OceanBase中採用多版本方式管理後設資料,為了避免分散式環境下不同ObServer後設資料版本不同帶來的問題,採用如下方案解決。

  1. ObProxy將同一個事務的請求傳送給同一個中控ObServer處理,這樣可以保證事務的不同語句在中控ObServer看到的後設資料版本是遞增的,從而避免語句級別的後設資料版本回退導致資料不一致的問題;
  2. 同一條語句可能會涉及到多個ObServer,可能會出現多個ObServer的後設資料版本不同的情況,如果出現和中控ObServer的後設資料版本不一致時,則進行語句重試。

準備階段最重要的輸出為快照點,在傳統單機資料庫中,獲取快照點比較容易的,一般獲取系統當前時間戳即可,但在分散式資料庫中,由於每臺機器的時間戳不可能是完全一致的,因此,不能簡單的獲取某個機器的時間戳作為快照點。OceanBase 2.0中實現了租戶級全域性時間戳,每個租戶提供一個授時服務,每個租戶的事務版本號都通過授時服務來獲得,同樣,索引構建準備階段的快照點也通過授時服務來獲得。

2)構建階段

構建階段的目標是基於主錶快照點掃描出索引表所需資料,並按照索引列排序規則生成索引表基線SSTable資料。

在OceanBase 1.x中,Memtable轉換成轉儲SSTable時,多版本的資料會被歸併成單版本的,因此,資料一旦發生轉儲,且快照點落在轉儲SSTable範圍內,則無法讀取快照點資料。為了能夠基於某個快照點掃描主表的資料,OceanBase 2.0中,Memtable轉成轉儲SSTable時,會把Memtable中所有資料及其版本號都記錄下來,從而掃描資料時,能根據快照點和資料的版本號,讀取到所需版本的資料。

排序根據索引型別不同,執行過程也不相同,對於區域性索引,索引表分割槽和主表相同,因此,構建階段的資料流動僅僅是在本機,而對於全域性索引,索引表分割槽和主表不同,一個索引表分割槽的資料通常來自主表的多個分割槽,而這些分割槽可能是在不同ObServer上的,是一個分散式排序過程,整個構建過程描述成了一個SQL的plan。

和麵向OLTP的分散式執行不同,索引構建的分散式排序更關注容災,在機器故障等情況下,能以較小的代價快速恢復,OceanBase 2.0的SQL執行框架中支援構建過程的中間結果持久化,構建過程出現機器當機時,只需要選擇其他機器重新執行故障機器相關的任務。同時,為了加速構建過程,將資料掃描和排序都做了並行化,充分利用磁碟和CPU的並行能力。

構建階段可能會遇到合併,此時主表的基線SSTable的版本會增加,在索引表構建好之後,其版本號是落後於主表的。為了能描述這種場景,我們對基線SSTable管理功能做了重構,每個SSTable單獨管理版本號,能夠保證索引構建時,即使索引表落後主表多個版本,也能構建成功。

在生產環境,主表資料量通常比較大,索引構建往往是比較耗時的操作,為了避免索引構建對正常使用者請求產生影響,OceanBase 2.0中會基於使用者IO請求來限速,當使用者請求的IO響應時間超過一定閾值時,會自動限制構建過程中IOPS。

3)拷貝階段

鑑於索引構建比較耗時,OceanBase 2.0中只在單副本上構建索引表,其他副本從構建好的副本拷貝資料。在單副本構建期間寫入索引表的資料會通過一致性演算法同步到索引表多個副本上,因此,拷貝階段僅僅需要將構建好的基線SSTable拷貝到其他的副本上。和構建一樣,我們對拷貝過程也做了並行化,每個分割槽會按照資料量切分成多個任務,由多個執行緒批量地執行任務。為了避免並行拷貝過度的消耗資源,會對拷貝階段整體網路頻寬作限制,同時也提供了拷貝的並行度控制,方便運維。

4)收尾階段

收尾階段分為兩步,分別是資料校驗和索引生效。資料校驗根據索引型別的不同,會做不同的操作,對於普通索引,會根據構建階段計算的主表的列校驗和索引表的列校驗和進行比對,保證構建出來的索引表的資料和主表的資料是一致的;對於唯一索引,因為構建過程中也有可能寫入資料,而構建過程中索引表的基線資料還未構建完成,唯一性校驗可能不完整,因此,需要在索引表基線資料構建和拷貝完成後,對唯一索引做唯一性校驗,同時為了保證索引構建的資料正確性,我們採用將主表和索引表資料進行校驗,既驗證了資料正確性,也驗證了索引是否滿足唯一性。

當資料校驗通過時,將索引表設定成可讀寫狀態,進而SQL優化器能使用新的索引來加速查詢,當資料校驗不通過時(一般是唯一索引唯一性約束不滿足),會將索引設定成不可用狀態。

總結

OceanBase的索引實時生效包括兩層含義,第一是索引構建過程從合併中解耦,使用者觸發後能立即進行構建流程,第二是通過專門為LSM Tree儲存引擎優化的構建流程,構建流程的並行化,單副本構建,副本拷貝的並行化,以及容災時快速恢復等技術手段,有效地加快了索引生效的速度。

OceanBase的索引構建時能對佔用的資源做控制,減少對正常使用者請求的影響。通過完備的資料校驗,保證了構建完成的索引表的資料正確性。另外,當前的索引構建方案對計算儲存一體化以及計算儲存分離的架構都是適用的。

參考文獻

1. Innodb Online DDL Operations

2. Online, Asynchronous Schema Change in F1

OceanBase技術交流群

— 想了解更多OceanBase 2.0新特性?

— 想與螞蟻金服OceanBase的一線技術專家深入交流?

掃描下方二維碼聯絡小編,快速加入OceanBase技術交流群!

2.0解析系列 | OceanBase 2.0 之 索引實時生效

2.0解析系列文章

相關文章