RFC-0005:Blobfs 快照

RFC-0005:Blobfs 快照
狀態已遭拒
區域
  • 儲存空間
說明

支援在升級期間使用 Blobfs 快照。

Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2020-09-06
審查日期 (年-月-日)2020-09-21

摘要

這份 RFC 說明瞭簡單的快照機制,可提高升級程序中的錯誤復原能力。變更 Fuchsia 音量管理員 (FVM),可讓 Blobfs 區隔的快照,在升級期間的任何階段皆可還原。

提振精神

在撰寫本文時,升級失敗導致 Blobfs 分割區損毀,可能會讓裝置處於難以復原的狀態。目前的復原分區無法還原處於此狀態的裝置,因此在這種情況下,唯一支援的方式是透過引導程式,使用不利於使用者的程序。

快照機制可降低我們處於此狀態的風險。

設計

基本概念是在 FVM 中支援原始快照機制,讓升級期間出現兩個分區,但允許在分區之間共用資料。

目前,FVM 是簡單的音量管理器,可將切片從任意切片對齊邏輯偏移,對應至基礎裝置上的特定偏移,並將不同分區的對應項目分開。

Blobfs 包含下列獨立區域:

地區
超級區塊
分配點陣圖
索引節點
日誌
資料

為了支援這項提案,我們可以在 FVM 中允許不同的切片類型1。這些類型會套用至切片的範圍

類型 說明
A/B 區塊 這是具有備用副本的區塊範圍。
A/B 位圖2 這是切片的範圍,其中包含代表共用資料範圍中分配情形的點陣圖備用副本。
分享的資料 這是切片的範圍,其分配方式由 A/B 位圖範圍管理。
共用 這會是兩個分區之間共用的區域,但一次只能由一個分區寫入該區域。

有了這些切片類型,FVM 就能呈現兩個顯示範圍的 A/B 變化版本。因此,回到 Blobfs 區域,我們會看到:

地區 類型
超級區塊 A/B 區塊
分配點陣圖 A/B 點陣圖
索引節點 A/B 區塊
日誌 共用3
資料 分享的資料

在大多數情況下,系統只會啟用其中一個分割區,因此系統會顯示目前的樣貌。

在升級期間,系統可以啟用第二個分割區,此時第一個分割區會變成「鎖定」狀態,且無法再寫入,但仍可繼續讀取。您可以準備第二個分區,方法可能與目前相同,但在整個升級期間,您隨時可以選擇回復第一個分區,且保證不會受到影響。

對於 A/B 範圍,您可以輕鬆查看第一個分區的資料如何保留;第二個分區不會看到第一個分區的資料。對於日誌,共用區域 — 只有可寫入的區段才能寫入,也就是第二個區段。對於共用資料區域,位元遮罩會指出可寫入哪些區塊。任何標示為由第一個分區使用的區塊,在兩個分區中都會顯示為唯讀。

為了方便實施這項配置,第二個區段也必須能夠讀取替代位元資料,以便瞭解可分配哪些區塊。因此,可在邏輯位址空間中,以目前未使用的偏移量呈現。假設提案是所有替代 A/B 範圍都會顯示在相同的偏移量,但會設定頂層位元 (唯讀)。

下圖說明各個區隔的顯示方式:

分區排列方式

圖 1:分區配置。

注意:

  • 這可為我們帶來部分彈性,但並非全部,這與簡單的 A/B 區隔方法相同。
  • 我們可以保留目前的增量更新方式 (即只更新已變更的 Blob),但代價是無法獲得可預測的版面配置。在使用者版本中,我們可以選擇完全重寫所有 blob,但仍會受到分割的影響。
  • 這會使 FVM 變得更複雜。

新版升級流程

您必須修改升級流程,以利快照互動。圖 2 顯示目前的流程,圖 3 則顯示建議的替代方案。新的 API 和互動會以顏色標示。

目前的 OTA

圖 2:目前的升級實作方式 (高階)

建議的 OTA

圖 3:建議的升級實作方式 (概略)

新的 FVM 作業

您必須實作並整合幾項新的 FVM 作業,才能在軟體提交 (SWD) 堆疊中執行這些作業。這些 API 可用於驅動狀態機器 (圖 4),最終在分區之間切換系統。

快照狀態機器

圖 4:快照狀態機器。

TakeSnapshot

將有效分割區的中繼資料快照儲存至先前已清除的備用分割區 (請參閱「DeleteSnapshot」)。使用中的分區會變成唯讀,且所有後續寫入作業現在都必須寫入至非使用中分區。

  • FVM 會將有效分區設為唯讀。
    • 必須清除待處理的記事本項目。
  • FVM 會建立非活動分區。
  • FVM 會從活動分割區複製至非活動分割區的中繼資料。

在這個多步驟程序期間,寫入新的 Blob 是不可能的,而且必須放棄一半已寫入的 Blob,但這不應造成限制,因為負責寫入 Blob 的元件應與負責要求快照的元件相同。

CancelSnapshot

取消使用 TakeSnapshot 建立的快照填充作業,清除非活動分割區並允許建立其他快照。

  • 此時,必須關閉所有與非活動分割區的讀取連線。
  • FVM 會刪除閒置的分區。活動分區將再次可寫入。

SetWritablePartition

切換可寫入的分區。

  • 此時必須將日誌刷新 (所有待處理的作業都必須完成)。上圖中的 fsync 呼叫可協助完成這項作業,但理想情況下,日誌沖洗作業應與此作業的其他部分一併以交易方式完成,這樣就不會有新的寫入作業「偷偷溜進」系統。

由於 TakeSnapshot 會自動切換可寫分區,因此這個 API 可能很少使用,但如果需要返回並讓有效分區可寫 (例如為了垃圾收集未使用的 blob),則可以使用這個 API。

SetBootPartition

變更可啟動的分區。

一般而言,可啟動的分區會根據哪個 ZBI 插槽處於作用中而變更,但也可以個別切換可啟動的分區。這個選項很少使用。

DeleteSnapshot

將備用分區標示為已清除。FVM 可以選擇刪除其中的結構描述資料。

ListSnapshotPartitions

針對已設定為可建立快照的分割區,查詢 FVM。

QuerySnapshotPartition

向 FVM 查詢支援快照功能的分區資訊。

  • 識別 A/B 分割區的狀態,例如哪個分割區處於活動狀態。

故障模式

系統可能會在狀態機器中描述的任何狀態中發生故障。本節說明系統發生故障時應採取的適當行動。

請注意,失敗可能是自願的 (系統主動決定取消進行中的更新),也可能是非自願的 (系統因外部因素而失敗,例如電力中斷)。必須考量這兩種情況。

請注意,blobfs 有日誌記錄機制,可在修改過程中發生非自願失敗時,防止毀損中繼資料。您不需要額外處理,即可讓 blobfs 在修改期間對非自願性失敗狀況具備抗性。

在必要時,應將 FVM 中的任何新中繼資料作業設為交易,以免在修改期間因非自願的失敗而導致 FVM 損毀。

狀態 1:拍攝快照前

在這個狀態下,失敗處理作業不需要任何變更;行為與目前的系統行為相同。

狀態 2:在 TakeSnapshot 後、重新啟動前

  • 如果是自願性失敗,您可以叫用 CancelSnapshot API 來刪除非活動分區,並將系統還原為狀態 1。
  • 如果是非預期的失敗,系統可以決定在重新上線後直接中止更新 (透過叫用 CancelSnapshot),或是嘗試恢復更新。

狀態 3:重新啟動後,但尚未執行 TakeSnapshot

等同於狀態 1。

支援暫時性套件

暫時性套件是指未納入特定系統版本的基礎套件組合。

此提案對暫時性套件幾乎沒有額外限制。下文「新建立檔案的路由」一節說明如何在 OTA 期間,在任何狀態下繼續支援暫時性套件,但有一個例外狀況,如果在準備新基本分割區時中斷快照,則必須刪除暫時性套件。

由於在更新開始前寫入至有效分割區的暫時性套件會在呼叫 TakeSnapshot 時複製至非活動分割區,因此暫時性套件可能會在更新期間保留,且在那之後,所有暫時性套件都會寫入新分割區,而系統可讀取及寫入新分割區 (且在更新完成後會成為新的有效分割區)。

新建檔案的路由

決定新檔案安裝位置時,有三種情況需要考量。為了簡化討論,我們假設區隔 A 處於活動狀態,區隔 B 處於非活動狀態。

案例 1:在 TakeSnapshot 之前

  • 基本套件:未撰寫。
  • 暫時性套件:寫入分區 A。

案例 2:在 TakeSnapshot 之後,但在重新啟動之前

  • 基本套件:寫入分區 B。
  • 暫時性套件:寫入分區 B。請注意,如果在嘗試下一個快照之前中斷快照,這些套件就會遭到刪除。

案例 3:重新掛載後 (注意:等同於「Before TakeSnapshot」)

  • 基本套件:未撰寫。
  • 暫時性套件:寫入分區 B。

FVM 中繼資料的變更

FVM 的中繼資料結構如下:

地區 說明
超級區塊 預期結果。
分區資料表 一個項目陣列,每個分區一個,包含分區名稱、類型等。
配量分配 陣列項目,每個可分配的切片各有一項,用於指出該切片已分配至哪個分區 (如果有的話),以及該分區內的邏輯偏移量。

為了方便提案,您需要額外提供中繼資料,以便記錄範圍的切片類型,因此需要儲存類似下列的內容:

enum class uint32_t SliceType {
  kNormal,
  kAB,
  kABBitmap,
  kSharedData,
  kShared,
};

struct {
  uint32_t slice_offset;  // Offset within the partition
  SliceType slice_type;   // The slice type
} extents[8];

這項中繼資料可新增至每個分割區項目。建議您採用更好的方法,新增包含此中繼資料的獨立區隔 (即快照中繼資料區隔)。這裡不會討論此中繼資料的精確位置和結構,而是將其留作實作細節。

使用這個結構時,Blobfs 的範圍會是:

[
  /* super block: */       { 0,                    SliceType::kAB },
  /* allocation bitmap: */ { 0x10000 / kSliceSize, SliceType::kABBitmap },
  /* inodes: */            { 0x20000 / kSliceSize, SliceType::kAB },
  /* journal: */           { 0x30000 / kSliceSize, SliceType::kShared },
  /* data: */              { 0x40000 / kSliceSize, SliceType::kSharedData }
]

系統需要一些狀態,指出兩個分區中哪一個目前可寫入、兩個分區是否都處於啟用狀態 (或只有一個),以及哪個分區應視為可啟動分區4

除了需要分配其他偏移位置的切片外,您不需要對切片分配方式進行任何變更。

超級區塊可能需要進行其他小幅變更 (例如版本升級)。

支援 blobfs 格式演進

這項提案可大幅簡化 blobfs 格式演進,因為每次更新時,可完全刪除及重新建立替代分區,而這項作業的成本不高。

不過,根據此提案改進 blobfs 格式時,仍有兩項挑戰需要處理。

  • 區塊配置對應表無法變更,因為這是兩個活動/非活動分區之間共用的結構。(考量到分配圖表的簡單程度,這似乎是可接受的做法)。
  • 活動分區無法覆寫由非活動分區分配的任何區塊。不過,這很容易處理:如果某個區域內的部分資料需要變更內部格式,系統可以在 TakeSnapshot 呼叫期間,直接分配新的區域並移動資料。

實作

實作這項功能需要進行下列變更,這些變更大致取決於先前的變更:

  1. 變更 FVM 和分割區設定。
  2. Blobfs 配置的變更。
  3. 對前期引導程式碼進行變更。
  4. 升級程序的變更,以便使用新的 API。

大部分的變更都必須符合 #1 和 #4 的要求。#1 會涉及磁碟上的格式變更,且在全新安裝的情況下,系統會支援遷移作業。您也必須重新安裝才能還原。這是涉及最多風險的重要步驟,但請注意,只有格式變更需要就位;任何使用新 FVM 中繼資料的程式碼都可以在後續階段保持休眠狀態。

其他步驟則可在不需要重新安裝的情況下完成,且可同樣還原。

成效

這對效能應該不會造成太大影響。升級期間可能會因為快照作業產生成本而受到輕微影響,但相較於其他升級活動,這可能不具影響力。在其他時間,則不應有任何變化。

空間需求

您需要為 Blobfs 區域的額外副本保留空間,包括超級區塊、Inode 表格和 Bitmap。具體值取決於裝置的設定,但與 Blobfs 可用的總空間相比,應相對較小。

安全性考量

無。

隱私權注意事項

無。

測試

我們會採用標準 Fuchsia 測試方法。現有的系統測試應已測試升級功能。這些測試將擴大範圍,納入故意毀損新 Blobfs 區隔的測試,以及故意毀損快照區隔的測試。

說明文件

我們會在 Fuchsia > 概念 > 檔案系統架構 一節中說明 FVM 的新架構和功能。

缺點、替代方案和未知事項

完整的 A/B 提案

我們考慮了完整的 A/B 提案。雖然這個提案在概念上很簡單,但有幾個重大缺點:

  • 每個分區只能使用可用磁碟空間的 50%。
    • 目前這項限制對系統更新而言是軟性限制,因為系統更新的預算只使用可用的 Blobfs 空間的 50%,但 A/B 版本提案會將這項限制設為硬性限制。
    • 工程建構作業已超過 50% 的預算,因此不支援會修改許多檔案的升級作業。工程師非常依賴這項功能,可進行小幅的漸進式更新;因此,中斷這項工作流程是不可行的。
  • 沒有在分區之間共用檔案的機制,因此每次更新都會重寫每個檔案。
    • 這表示會增加閃存裝置的磨損,並導致升級速度變慢。每項更新基本上都是最大更新。

完整的 FVM 快照功能

開發完整的 FVM 快照功能會遇到一些挑戰。傳統的快照機制通常具有動態性質,也就是說,需要在寫入作業完成時更新中繼資料。此外,FVM 的切片大小 (目前為 1 MiB,即將變更為 32 KiB) 與 Blobfs 的區塊大小 (8 KiB) 不符。解決這個問題會大幅增加 FVM 的複雜度,而且也可能會出現空間不足的極端情況。或許可以開發具有靜態對應項目的配置,但不久之後,您就會提出與這裡所述相去不遠的提案。總而言之,這項做法實作起來可能需要更長的時間,且可能會帶來一些嚴重的負面影響 (寫入放大、複雜度),而且不提供我們認為近期需要的明確效益。完整快照功能可能會在長期上有所助益,因此 FVM 中精確的結構定義應可提供擴充空間,以便在未來支援這類用途。

既有技術與參考資料

可靠且具備復原能力的升級作業是常見問題,通常可透過以下方式解決:

  1. A/B 版本:保留功能相當的版本,並視需要在兩者之間切換。簡單,但會佔用空間。
  2. A/R 副本:保留復原副本,這是僅支援還原軟體的簡化版本。較為複雜,空間需求較低,使用者體驗略為下降。
  3. A/B/R:#1 和 #2 的組合。
  4. A+ 快照:大多數情況下,只有一個可用的副本。在升級時,請擷取 A 的快照,並將更新套用至快照的差異。隨時提供回復快照的選項。通常複雜,但具彈性。

作者認為 Android 使用 #3,iOS 和 macOS 則使用 #2 和 #4。

本 RFC 是 #4 的簡化版本。

撤銷的理由

我們在這個 RFC 的開發工作進行了數個月後,決定停止這項 RFC 的工作。這項決定受到多項因素影響,主要包括:

  • FVM 程式碼集的技術債導致進度緩慢,且變更風險高。缺乏測試涵蓋率、長期潛在的錯誤,以及對 FVM 格式版面配置的廣泛假設 (由於 FVM 格式缺乏封裝) 是主要的阻礙。

  • 團隊對 FVM 的文件不足,也無法充分理解。關於 FVM 的組織知識隨著時間流逝而逐漸減少,而我們最初假設 FVM 是建構這項功能的相對簡單且合適的場所,這項假設並不正確。

  • 推出這項功能的影響比原先預期的更大,因為這項功能需要 FVM 主要格式修訂版本,而這項修訂版本被判定會對工程作業造成極大干擾 (因為這需要重新映像裝置,且也需要推出 Zedboot 版本,而這本身就是極具干擾性的作業)。

由於開發這項功能的風險極高,且很可能會影響不斷成長的 Fuchsia 開發人員社群,因此我們不再繼續開發這項功能。相反地,儲存空間團隊將專注於改善測試涵蓋率和自動化,以減輕此 RFC 動機中所述的風險,並繼續改寫 FVM 主機工具 (造成意外複雜性的重大來源),以及評估減少對特定 FVM/Zedboot 版本依賴的可能性,以便在需要變更其中任何一個版本時,降低對開發人員的影響。


  1. 請注意,這些額外的切片類型不一定需要加入 FVM 格式;表達此中繼資料的方式有很多種,具體格式則留給實作細節。 

  2. 為了簡化程序,我們可以省略 A/B 位圖和共用資料類型,並信任 Blobfs 的運作方式正確無誤。不過,在 FVM 中加入這項功能,可為 Blobfs 實作提供額外的錯誤防護機制。您也可以選擇保留空間,並在稍後再新增。 

  3. 日誌的區域可供共用。在啟用第二個分割區時,系統可以將日誌刷出,並且不再需要用於鎖定的唯讀分割區;這項作業只需要用於避免可寫分割區出現不一致的情況。 

  4. 這個可啟動的狀態可以儲存在其他位置,並在繫結時傳遞至 FVM,但最好還是直接將這個狀態儲存在 FVM 中。