RFC-0207:離線 blob 壓縮

RFC-0207:離線 Blob 壓縮
狀態已接受
區域
  • 軟體交付
說明

下載壓縮的 Blob。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2022-11-02
審查日期 (年-月-日)2023-02-01

摘要

在 Fuchsia 目前的設計中,解析套件時,系統會透過網路擷取未壓縮的 Blob,然後在寫入磁碟前壓縮 Blob。這項 RFC 建議採用替代方案,可直接擷取壓縮的 Blob 構件。這項功能可減少頻寬,並省去裝置上耗費運算資源的壓縮作業。

提振精神

pkg-resolver 會從 Blob 伺服器擷取未壓縮的 Blob,並直接寫入 Blobfs。Blobfs 會即時壓縮資料,因此需要大量 CPU 和記憶體。

離線壓縮功能會先預先壓縮 Blob,再擷取 Blob,並以適合直接寫入磁碟的格式傳送至 Blobfs。這樣做可減少套件作業解析和系統更新期間的 CPU、記憶體和網路用量。因此更新速度會更快、記憶體不足 (OOM) 率會降低,且伺服器頻寬費用也會減少。

此外,由於裝置不需要壓縮大型測試二進位檔,開發人員的工作效率也會提升。啟動大型二進位檔的測試時,速度應該會快上許多。

利害關係人

協助人員:

abarth@google.com

審查者:

  • jsankey@google.com (SWD)

  • etryzelaar@google.com (SWD)

  • mlindsay@google.com (本機儲存空間)

  • csuter@google.com (本機儲存空間)

  • bcastell@google.com (本機儲存空間)

  • marvinpaul@google.com (伺服器基礎架構)

  • aaronwood@google.com (產品組裝)

  • amituttam@google.com (PM)

  • dschuyler@google.com (SDK 交付)

  • atyfto@google.com (基礎架構)

社交:

軟體交付團隊在設計討論中,討論了離線壓縮和差異更新。相關利害關係人已審查 RFC 的初稿。

策略遊戲

改善 OTA 時間和頻寬的主要策略有兩種:

  • 離線壓縮:Blobfs 會動態壓縮磁碟上的 Blob。不過,pkg-resolver 會透過網路擷取未壓縮的 Blob。我們可以改為擷取預先壓縮的 Blob,節省頻寬、CPU 和記憶體用量。
  • 差異修補程式:無須再次下載未變更的 Blob。這是差異壓縮的一種形式,但只有在 Blob 內容保持相同時才有效。如果 Blob 內有少量變更,伺服器可以產生新舊 Blob 之間的差異修補程式,並只提供兩者之間的差異。

方法

結合上述策略,我們有下列方法可將 Blob 從 Blob 伺服器傳送到裝置,我們會逐一說明,然後顯示實驗結果。

未壓縮的 Blob

uncompressed_blobs

這是目前的行為。系統會直接下載未壓縮的 Blob,並在 Blobfs 將 Blob 寫入永久儲存空間時,即時壓縮 Blob。

離線壓縮

offline_compression

套件伺服器支援以適合 Blobfs 擷取的格式,下載預先壓縮的 Blob 構件。無須在裝置上壓縮,且可使用線上演算法,透過串流解壓縮器以節省記憶體的方式計算 Merkle 樹狀結構 / 根。

未壓縮 Blob 之間的差異

delta_between_uncompressed_blobs

針對每個新 Blob,我們會找出相同套件中的舊 Blob,並在兩個未壓縮的 Blob 之間產生修補程式。裝置必須下載修補程式、找出舊的 Blob、解壓縮、套用修補程式,然後壓縮/驗證新的 Blob 內容。

由於需要額外的解壓縮和壓縮步驟,相較於其他方法,這會需要大量的額外 CPU 和記憶體。這也能避免 Blobfs 最終移除壓縮程式碼。

壓縮 Blob 之間的差異

delta_between_compressed_blobs

與上述類似,但我們會在兩個離線壓縮 Blob 之間產生修補程式。在這種情況下,我們不需要在裝置上解壓縮舊的 Blob 或壓縮新的 Blob。這種做法的缺點是,使用壓縮功能可能會導致大型差異修補程式。

部分解壓縮 Blob 之間的差異

與上述類似,但修補步驟中,我們會在前後套用轉換,以縮減修補大小。

這項轉換會還原 zstd 中的 tANS 編碼,讓資料位元組對齊,且更加穩定。因此,差異修補作業的效率更高,同時在裝置上套用修補程式時,仍可避免重新執行壓縮演算法中耗費資源的相符項目尋找部分。

實驗資料

實驗結果顯示,與目前僅使用未壓縮 Blob 的方法相比,上述任一方法都能節省超過一半的 OTA 大小。未壓縮 Blob 之間的差異比離線壓縮小約 30% 至 50%,壓縮 Blob 之間的差異則比離線壓縮小約 10% 至 40%。我們目前尚未對部分解壓縮 Blob 之間的差異進行實驗,但預估結果會介於這兩種差異方法之間。

提案

根據實驗資料,我們建議先實作離線壓縮。除了離線壓縮之外,我們未來也可能會探索壓縮或部分解壓縮的 Blob 方法之間的差異修補程式 (如果找不到先前的 Blob,則會回復為離線壓縮)。

這個方法會導致我們無法在日後使用未壓縮 Blob 之間的差異,雖然這是所有選項中最節省頻寬的方法,但裝置上額外的 CPU 和記憶體用量可能會超過潛在的頻寬成本。

我們將在日後的 RFC 中介紹差異更新的設計,包括更新套件格式的必要變更。

設計

傳送 Blob

傳送 Blob 是指格式適合從伺服器傳送及寫入裝置的 Blob。傳送的 Blob 會包含含有中繼資料的標頭,以及含有 Blob 資料本身的酬載。

delivery_blob

傳送 Blob 格式應包含下列位元組對齊的欄位,並採用小端序:

  • 魔術數字b"\xfc\x1a\xb1\x0b" 4 位元組的 ID (代表紫紅色斑點),用於信心檢查,不會變更
  • 類型 (格式):表示 Blob 酬載的格式 (例如未壓縮、zstd-chunked),4 位元組列舉
  • 標頭長度:標頭大小,包括魔法和儲存空間專屬中繼資料,4 個位元組
  • 中繼資料:透過 Blobfs 將傳送 Blob 寫入裝置儲存空間時,所需的儲存空間專屬中繼資料
  • 酬載Type 指定格式的 Blob 資料

「Payload」和「Metadata」欄位會隨著時間演進,應視為實作詳細資料,且只能使用儲存空間團隊提供的工具和程式庫進行互動。針對指定的「Type」ID,相關聯的「Metadata」和「Payload」格式變更必須向後相容。

產生傳送 Blob

正式版

Fuchsia 產品建構程序會建立構件,包括未壓縮的 Blob 和中繼資料,指定產品預期接收的支援傳遞 Blob 類型。生產環境的 Blob 伺服器應使用 SDK 中儲存團隊提供的工具,壓縮每個 Blob 並產生指定 Type (--type) 的傳送 Blob。這樣一來,伺服器就能產生及提供指定 Blob 的多種格式,支援格式轉換,並處理不同產品使用的 Blob 類型可能存在的差異。

伺服器一律會使用最新發布的 SDK 中的工具,因此必須支援舊版格式,直到所有產品的所有管道都已完成過渡期為止。這樣一來,推送任何新版本時,就不必以舊格式產生 Blob。

對於相同資料、類型和工具版本,工具必須產生確定性輸出,但新版工具可以產生與舊版不完全相同的輸出。舉例來說,更新基礎壓縮程式庫的版本,就可能導致這種情況。對於指定的「類型」,工具「必須保證」產生的傳送 Blob 可向後相容於現有的 Fuchsia 版本,這些版本預期會使用這個「類型」的 Blob。

如果已存在相同雜湊和「類型」的傳送 Blob,伺服器應使用該 Blob,而非以最新工具產生新的傳送 Blob。這樣可確保現有經過充分測試的 OTA 路徑,不會受到日後無關的建構版本發布影響。

開發人員工作流程

目前建構作業會在產生 Blobfs 映像檔時壓縮所有 Blob。壓縮作業將從圖片生成程序中移除,改為使用專用工具,對每個 Blob 執行壓縮作業。這項工具的輸出內容會是指定傳送 blob 格式的 blob,主機上的 blob 伺服器可直接使用,以節省建構時間。

產生最終 Blobfs 映像檔時,應可選擇直接使用傳送 Blob 做為輸入內容 (除了未壓縮的 Blob 之外)。

基礎架構工作流程

建構工具會將產品建構和組裝期間產生的傳送 Blob,以及未壓縮的 Blob 上傳至 Blob 的共用 GCS 值區。測試執行工具和部分開發人員工作流程會使用這些傳送 Blob,從基礎架構下載套件。

由於 GCS 儲存空間會與所有建構作業共用,因此基礎架構應先驗證解壓縮的傳送 Blob 雜湊是否相符,再上傳。在 fuchsia.git 變更中,絕對不能略過這項驗證,以免惡意變更在未經驗證的情況下,執行預先提交上傳這些 Blob 的作業。

Blob 伺服器通訊協定

目前狀態

目前下載 blob 唯一需要的中繼資料是雜湊,我們可以透過雜湊在 blob 伺服器上找到未壓縮的 blob 網址,例如 https://blob.server.example/781205489a95d5915de51cf80861b7d773c879b87c4e0280b36ea42be8e98365

Blobfs 目前需要 blob 的大小才能開始寫入程序,而這項資訊可從 Content-Length HTTP 標頭取得。

離線壓縮

如果是離線壓縮,指定 Blob 的雜湊值仍會相同 (也就是與未壓縮 Blob 的 Merkle 根目錄相符)。因此,pkg-resolver 可以使用雜湊,透過網址在 blob 伺服器上尋找傳送 blob,並包含 blob 格式/類型。舉例來說,類型 1 的 Blob 網址會是 https://blob.server.example/1/781205489a95d5915de51cf80861b7d773c879b87c4e0280b36ea42be8e98365

傳送 Blob 的大小可從 Content-Length HTTP 標頭取得。傳送 Blob 格式包含標頭長度,因此可視需要有效率地擷取酬載。

如果要求類型中沒有 blob,伺服器會傳回 404。在這種情況下,我們會改用現有的寫入路徑,下載並寫入未壓縮的 Blob。正式版會停用這項備援機制,離線壓縮功能全面推出後即可移除。

相較於其他選項,探查 Blob 類型是否可用具有優勢,因為不需要變更格式。不過,探查的主要缺點是安裝套件時,我們可能會將 HTTP 要求加倍。為避免這種情況,我們可以確保 Blob 一律可用,並在正式版中停用備援。

Blobfs

目前狀態

目前寫入 Blob 的流程如下:

  1. Open 模式中 /blob/781... 的檔案控制代碼。Create
  2. Truncate 檔案的長度與未壓縮的 Blob 完全相同。
  3. Write 未壓縮的 Blob 酬載。

離線壓縮

封裝系統會在寫入 Blob 時,透過傳達傳送 Blob 格式類型,將 Blob 寫入 Blobfs。雖然兩者之間的通訊協定是內部通訊協定,但我們建議擴充目前的系統來實作這些通訊協定,方法如下:

  1. 如要寫入傳送 blob,pkg-cache 會開啟 /blob/v1-781... 以供寫入,並照原樣寫入傳送 blob。由於傳送 blob 標頭包含 Blobfs 寫入 blob 所需的所有資訊,因此不再需要截斷。

  2. 如果套件解析器改為下載原始未壓縮格式,pkg-cache 會開啟 /blob/781... 並遵循現有的寫入路徑

無論 Blob 是如何寫入裝置,從 Blobfs 讀回 Blob 的方式都相同,都是使用 Merkle 根做為路徑 (例如 /blob/781...)。

離線壓縮轉換完成後,blobfs 和 pkg-cache 可能會移除對舊版未壓縮 blob 格式的寫入支援,並移除 v1- 前置字元。

為減少寫入期間的記憶體用量,Blobfs 會將 Blob 資料串流至儲存空間,並使用串流解壓縮器產生 Merkle 樹狀結構。由於 Blob 應視為系統的不受信任輸入內容,因此所有解壓縮作業都必須在沙箱元件中完成。詳情請參閱「安全注意事項」。

離線壓縮功能推出後,我們可能會利用 zx 串流支援功能,進一步提升寫入路徑的效能。

檢查大小

這項 RFC 實施後,blobfs 中特定 Blob 的壓縮大小可能會出現更顯著的差異,因為不同軟體版本可能會對相同 Blob 使用不同的壓縮演算法。如果 blob 是使用較早的壓縮演算法在 blobfs 中快取,則磁碟上的大小可能與伺服器上相同 blob 的較新壓縮版本不同。產品在決定發布版本大小,以及為空間受限的裝置設定發布版本大小上限時,應考量這一點。

實作

如要支援離線壓縮,請按照下列步驟操作:

  • Blobfs 會更新離線壓縮的實驗性實作,在路徑中加入格式類型,並預設啟用這項功能。

  • pkg-cache 支援將傳送 Blob 寫入 Blobfs。

  • pkg-resolver 支援下載指定類型的傳送 Blob

  • Blob 伺服器會產生各種型別的傳送 Blob,並提供這些 Blob。

  • 建構系統會將傳送 Blob 發布至開發主機 TUF 存放區。

  • ffx repository 支援直接放送放送 Blob

效能

下載 Blob 時,離線壓縮功能可大幅減少頻寬、CPU 和記憶體用量。

視建構中包含的 Universe 套件數量而定,清除建構時間可能會較慢。這是因為這些套件目前不會在建構期間壓縮,而是在解析套件時,於各個裝置上壓縮。

由於重建 blob 映像檔時可能不需要再重新壓縮所有基本 blob,因此漸進式建構時間可能會快上許多。

人體工學

現在我們在多個位置都有放送 Blob 格式,因此主機工具可與其互動,有助於偵錯。

我們會在 ffx 中提供主機工具,可執行下列操作:

  • 在傳送 Blob 和未壓縮 Blob 之間轉換。
  • 顯示傳送 Blob 格式資訊:版本、未壓縮大小、裝置空間大小、壓縮比、區塊數量、區塊大小等。

回溯相容性

從未壓縮遷移至離線壓縮,以及從一種傳送 Blob 格式類型遷移至另一種,兩者非常相似,因此本節將一併說明。

特定格式類型的放送 Blob 必須與接受/支援該類型的任何現有裝置向後相容。如果特定 Blob 格式有任何重大變更,必須使用不同的型別 ID。

生產和開發人員工作流程會使用不同的轉換策略,因為兩者之間的取捨不同。在實際工作環境中,一部伺服器會為現場所有裝置提供服務,但開發人員通常只有一部裝置。

正式版

在轉換期間,伺服器必須為相同版本產生多種 Blob。在建立過渡版本前,如果任何裝置可能收到該版本的更新,就必須更新。

這種做法可讓我們更快推出新類型,而且只需要一個過渡步驟即可完成轉換。這也讓您不必在實際工作環境中加入用戶端備援邏輯。

開發人員工作流程

開發主機一次只能發布一種 Blob 格式。因此,主機端伺服器不一定會提供舊版 Blob 格式,以確保回溯相容性。

為確保 fx ota 繼續運作,我們會採用裝置端備援機制。 在過渡期間,執行新版建構作業的裝置會先嘗試取得新版 Blob 類型。如果主機上的套件伺服器沒有這個 Blob 格式,裝置應要求較舊的 Blob 類型做為備用。當大多數裝置完成更新後,我們就能切換主機端來產生新類型,並移除裝置上對這個備援方法的支援。

安全性考量

離線壓縮會擴大解壓縮程式的攻擊面,除了磁碟上儲存的資料外,現在還必須剖析透過網路傳輸的資料。除了使用 TLS 外,以傳送格式下載的 Blob 不會包含簽章,只能將不受信任的資料提供給解壓縮器進行驗證。

因此,我們必須使用沙箱解壓縮器進行離線壓縮,以做為深層防禦措施。如果攻擊者在解壓縮器中發現漏洞,就必須將其他攻擊連結至沙箱,才能入侵系統。系統會使用 Merkle 根雜湊驗證所有解壓縮資料,防止遭入侵的解壓縮器產生惡意輸出內容。

系統會使用安全核准的剖析程式庫,剖析提交格式中的中繼資料。

另一種可能的攻擊是將惡意 Blob 上傳至基礎架構 GCS 儲存空間,這部分會在「基礎架構工作流程」一節中說明。

隱私權注意事項

裝置傳送至伺服器的唯一額外資訊是傳送 Blob 格式類型。

測試

這項功能將涵蓋 pkg-resolver 整合測試、E2E OTA 測試,以及測試團隊的手動 OTA 測試。

說明文件

fuchsia.dev 上的 OTA 更新說明文件將會更新,明確指出系統會在套件解析期間下載傳送 Blob,且 Blob 的 Merkle 根雜湊仍為未壓縮的資料。

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

缺點

離線壓縮會將壓縮作業從裝置端移至伺服器端,因此套件解析速度會變快,但建構速度可能會變慢,且將建構作業上傳至伺服器時,可能會使用更多資源並耗費更長時間。

替代方案

未壓縮 Blob 之間的差異

這項功能也能大幅減少 OTA 大小,但會使用更多裝置 CPU 和記憶體。

含有壓縮雜湊的內容位址

除了未壓縮的雜湊值,我們也可以在參照套件中的 Blob 時加入壓縮的雜湊值。如果壓縮的雜湊值變更,我們仍需再次擷取未變更的 Blob。

優點:

  • 無論採用哪種升級路徑,執行相同版本的裝置都會有完全相同的 Blob。
  • Blobfs 不需要支援非常舊的 Blob 格式。
  • 更準確地計算空間用量。

缺點:

  • 需要大幅變更套件格式,但因中繼資料/內容格式變更而遭到封鎖。
  • 必須變更釘選套件網址格式,加入壓縮雜湊。
  • Blobfs 必須執行更多工作,才能追蹤未壓縮和壓縮的雜湊。
  • 如果壓縮方式有變,裝置下載相同 Blob 時會使用更多頻寬。
  • 如果更新會變更 Blob 格式,則必須更新每個 Blob,導致裝置非常接近儲存空間上限。

在套件解析期間驗證傳送 Blob 的雜湊

與「具有壓縮雜湊的內容位址」類似,當 Package Resolver 下載 Blob 時,我們可驗證傳送 Blob 雜湊,藉此獲得部分相同的安全性優勢。儲存在 Blob 儲存空間中的傳送 Blob 可能會變更,開頭會包含雜湊:

  • 傳送 Blob Merkle:針對傳送 Blob 中剩餘位元組計算的 merkle。

然後,可以變更套件格式、TUF 中繼資料、Omaha 回應和已釘選的套件網址,同時納入未壓縮 Blob 和傳送 Blob 的 Merkle 樹狀結構。

下載 blob 時:

  • Package Resolver 會開始下載前 32 個位元組,並從傳送 Blob 中擷取傳送 Blob Merkle。
  • Package Resolver 會開啟 /blobs/,並將其截斷至傳送 Blob 的長度減去 32 個位元組。
  • Package Resolver 會在計算傳送 Blob 的實際 Merkle 時,開始將前 32 個位元組串流至 blobfs。
  • 收到最後一個區塊後,請先驗證傳送 Blob 是否有正確的 Merkle 樹狀結構,再將其寫入 blobfs。如果是,請將最後一個區塊寫入 blobfs,否則請提早關閉檔案。
  • Blobfs 收到檔案關閉要求後,會在沙箱中解壓縮傳送的 Blob,並驗證其 Merkle 樹狀結構是否與未壓縮的 Blob Merkle 樹狀結構相符。

優點:

  • 由於傳送 Blob 的雜湊值儲存在已簽署的中繼資料中,因此裝置會拒絕惡意 Blob,避免遭到攻擊者利用解壓縮程式攻擊。攻擊者必須具備 Blob 儲存區的寫入權限,才能發動這類攻擊。

缺點:

  • 您必須變更套件格式,方法是使用新的中繼資料/內容格式,或在 meta/fuchsia.pkg 中新增檔案。
  • 如果本機攻擊者能直接透過解壓縮器攻擊,將本機 Blob 覆寫至 blobfs,這項措施就無法提供保護。
  • 由於我們不會覆寫任何本機現有的 Blob,因此無法保證所有裝置的 Blob 完全相同。
  • 需要變更釘選套件網址格式,加入壓縮雜湊。

簽署傳送 Blob

壓縮資料的簽章可納入中繼資料,以便在將酬載傳遞至解壓縮器前進行驗證。這可解決解壓縮不受信任資料的安全疑慮。

不過,為了讓這項資料可串流,我們必須在傳送 Blob 中加入壓縮資料的完整 Merkle 樹狀結構,並驗證每個區塊的雜湊。我們也需要考量金鑰管理、金鑰輪替等事項。

這會為串流流程和傳送 Blob 生成程序帶來許多複雜性,且不清楚這如何與第三方 Blob 伺服器搭配運作。

我們認為使用安全核准的剖析程式庫和解壓縮沙箱,就足以降低這項風險。

先前技術和參考資料

Chrome OS

Crostini 映像檔是使用 gzip 壓縮的 squashfs,puffin 則用於解碼 deflate 串流的 huffman 編碼,並以自訂格式產生兩個壓縮映像檔之間的高效率 delta 修補程式。

Android

Android 中的非 A/B 更新會使用 imgdiff 修補 APK,在 APK 中未壓縮的檔案上執行 bsdiff。Android 改用 A/B 更新後,imgdiff 就由 puffin 取代。