分散式資料庫跨版本升級資料遷移方案

語言: CN / TW / HK

動機

向後相容性對資料庫至關重要,但會給程式碼庫帶來相當大的複雜性。 在其他條件相同的情況下,最好將向後相容性邏輯與分散式資料庫軟體系統所固有的複雜性隔離開來,尤其是諸如元資料等各種系統資料,需要一個整體的升級遷移方案來統一處理。

 

短期設計

1. 因為在一般情況下處理升級遷移是一個非常廣泛的問題,包括許多潛在的遷移型別,我們選擇首先處理最簡單的情況,即向後相容的遷移,同時為可以支援向後不相容的更復雜的方案敞開大門。

2. 在短期內,該方案中的遷移鉤子將由名稱和工作函式組成。在穩定狀態下,程式碼中有一個遷移名稱的有序列表,按新增時間排序。

3. 當一個節點啟動時,它會檢查所有已知的遷移是否已經執行(可以使用下面描述的 /SystemVersion 鍵來限制涉及的工作量)。如果沒有,它會通過遷移協調器執行它們。

4. 當叢集需要趕上多個遷移時,遷移的有序列表可用於對遷移進行排序。

5. 遷移協調器首先獲取並維護遷移租約,以確保一次只執行一個租約。在不同節點進行遷移時啟動並需要遷移的其他節點將不得不阻塞,直到租約被釋放(或在節點故障的情況下到期)。然後對於每個丟失的遷移,遷移協調器執行其工作函式並將完成記錄寫入 kv 條目/SystemVersion

6. 每個工作函式都必須是冪等的(以防遷移協調器崩潰)並且與之前版本的云溪資料庫相容,這些版本可以在叢集中的其他節點上執行。後者的限制將在長期設計中放寬。

 

示例

簡單的遷移,如 CREATE TABLE IF NOT EXISTS system.jobs (...),可以通過一個鉤子完成並立即使用。要新增這樣的遷移,需要建立一個名為“CREATE system.jobs”的新遷移,其中包含一個建立新系統表並將其新增到遷移列表末尾的函式。每當包含它的資料庫節點的版本首次加入現有叢集時,它就會自動執行。

 root 使用者新增到system.users 的示例也可以通過單個節點掛遷移鉤子來完成,這意味著它可以類似地新增到遷移掛鉤列表中並在啟動時執行,而無需關心分散式資料庫叢集中其他活躍節點使用的是什麼版本。 

 

長期設計

雖然我們最直接的需求實際上並不需要向後不相容的更改,但我們希望相容更多的場景。對於具有預遷移版本的此類鉤子,我們必須部署額外的工具以確保安全遷移。

選項1:需要管理員干預

支援不向後相容遷移的第一個選項是引入新的 CLI 命令,使資料庫管理員能夠控制遷移發生的時間。例如:./znbase cluster-upgrade

• 列出已在叢集上執行的所有遷移

• 列出可用的遷移、它們所依賴的遷移,以及執行每個遷移是安全的資料庫版本

• 執行管理員指定的單個遷移

• 執行滿足遷移依賴關係的所有可用遷移(請注意,如果叢集中的所有節點都不滿足遷移的最低版本,可能會很危險)

在升級到新版本之前,管理員將在發行說明中查詢他們想要執行的版本,並確保他們已經運行了所有必需的遷移。如果沒有,則需要在升級之前執行此操作。如果所需版本的所有升級在叢集的當前版本中不可用,則可能需要先升級到支援遷移的中間版本,以便可以執行遷移。如果資料庫節點啟動,並且它加入的叢集尚未執行該節點版本所需的所有遷移,則該節點將退出並顯示相應的錯誤訊息。這種方法使管理員能夠完全控制潛在的破壞性遷移,但代價是增加了額外的手動工作。從好的方面來說,它更好地支援回滾,在使用新版本啟動節點時,系統會自動進行不向後相容的更改。
CLI 工具將是針對生產場景升級資料庫版本的推薦方法,主要適用於小型叢集和本地開發。如果需要,單節點叢集將能夠在其正常啟動過程中進行遷移。此外,未指定最低版本的遷移仍然可以在啟動時執行(如在短期設計中),因為缺少最低版本可以假定為意味著它們是向後相容的。

選項 2:自動執行所有遷移

手動干預的主要替代方法是嘗試自動為他們完成使用者操作的工作。這將為不太熟悉工具的使用者提供最佳的使用者體驗,他們更關心自己的時間和精力,而不是完全控制。與向後相容的情況不同,我們不一定能在新版本的第一個節點啟動時執行所有已知的遷移,因為某些遷移可能會以與叢集中其他節點不相容的方式修改叢集狀態。我們永遠無法自動執行遷移,除非我們知道叢集中的所有節點都處於足夠新的版本。我們至少可以通過兩種不同的方式確保這一點:

• 開始跟蹤整個叢集的兩個節點版本資訊,並新增一些能夠充分理解我們的語義版本控制方案的程式碼,以確定哪些節點版本支援哪些遷移。

• 開始跟蹤每個節點知道的尚未執行的遷移。然後,一旦所有節點都知道遷移,就可以執行遷移。

 

短期設計程式碼示例

Sqlmigrations包定義了跨版本升級資料相容的方法,backwardCompatibleMigrations是在啟動時執行的遷移列表,在每個節點啟動時按順序執行,完成版本升級。

var backwardCompatibleMigrations = []migrationDescriptor{...        name:                "create system.comment table",        workFn:              createCommentTable,        includedInBootstrap: true,        newDescriptorIDs:    staticIDs(keys.CommentsTableID),    },...}

1. 建立系統表

建立一些版本升級新增的系統表

func createCommentTable(ctx context.Context, r runner) error {    return createSystemTable(ctx, r, sqlbase.CommentsTable)}
func createSnapshotTable(ctx context.Context, r runner) error {    return createSystemTable(ctx, r, sqlbase.SnapshotsTable)}

2. 建立系統schema

func createDefaultdbSchema(ctx context.Context, r runner) error {    return createSystemSchema(ctx, r, sqlbase.DefaultdbPublicSchema)}
func createPostgresSchema(ctx context.Context, r runner) error {    return createSystemSchema(ctx, r, sqlbase.PostgresPublicSchema)}

3. 在root下執行一組命令(重試有限次)

包括新增角色、使用者、叢集金鑰以及配置叢集引數。

func addAdminRole(ctx context.Context, r runner) error {    const upsertAdminStmt = `          UPSERT INTO system.users (username, "hashedPassword", "isRole") VALUES ($1, '', true)          `    return runStmtAsRootWithRetry(ctx, r, "addAdminRole", upsertAdminStmt, sqlbase.AdminRole)}
func addRootUser(ctx context.Context, r runner) error {    const upsertRootStmt = `            UPSERT INTO system.users (username, "hashedPassword", "isRole") VALUES ($1, '', false)            `    return runStmtAsRootWithRetry(ctx, r, "addRootUser", upsertRootStmt, security.RootUser)}