ClickHouse複製表同步機制剖析 0 術語解釋 1 哪些操作會引發數據同步? 2 ZooKeeper的結構和作用 3 負責數據同步的tasks 4 總結

ReplicatedMergeTree是ClickHouse最常用的表引擎之一,該引擎和MergeTree一樣都繼承自MergeTreeData, 和MergeTree共享相同的底層存儲層實現。ReplicatedMergeTree區別於MergeTree引擎的地方在於其實現了多副本數據存儲,並藉助zookeeper進行多副本之間的數據同步。

本文針對ReplicatedMergeTree引擎如何實現副本間數據同步,結合部分源代碼(20.10版本)做一個簡單剖析。

0 術語解釋

data part : clickhouse中的數據組織單位,一次INSERT會生成一個或多個data part, 一個data part裏的數據存放在同一個文件夾裏。

mutation : 對一個或多個data parts的內容數據的修改操作,最典型的觸發mutation的操作是:ALTER TABLE ... DELETE 和 ALTER TABLE ... UPDATE(詳見mutations)。

1 哪些操作會引發數據同步?

這裏說的數據同步包括了元數據和內容數據的同步。在ClickHouse中,數據插入(INSERT),元數據變更(ALTER),內容數據變更(MUTATION),合併(MERGE)等操作都會引發數據同步。

1.1 INSERT

clickhouse執行ReplicatedMergeTree表的插入操作時,會將數據以data part爲單位寫入到磁盤/內存裏,每個data part寫入完成後都會做一個commit操作, 這個commit操作會生成一個GET_PART類型的log entry並上傳到zookeeper中供其他副本拉取同步,詳見源碼

由此可見,INSERT語句引發的是data part級別的內容數據的同步。

1.2 ALTER METADATA

這裏之所以稱爲'ALTER METADATA'而不直接稱'ALTER',是因爲,在ClickHouse中update和delete也是通過ALTER語句實現的,即ALTER TABLE ... DELETE 和 ALTER TABLE ... UPDATE.  ALTER TABLE ... DELETE 和 ALTER TABLE ... UPDATE語句觸發的是mutations,所以它們被歸爲mutation的範疇。

這裏說的是會對錶的元數據進行修改的ALTER語句。ClickHouse將ReplicatedMergeTree表的元數據信息存放在zookeeper和本地文件中,ALTER語句會觸發對這兩個地方的元數據信息進行修改。注意,在zookeeper上,表的元數據信息會在表級別和副本級別的znodes(即下文說的zookeeper_path和replica_path)下都進行存儲。

alter語句首先會修改掉zookeeper上zookeeper_path下的元數據,然後將修改後的元數據包裝生成一個ALTER_METADATA類型的ReplicatedMergeTreeLogEntryData對象,並將其上傳到zookeeper上,供所有副本(包括當前副本)拉取更新。詳見源碼

注意,這裏說的”會對錶的元數據進行修改的ALTER語句“ 有一些同時也會觸發mutation,下文將進一步介紹。

1.3 MERGE

merge操作用於將若干個data parts合併爲單個data part,ClickHouse會在後臺選擇需要做merge的data parts,然後創建一個MERGE_PARTS類型的ReplicatedMergeTreeLogEntryData對象,並將其上傳到zookeeper上,供所有副本(包括當前副本)拉取同步。詳見源碼StorageReplicatedMergeTree::mergeSelectingTask() .

1.4 MUTATION

mutation是對data parts數據的修改操作,一個mutation可能包含了對很多個data parts的修改。

除了ALTER TABLE ... DELETE 和 ALTER TABLE ... UPDATE,以下類型的ALTER語句也會觸發mutation :

    1. DROP_COLUMN

    2. DROP_INDEX

    3. RENAME_COLUMN

    4. 非meta only的MODIFY_COLUMN

對於會觸發mutation的ALTER語句,ClickHouse會生成一個ReplicatedMergeTreeMutationEntry對象,並將其上傳到zookeeper,供所有副本(包括當前副本)拉取同步。

詳見源碼StorageReplicatedMergeTree::alter 和 AlterCommand::isRequireMutationStage.

2 ZooKeeper的結構和作用

zookeeper是ReplicateMergeTree表副本之間進行數據同步的橋樑,存儲了複製表的各類元數據信息。這裏介紹幾個和數據同步相關的主要信息。

2.1 zookeeper_path和replica_path

一張ReplicatedMergeTree表的所有副本在zookeeper上共享一個znode路徑,我們稱之爲zookeeper_path,這個路徑在建表時指定,我們對該路徑的命名規範是:

        /clickhouse/tables/{layer}-{shard}/<table_name>

其中layer和shard都是macro,各個副本節點取值可能不同。

zookeeper_path是表級別的,其下有一個replicas節點,內部包含了各個副本的元信息節點,即:

        /clickhouse/tables/{layer}-{shard}/<table_name>/replicas/<replica>

我們稱副本的元信息節點爲replica_path.

2.2 表的元信息(metadata, columns)

在zookeeper_path和replica_path下都保存了表的metadata和columns信息,元數據變更時會先修改zookeeper_path下的metadata和columns,然後會異步地將更新同步到replica_path下。

除此之外,replica_path下面還會有一個metadata_version用於記錄當前元數據信息的版本,這個值就是zookeeper_path下的metadata節點的dataVersion.

zookeeper_path下的metadata和columns信息的更新詳見源碼

replica_path下的metadata, columns和metadata_version的更新見源碼

2.3 同步日誌和副本隊列(log, queue)

2.3.1 同步日誌(log)

log位於zookeeper_path下面,用於保存所有副本間的數據同步日誌(稱爲log entry),ClickHouse支持的LogEntry類型有:

從各個副本觸發的數據變更操作都會上傳對應的log entry到log節點下,比如:

        1. 在一個副本上執行alter table ... add column操作會上傳一個ALTER_METADATA類型的log entry到log節點下。

        2. 在一個副本上執行insert into ...操作會上傳一個GET_PART類型的log entry到log節點下。

zookeeper_path/log下的log entry在zookeeper上的znode命名規範是log-seqNum, 其中seqNum爲創建sequential znode生成的自增序號,如log-0000000001。

2.3.2 副本隊列 (queue)

queue位於replica_path下面,保存了當前副本需要同步的log entries. queue裏面的log entries是從log中拉取的(由下文說的queue updating task負責拉取),被所有副本都拉取過的log entry就可以從log中刪除掉, 刪除操作由後臺清理線程執行

replica_path下還有一個log_pointer節點,保存了當前副本從log拉取到的最大log entry id + 1,即下一個需要拉取的log entry的id.

replica_path/queue下的entry在zookeeper上的znode命名規範是queue-seqNum, 其中seqNum爲創建sequential znode生成的自增序號,如queue-0000000001。

2.4 數據變更(mutations)

在zookeeper_path下面有一個mutations節點,ALTER操作觸發的mutation操作都會先封裝成mutation entry 後被上傳到這個節點。mutation entry保存了mutation操作的相關信息,包括:

    a. source replica : 觸發mutation的副本節點。

    b. mutation commands : 需要apply到data parts上的mutation指令。

    c. block numbers :這是一個partition_id -> block_number的映射,保存了每個partition的mutation version。mutation只會被apply到partition中block number小於mutation version的blocks(因爲merge的關係,每個data part可能包含一個或多個blocks,並且data part包含的block numbers不一定是連續的)。

    d. alter version : mutation操作對應的metadata version,只有ALTER MODIFY/DROP 操作觸發的mutation纔會有非-1的值,對於ALTER TABLE ... DELETE 和 ALTER TABLE ... UPDATE操作觸發的mutation操作,這個值都爲-1。這個值是爲了將ALTER MODIFY/DROP的ALTER METADATA操作和MUTATION操作關聯起來。

mutation entry的結構詳見ReplicatedMergeTreeMutationEntry

一個mutation entry會被轉換成若干個MUTATE_PART類型的log entry被投放到同步日誌(zookeeper_path/log)中,下文介紹merge/mutation selecting task時會介紹其轉換過程。

另外,在replica_path下面有一個mutation_pointer節點,用於記錄副本上最後完成的mutation的id(也就是znode name).

3 負責數據同步的tasks

每個副本節點都有一系列的負責數據同步的任務,這些任務會與zookeeper交互,獲取最新的同步任務,然後在本地內存中維護任務隊列並藉助任務池調度執行,執行完成後會更新zookeer上的對應狀態。下面我們逐一介紹各類同步任務。

3.1 queue updating task

queue updating task負責當前副本上同步日誌(log entry)隊列的更新。

ReplicatedMergeTree表對應的StoreageReplicatedMergeTree類中有一個queue_updating_task對象,它是由後臺調度池(BackgroundSchedulePool)調度執行的任務,其執行的函數是StorageReplicatedMergeTree::queueUpdatingTask().

3.1.1 工作內容

queue updating task做的事情包括:

1. 從zookeeper拉取zookeeper_path/log下的所有log entries,並從replica_path/log_pointer獲取當前副本的log_pointer (即下一個需要拉取的log entry的id)。

2. 根據log_pointer在log entries中定位到需要拉取的entries,如果log_pointer爲空,則從log entries中最小的entry開始拉取。

3. 將需要拉取的log entries都同步到replica_path/queue下面,這裏的同步就是以選定的log entries的內容爲節點內容,一個一個地在replica_path/queue下創建新的sequential znode. 注意,這裏創建的znode名稱並不會和zookeeper_path/log下的一致。

4. 更新replica_path/log_pointer的內容爲拉取到的log entries中最大的entry id + 1.

5. 將拉取到的log entries插入到內存中的同步任務隊列裏,即插入到ReplicatedMergeTreeQueue::queue

6. 觸發queue executing task(下文會介紹)執行。

這些步驟的實現細節請見源碼ReplicatedMergeTreeQueue::pullLogsToQueue.

3.1.2 觸發時機

queue updating task被觸發執行的時機有:

1. 每張ReplicatedMergeTree表都有一個restarting thread,這個thread會被週期性調度執行,默認間隔是1分鐘(由參數zookeeper_session_expiration_check_period控制)。當複製表首次被加載或者zookeeper session過期後被重新加載時,restarting thread會激活並調度queue updating task執行。

2. queue updating task每次被執行時, 會在從zookeeper_path/log獲取log entries時添加一個watch callback (詳見源碼),這個callback會調度執行queue updating task, 所以每當zookeeper_path/log下有新的log entry生成時,queue updating task就會被調度執行

3. 如果queue updating task在拉取和更新隊列時發生異常,在異常處理中會再次調度執行queue updating task,調度執行的間隔時間是QUEUE_UPDATE_ERROR_SLEEP_MS(1秒)。

3.1.3 爲什麼既要拉取到replica_path/queue又要拉取到本地內存中?

1. 拉取到replica_path/queue是爲了方便記錄每個副本有哪些是待執行的entries,執行完成的直接從replica_path/queue裏刪除即可。同時,當某個副本重啓或者某張ReplicatedMergeTree表被detach後再attach時,ClickHouse可以從replica_path/queue快速加載待執行的entries (詳見源碼ReplicatedMergeTreeQueue::load)。

2. 拉取到本地內存相當於做了一個緩存,避免每次遍歷待執行entries都需要從zookeeper拉取,提升了性能,也可以降低zookeeper的負載。

3.2 queue executing task

queue updating task把同步日誌(log entries)拉取到本地內存後,我們需要一個任務去執行這些log entries. 這個任務就是queue executing task. 

queue executing task在StoreageReplicatedMergeTree類中被實現爲一個通過BackgroundProcessingPool調度執行的task,即 StorageReplicatedMergeTree::queue_task_handle.

queue_task_handle執行的函數是StorageReplicatedMergeTree::queueTask.

下面介紹queueTask函數的主要執行步驟。

3.2.1 選擇要處理的log entry

從ReplicatedMergeTreeQueue::queue中選擇一個log entry, 選擇邏輯如下(實現細節詳見ReplicatedMergeTreeQueue::shouldExecuteLogEntry):

1. 對於GET_PART類型的log entry,如果它生成的data part被某個正在執行的log entry的resulting data parts所包含,則當前entry此輪不被選擇。

2. 對於MERGE_PARTS類型的log entry:

    2.1 如果它生成的data part被某個正在執行的log entry的resulting data parts所包含,則當前entry此輪不被選擇。

    2.2 如果它的source data parts中存在某個data part還處於被生成過程中,則當前entry此輪不被選擇。

    2.3 如果它是TTL類型merge(包括TTL_DELETE, TTL_RECOMPRESS,詳見MergeType)且在運行的TTL類型merges的個數(全局值,不是針對當前表的)>= max_number_of_merges_with_ttl_in_pool (配置),則當前entry此輪不被選擇。

    2.4 如果source data parts的總數據量大於max_source_parts_size(max_source_parts_size是運行時確定的變量,確定邏輯詳見源碼)且background pool中沒有足夠的free threads做large merges (如果有足夠free threads則不考慮source data parts的數據量是否超過max_source_parts_size,詳見源碼1源碼2),則當前entry此輪不被選擇。

3. 對於MUTATE_PART類型的log entry:

    3.1 如果它生成的data part被某個正在執行的log entry的resulting data parts所包含,則當前entry此輪不被選擇。

    3.2 如果它的source data parts中存在某個data part還處於被生成過程中,則當前entry此輪不被選擇。

    3.3 如果source data parts的總數據量大於max_source_parts_size(max_source_parts_size是運行時確定的變量,確定邏輯詳見源碼),則當前entry此輪不被選擇。

    3.4 如果它是某個alter modify/drop query的一部分,則需要按alter語句的發生順序依次執行,如果存在比它早的alter語句尚未執行完成,則當前entry此輪不被選擇。詳見源碼

4. 對於ALTER_METADATA類型的log entry, 需要按照alter語句的發生順序依次執行,如果存在比它早的alter語句尚未執行完成,則當前entry此輪不被選擇。詳見源碼

5. 對於DROP_RANGE 和 REPLACE_RANGE類型的log entry,因爲它們會等待生成指定範圍內的data parts的log entries執行完成,爲了避免死鎖,如果已經有DROP_RANGE 和 REPLACE_RANGE類型的log entry正在執行,則當前entry此輪不被選擇。詳見源碼

6. 對於MERGE_PARTS 和 MUTATE_PART類型的log entry,如果merge/mutation/TTL類型merge被cancel了(如果被cancel則後續不能提交對應類別的操作,在運行的該類操作也會拋異常),則對應操作的log entry也不會被選中執行。詳見源碼1源碼2.

如果上述條件都不滿足,則該log entry被選中執行。每次queue executing task執行只會選擇一個log entry進行處理

3.2.2 處理選中的log entry

選中log entry後,StorageReplicatedMergeTree::queueTask函數會調用ReplicatedMergeTreeQueue::processEntry對log entry進行處理。processEntry函數的第三個參數是用於執行log entry的函數,此處傳入的是StorageReplicatedMergeTree::executeLogEntry.

3.2.2.1 execute log entry

log entry的處理邏輯如下:

1. 對於DROP_RANGE和REPLACE_RANGE類型的log entry, 調用對應的StorageReplicatedMergeTree::executeDropRange 和 StorageReplicatedMergeTree::executeReplaceRange函數進行處理,這裏對executeDropRange的執行過程做簡要介紹(另一個讀者可自行分析源碼):

    1.1 從zookeeper上的replica_path/queue節點和內存中移出所有會生成對應range(這裏的range是指定partition內指定的block number的範圍)中的data part的log entries (這裏會刪除zookeeper上replica_path/queue節點下的entry),如果有正在運行中的,則等待其運行完成。詳見源碼ReplicatedMergeTreeQueue::removePartProducingOpsInRange

    1.2 移除當前working set(即MergeTreeData::data_parts_by_info)中屬於對應range的data parts(詳見MergeTreeData::removePartsInRangeFromWorkingSet)。注意,這裏並沒有從磁盤刪除這些data parts,只是修改了內存中的一些狀態,比如,將data part的state改成IMergeTreeDataPart::State::Outdated(詳見MergeTreeData::removePartsFromWorkingSet),真正從磁盤刪除data parts的操作是通過喚醒cleanup線程完成的。

    1.3 從zookeeper上的replica_path/parts節點中刪除在1.2中被移除的data parts,這裏如果刪除失敗則會重試,默認重試5次。

    1.4 喚醒cleanup線程,從磁盤清理掉已被移除的data parts。

2. 對於GET_PART類型的log entry,處理邏輯如下:

    2.1 判斷目標data part在當前副本是否已經存在或被已存在的data part所包含,這裏不僅會檢查active data parts,也會檢查處於MergeTreeDataPartState::PreCommitted狀態的data parts,詳見源碼

    2.2 判斷目標data part在zookeeper的replica_path/parts節點下是否已經存在。

    2.3 如果2.1和2.2的判斷結果都是已存在,則直接跳過當前log entry,不做處理(此時executeLogEntry返回true,表示處理成功)。

    2.4 否則,進一步判斷目標data part是否是某次失敗的write with quorum操作的resulting data part,判斷方法是查看zookeeper上zookeeper_path/quorum/failed_parts節點下是否存在目標data part。如果存在,則和2.3一樣,直接跳過當前log entry,不做處理(此時executeLogEntry返回true,表示處理成功)。

    2.5 否則,從其他有目標data part的副本節點去拉取目標data part。拉取是調用StorageReplicatedMergeTree::executeFetch實現的。具體拉取過程暫不剖析,後續data fetcher相關文章中再介紹。

3. 對於MERGE_PARTS類型的log entry,處理邏輯如下:

    3.1 同GET_PART的2.1 ~ 2.4操作。

    3.2 調用StorageReplicatedMergeTree::tryExecuteMerge函數,該函數會根據各種情況/狀態及相關配置而決定是執行該merge操作還是建議從其他副本節點拉取目標data part。具體merge邏輯暫不剖析,後續merge相關文章中再介紹。

    3.3 如果tryExecuteMerge返回false,即建議從其他副本拉取data part,則調用StorageReplicatedMergeTree::executeFetch進行拉取。

4. 對於MUTATE_PART類型的log entry,處理邏輯如下:

    4.1 同GET_PART的2.1 ~ 2.4操作。

    4.2 調用StorageReplicatedMergeTree::tryExecutePartMutation函數,和StorageReplicatedMergeTree::tryExecuteMerge一樣,該函數也會根據各種情況/狀態及相關配置而決定是執行該mutate操作還是建議從其他副本節點拉取目標data part。具體mutate過程此處不做剖析,讀者可自行分析源碼。

    4.3 如果tryExecutePartMutation返回false,即建議從其他副本拉取data part,則調用StorageReplicatedMergeTree::executeFetch進行拉取。

5. 對於ALTER_METADATA類型的log entry, 調用StorageReplicatedMergeTree::executeMetadataAlter函數進行處理,處理邏輯如下:

    5.1 從該log entry中解析出要更新的metadata和columns信息。

    5.2 更新zookeeper上的replica_path/metadatareplica_path/columns節點的內容爲5.1中的metadata和columns信息。

    5.3 更新當前副本本地的元信息(對於ordinary database下的表,會修改本地保存的sql文件),詳見StorageReplicatedMergeTree::setTableStructure

3.2.2.2 remove processed entry

executeLogEntry執行完成後會返回一個bool值,表示是否執行成功。如果執行成功,則processEntry會調用ReplicatedMergeTreeQueue::removeProcessedEntry從內存中的隊列和zookeeper上的replica_path/queue節點中移除剛執行完成的log entry。

3.3 mutations updating task

mutations updating task做的事情是:基於zookeeper上的zookeeper_path/mutations節點更新內存中的mutation狀態數據。這裏說的狀態數據主要是mutations_by_znode 和 mutations_by_partition

其中,mutations_by_znode保存了mutation id(也就是zookeeper_path/mutations下的節點名稱)到MutationStatus對象的映射,MutationStatus中保存了單個mutation的狀態信息,包括entry, parts_to_do, is_done等。

mutations_by_partition的類型是std::unordered_map<String, std::map<Int64, MutationStatus *>>, 保存的是Partition -> (block_number -> MutationStatus)的雙層映射關係,這樣我們通過partition id和data part包含的block number就可以找到某個data part需要執行的mutation操作。

注意,mutations_by_partition中的第二層key(ie. block_number)就是mutation version,每個data part的MergeTreePartInfo::getDataVersion(如果data part被mutate過或者包含mutated part,則返回的是對應的mutation version,否則返回改data part中最小的block number)會和mutation version做比較,如果小於這個version,則說明data part需要執行這個mutation。

mutations updating task是一個通過BackgroundSchedulePool調度執行的task,即StorageReplicatedMergeTree::mutations_updating_task。mutations_updating_task執行的函數是StorageReplicatedMergeTree::mutationsUpdatingTask,其主要工作是通過調用ReplicatedMergeTreeQueue::updateMutations函數完成的。

下面我們看看mutations updating task的主要工作內容有哪些。

3.3.1 工作內容

1. 從zookeeper_path/mutations節點拉取得到所有的mutation entries的id (即對應的znode name)。

2. 從mutations_by_znode和mutations_by_partition中移除掉zookeeper_path/mutations中不存在的mutation,出現這種mutation的原因可能是:

    2.1 被KILL MUTATION語句殺掉的。

    2.2 已經指向完成後被mutations finalizing task (下文會介紹)移除掉的。

3. 將zookeeper_path/mutations中新的mutations(即在mutations_by_znode和mutations_by_partition中不存在的)加入到mutations_by_znode和mutations_by_partition中。

4. 對每個新增的mutation,填充其parts_to_do (即需要mutate的data parts),填充的步驟如下:

    4.1 遍歷mutation entry中的block_numbers,對於其中的每個partition, 從當前副本當前表的現存data parts(ReplicatedMergeTreeQueue::current_parts)中找到屬於這個partition並且MergeTreePartInfo::getDataVersion小於對應mutation version的data parts(詳見getPartNamesToMutate),將這些data parts添加到parts_to_do中。

    4.2 遍歷內存中的同步任務隊列(ReplicatedMergeTreeQueue::queue)中的每個log entry,在每個log entry的resulting data parts中找到partition存在於mutation entry的block_numbers中且MergeTreePartInfo::getDataVersion小於對應mutation version的data parts,將這些data parts添加到parts_to_do中。

5. 如果步驟3中獲取到的新的mutations不爲空,則調度merge/mutation selecting task(下文會介紹)執行。

6. 如果在步驟4中發現某些新增的mutation的parts_to_do爲空(表名這個mutation可能可以終止了),則調度mutations finalizing task執行。

3.3.2 觸發時機

mutations updating task被觸發執行的時機和queue updating task基本一致:

1. 和queue updating task一樣,當複製表首次被加載或者zookeeper session過期後被重新加載時,restarting thread會激活並調度mutations updating task執行。

2. mutations updating task每次被執行時, 會在從zookeeper_path/mutations獲取mutation entries時添加一個watch callback (詳見源碼),這個callback會調度執行mutations updating task, 所以每當zookeeper_path/mutations下有新的mutations entry生成時,mutations updating task就會被調度執行

3. 如果mutations updating task在拉取和更新mutations時發生異常,在異常處理中會再次調度執行mutations updating task,調度執行的間隔時間是QUEUE_UPDATE_ERROR_SLEEP_MS(1秒)。

3.4 merge/mutation selecting task

上文中曾提到,zookeeper_path/muations中的一個mutation entry會被轉換成多個MUTATE_PART類型的log entry並投放到zookeeper_path/log中。這個轉換工作是由merge/mutation select task完成的。顧名思義,這個task會負責merge和mutation任務的選擇和提交。

merge/mutation selecting task是一個通過BackgroundSchedulePool調度執行的task,即StorageReplicatedMergeTree::merge_selecting_task。注意,這裏的命名雖然是merge_selecting_task, 但該task涵蓋了merge和mutation任務的選擇和提交。

merge_selecting_task執行的函數是StorageRreplicatedMergeTree::mergeSelectingTask,我們看看merge/mutate selecting task做了哪些工作。

3.4.1 工作內容

1. 統計同步任務隊列(ReplicatedMergeTreeQueue::queue)中MERGE_PARTS和MUTATE_PART類型的log entry的數量,詳見源碼

2. 如果步驟1中統計得到的merge + mutation的任務總數大於或等於max_replicated_merges_in_queue(配置),則ClickHouse認爲隊列中merge和mutation任務已經夠多了,不會選擇和提交新的merge/mutation任務,跳至步驟5,否則繼續執行步驟3。

3. 根據配置和一些runtime狀態(比如,磁盤空閒空間)計算出max_source_parts_size_for_merge 和 max_source_part_size_for_mutation,即最大可合併的source parts大小 和 最大可mutate的source part大小。這兩個值的計算邏輯見MergeTreeDataMergerMutator::getMaxSourcePartsSizeForMerge 和 MergeTreeDataMergerMutator::getMaxSourcePartSizeForMutation。相關配置有:

        a. max_bytes_to_merge_at_min_space_in_pool

        b. max_bytes_to_merge_at_max_space_in_pool

        c. number_of_free_entries_in_pool_to_lower_max_size_of_merge

        d. number_of_free_entries_in_pool_to_execute_mutation

4. 選擇是提交MERGE_PARTS任務,還是MUTATE_PART任務,或者都不執行:

    4.1 如果max_source_parts_size_for_merge爲0(即沒空間做merge了),或者沒有需要merge且可以merge的data parts(選取過程詳見MergeTreeDataMergerMutator::selectPartsToMerge),則不提交MERGE_PARTS任務,跳至步驟4.3,否則繼續執行步驟4.2。

    4.2 調用StorageReplicatedMergeTree::createLogEntryToMergeParts構建並提交一個MERGE_PARTS log entry到zookeeper_path/log中。然後跳至步驟5,否則繼續執行步驟4.3。

    4.3 如果滿足以下條件之一,則不提交MUTATE_PART,跳至步驟5,否則繼續執行步驟4.4。

        a. max_source_part_size_for_mutation爲0。 

        b. mutations_by_partition的size爲0,即沒有需要執行的mutation操作。

        c. 同步任務隊列中的MUTATE_PART任務數大於等於max_replicated_mutations_in_queue(配置)。

    4.4 遍歷當前表的committed data parts(MergeTreeData::getDataPartsVector),選擇一個滿足以下全部條件的data part 。如果沒有滿足條件的data part則跳至步驟5,否則繼續執行步驟4.5。

        a. size on disk <= max_source_part_size_for_mutation

        b. 在mutations_by_partition中存在data part所在partition的mutation操作,並且對應的mutation version高於data part當前的mutation version。詳見ReplicatedMergeTreeMergePredicate::getDesiredMutationVersion和 ReplicatedMergeTreeQueue::getCurrentMutationVersionImpl

    4.5 在步驟4.4中選中一個data part後,調用StorageReplicatedMergeTree::createLogEntryToMutatePart構建並提交一個MUTATE_PART log entry到zookeeper_path/log中。

5. 如果上述步驟沒有成功提交log entry,則調度merge_selecting_task在MERGE_SELECTING_SLEEP_MS(5秒)後執行。如果成功提交了,則立刻調度merge_selecting_task執行。

說明:

1. 每次merge_selecting_task的調用執行,只會提交一個MERGE_PARTS或MUTATE_PART任務到zookeeper_path/log中。

2. 根據MergeTreeDataMergerMutator::getMaxSourcePartSizeForMutation的實現可知,只有在空閒線程充足的情況下,纔會考慮提交MUTATE_PART。這是爲了留更多線程給MERGE_PARTS。而且,在步驟4中做選擇時,也是先判斷是否可以提交MERGE_PARTS,如果有可提交的MERE_PARTS,則不會考慮MUTATE_PART。 由此可見,在ClickHouse中,MERGE_PARTS任務的優先級是高於MUTATE_PART任務的。

3. merge/mutation selecting task只在leader副本上執行(常見所有副本都是leader的情況)。

3.4.2 觸發時機

merge/mutation selecting task被觸發執行的時機有:

1. 當前副本贏得leader election後,激活並調度merge/mutation selecting task執行。

2. merge/mutation selecting task在執行完成後,如果結果成功,則立刻調度merge/mutation selecting task執行,如果失敗,則調度merge/mutation selecting task延遲MERGE_SELECTING_SLEEP_MS(5秒)後執行。

3. ReplicatedMergeTree表的插入操作在完成commit data part後, 會觸發merge/mutation selecting task調度執行,詳見源碼

4. 在mutations updating task發現新的mutation並完成狀態更新後,會觸發merge/mutation selecting task調度執行。

5. queue executing task在成功執行MERG_PARTS log entry後,會觸發merge/mutation selecting task調度執行,詳見StorageReplicatedMergeTree::tryExecuteMerge

6. queue executing task在成功執行MUTATE_PART log entry後,會觸發merge/mutation selecting task調度執行,詳見StorageReplicatedMergeTree::tryExecutePartMutation

3.5 mutations finalizing task

merge/mutation selecting task將zookeeper_path/mutations下的mutation entry轉換成一個個MUTATE_PART log entry並投放到zookeeper_path/log下後,queue executing task會執行這些MUTATE_PART任務。當一個mutation entry相關的所有MUTATE_PART任務都完成了,那麼這個mutation entry就可以被結束了。

mutations finalizing task就是用來結束mutation entry的任務。mutations finalizing task是一個通過BackgroundSchedulePool調度執行的任務,即StorageReplicatedMergeTree::mutations_finalizing_task,其調用的函數是StorageReplicatedMergeTree::mutationsFinalizingTask,該函數的主要功能是通過調用ReplicatedMergeTreeQueue::tryFinalizeMutations實現的。

下面介紹一下mutations finalizing task的工作內容。

3.5.1 工作內容

1. 遍歷mutations_by_znode中的所有mutations,對於is_done標誌位false的mutations:

    1.1 如果znode小於等於mutation_pointer(mutation已經完成),則執行以下操作:

        a. is_done置true。

        b. 清空parts_to_do。

        c. 調用ReplicatedMergeTreeAltersSequence::finishDataAlter更新alter的狀態信息(如果對應的metadata alter也完成了,則會從alter sequence移除該alter)。

    1.2 如果znode大於mutation_pointer,且parts_to_do爲空(mutation可能已完成),則將該mutation放入candidates中。

2. 對於每個candidate,調用ReplicatedMergeTreeMergePredicate::isMutationFinished判斷其是否已經結束,判斷邏輯是:

    2.1 如果mutation涉及的blocks還有在committing階段的,則判定mutation爲unfinished。

    2.2 如果現存data parts 或者 同步任務隊列(ReplicatedMergeTreeQueue::queue)的resulting data parts中還有需要執行改mutation的data parts (詳見getPartNamesToMutate),則判定mutation爲unfinished。

    2.3 如果2.1和2.2都不成立,則判定mutation爲finished。

3. 如果存在finished candidates,則:

    3.1 更新mutation_pointer : 將zookeeper上的replica_path/mutation_pointer和內存裏的ReplicatedMergeTreeQueue::mutation_pointer都設置爲finished candidates中最大的znode name。

    3.2 更新每個finished candidate的內存狀態(MutationStatus):

        a. is_done置true。

        b. 調用ReplicatedMergeTreeAltersSequence::finishDataAlter更新alter的狀態信息。

說明:對於mutation finalizing的邏輯尚未完全理解,這裏只是根據源碼列出執行過程,後續會在mutation相關文章做進一步解釋。

3.5.2 觸發時機

1. 當複製表首次被加載或者zookeeper session過期後被重新加載時,restarting thread會激活並調度mutations finalizing task執行。

2. 在更新mutations相關狀態時,如果發現some_mutations_are_probably_done(即parts_to_do爲空),則調度mutations finalizing task執行。

3. 如果mutations finalizing task在finalize mutations發生異常 或者 成功地finalize了一個或多個mutations,那麼,則調度mutations finalizing task延遲MUTATIONS_FINALIZING_SLEEP_MS(1秒)執行。

4. 如果mutations finalizing task順利運行後沒有finalize任何mutation,則調度mutations finalizing task延遲MUTATIONS_FINALIZING_IDLE_SLEEP_MS(5秒)執行。

4 總結

1. 複製表的數據同步包括元數據同步和內容數據同步。

2. 引發數據同步任務的操作包括INSERT,ALTER METADATA,MERGE,MUTATION等。

3. zookeeper在複製表的數據同步中起到橋樑作用,znode路徑分爲表級別zookeeper_path和表副本級別的replica_path, 關鍵節點包括log,log_pointer, queue, mutations, mutation_pointer等。

4. 各個副本節點都運行着負責數據同步的各類任務,包括queue updating task, queue executing task, mutations updating task, merge/mutation selecting task和mutations finalizing task。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章