RFC-0207:離線 blob 壓縮

RFC-0207:離線 blob 壓縮
狀態已接受
區域
  • 軟體推送
說明

下載壓縮的 blob。

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

摘要

在 Fuchsia 目前的設計中,解決套件時,系統會透過網路擷取未壓縮的 blob,然後在裝置上壓縮後再寫入磁碟。這個 RFC 建議可直接擷取壓縮 blob 構件的替代方案。這樣做可以降低頻寬,並消除裝置端的運算成本高昂。

提振精神

pkg-resolver 會從 blob 伺服器擷取未壓縮 blob,然後將 blob 直接寫入 Blobf。Blobfs 會即時壓縮資料,這需要大量的 CPU 和記憶體。

使用離線壓縮時,系統會在擷取前預先壓縮 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 (Infra)

社群媒體化:

在軟體推送團隊中的設計討論中,我們討論了離線壓縮和差異更新。相關相關人員審查了 RFC 的早期草稿。

策略遊戲

想要改善 OTA 時間和頻寬時,可以採用以下兩種主要策略:

  • 離線壓縮:Blobfs 會動態壓縮磁碟上的 blob。不過,pkg-解析器會透過線擷取未壓縮的 blob。我們可以改為擷取預先壓縮的 blob,以節省頻寬、CPU 和記憶體用量。
  • Delta 修補程式:未變更的 blob 不需要重新下載。這是差異壓縮的形式,但只有在 blob 內容維持不變時才能使用。如果 blob 中有微幅變更,伺服器可能會在新 blob 和新 blob 之間產生差異修補作業,並且僅提供兩者之間的差異。

方法

透過結合上述策略,我們有下列方法,可從 blob 伺服器將 blob 傳送至裝置,接著逐一評估每個方法,然後顯示實驗結果。

未壓縮的 Blob

未壓縮的_blob

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

離線壓縮

live_compression

套件伺服器支援以適合 Blobfs 擷取的格式,下載預先壓縮的 blob 構件。不需要進行裝置端壓縮。此外,您可以使用線上演算法,透過串流解壓縮器,以高效率的方式計算 Merkle 樹狀結構 / 根。

未壓縮 Blob 之間的差異

介於_uncompress_blobs 之間的差異

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

相較於其他方法,這個方法需要額外的解壓縮和壓縮步驟,因此需要大量額外的 CPU 和記憶體。這麼做也能防止系統最終移除 Blobfs 的壓縮程式碼。

壓縮 Blob 之間的差異

差距_compress_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 (代表 fuchsia blob) 以進行可信度檢查,不會變更
  • Type (類型):代表 blob 酬載的格式 (例如未壓縮、zstd-chunked)、4 位元組列舉
  • 標頭長度:標頭大小,包括魔法和儲存空間專屬中繼資料,4 位元組
  • 中繼資料:透過 Blobfs 將交付 blob 寫入裝置儲存空間所需的儲存空間專屬中繼資料
  • 酬載Type 指定格式的 Blob 資料。

「Payload」(酬載) 和「Metadata」(中繼資料) 欄位會隨著時間演進,因此您應將其視為實作詳細資料,並且只使用儲存團隊提供的工具和程式庫互動。就特定類型 ID 而言,如果要變更相關聯的「中繼資料」和「酬載」格式,必須以回溯相容的方式完成。

產生外送 Blob

正式版

Fuchsia 產品建構程序會建立構件,包括未壓縮 blob 和中繼資料,藉此指定產品預期的受支援提交 blob 類型。實際工作環境 blob 伺服器應使用 SDK 中的儲存團隊提供的工具壓縮每個 blob,並以指定類型 (--type) 產生提交 blob。這可讓伺服器產生及提供多種指定 blob 的格式,藉此支援格式轉換,以及處理不同產品間使用的 blob 類型可能差異。

伺服器一律會使用最新發布 SDK 中的工具,因此在為所有產品管道導入所有管道前,請務必保留對舊格式的支援。確保推送任何新版本時,不需以舊格式產生 blob。

對於相同的資料、類型和工具版本,這項工具必須產生確定性輸出內容,但新版工具可以產生與先前版本不完全相同的輸出內容。這可能導致這種情況,例如將基礎壓縮程式庫更新為較新版本。針對指定的類型,工具「必須」確保產生的提交 blob 與預期的 blob 這類類型現有版本回溯相容。

如果已有相同雜湊和 Type 的傳送 blob 已存在,伺服器應使用該 blob,而非使用最新工具產生新的提交 blob。這可確保現有經過妥善測試的 OTA 路徑不會受到未來不相關的建構版本的影響。

開發人員工作流程

目前建構作業在產生 Blobfs 圖片時會壓縮所有 blob。 因此,壓縮功能會移出圖片產生程序,並使用專屬工具在每個 blob 上分別執行。工具的輸出內容將採用指定提交 blob 格式的 blob,主機上的 blob 伺服器可直接使用該檔案來節省建構時間。

除了未壓縮的 blob 外,產生最終 Blobfs 圖片還能選擇直接使用提交 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 隨時可用,並在實際工作環境中停用備用選項,我們就能避免這種情況。

水滴

目前狀態

目前的寫入 blob 的流程如下:

  1. Create 模式下為 /blob/781... 執行 Open 檔案控制代碼。
  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 如何寫入裝置,使用 Merkle 根層級做為路徑 (例如 /blob/781...) 都會保持不變。

轉換成離線壓縮後,blobfs 和 pkg-cache 會停止支援編寫舊未壓縮 blob 格式的功能,並移除 v1- 前置字串。

為了減少寫入期間的記憶體,Blobfs 應將 blob 串流至儲存空間,並利用串流解壓縮器產生 Merkle 樹狀結構。由於 blob 應視為系統中不受信任的輸入,因此所有解壓縮都必須在沙箱元件中完成。詳情請參閱安全性考量

離線壓縮推出後,我們可能會使用 zx Stream 支援,進一步改善寫入路徑的效能。

尺寸檢查

在這個 RFC 之後,blobfs 中的指定 blob 壓縮大小可能會大幅增加,因為不同的軟體版本可能會針對同一個 blob 使用不同的壓縮演算法。如果 blob 是透過舊版壓縮演算法在 blobfs 中快取,則磁碟的大小可能會與伺服器上相同 blob 的較新壓縮不同。產品在決定版本大小,並為空間受限的裝置設定發布大小上限時,應考量此點。

實作

您必須執行下列步驟才能支援離線壓縮功能:

  • Blobfs 更新離線壓縮的實驗性實作,將格式類型納入路徑中,並預設啟用該格式。

  • pkg-cache 支援將傳送 blob 寫入 Blobf。

  • pkg-resolver 支援下載指定類型的交付 blob

  • Blob 伺服器會產生各種類型的提交 blob,並提供這些 blob。

  • 建構系統會將提交 blob 發布至 devhost TUF 存放區。

  • ffx repository 支援直接提供提交 blob

效能

離線壓縮將可在下載 blob 時大幅降低頻寬、CPU 和記憶體用量。

視建構作業中包含的宇宙套件數量而定,清理建構時間可能會比較慢。這是因為目前這些套件並未在建構期間壓縮,而是在解決套件時,在每個裝置上壓縮。

由於重新建構 blobfs 映像檔可能不需要重新壓縮所有基礎 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 測試。

說明文件

OTA 更新的 fuchsia.dev 說明文件會更新,說明在套件解析度期間下載了推送 blob,而 blob 的 Merkle 根雜湊仍是未壓縮的資料。

缺點、替代方案和未知

缺點

離線壓縮會將壓縮從裝置端移到伺服器端,因此套件解析速度較快,但建構速度可能較慢,而將版本上傳至伺服器可能會耗用更多資源,也需要更多時間。

替代選項

未壓縮 Blob 之間的差異

此功能也能大幅縮減 OTA 的大小,但會在裝置上使用更多 CPU 和記憶體。

含有壓縮雜湊的內容位址

除了未壓縮的雜湊值之外,我們也能在參照套件中的 blob 時加入壓縮雜湊;對於維持不變的 blob,我們還是需要在經過壓縮的雜湊變更時再次擷取這些雜湊。

優點:

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

缺點:

  • 必須大幅變更套件格式,因為中繼/內容格式變更而遭到封鎖。
  • 必須變更為固定套件網址格式,才能加入壓縮雜湊。
  • Blobfs 需要投入更多工作才能追蹤未壓縮和壓縮的雜湊。
  • 如果壓縮有所改變,裝置就會使用更多頻寬下載同一個 blob。
  • 變更 blob 格式的更新將需要更新每個 blob,使裝置接近儲存空間上限。

在套件解析期間驗證交付 Blob 雜湊

與「含有壓縮雜湊的內容位址」類似,當 Package 解析器下載 blob 時,我們可以驗證交付 blob 雜湊,進而享有相同的安全性優勢。儲存在 blob 儲存庫中的推送 blob 可以改為在開頭加入雜湊:

  • 交付 Blob Merkle:在提交 blob 中,根據剩餘位元組計算的 Merkle。

然後,套件格式、TUF 中繼資料、Omaha 回應和固定套件網址可以變更,以同時包含未壓縮 blob 和提交 blob 的 Merkles。

下載 blob 時:

  • Package Resolver 會開始下載前 32 個位元組,然後從傳遞的 blob 中擷取推送 blob Merkle。
  • Package Resolver 會開啟 /blobs/,後者會將 blob 截斷至提交 blob 長度減 32 個位元組。
  • 套件解析器會在前 32 個位元組之後開始將每個區塊串流至 blobf,同時計算傳遞 blob 的實際 Merkle。
  • 收到最後一個區塊後,請在將其寫入 blobf 之前,驗證交付項目 blob 有正確的 Merkle。如果是,請將最後一個區塊寫入 blobf,否則請盡早關閉檔案。
  • Blobfs,即接收檔案關閉時,在沙箱中解壓縮交付 blob,並驗證其 Merkle 與未壓縮的 blob 完全相符。

優點:

  • 由於傳遞 blob 雜湊會儲存在已簽署的中繼資料中,因此裝置會拒絕含有該 blob 存放區寫入權限的攻擊者所編寫的解壓縮器攻擊的惡意 blob。

缺點:

  • 您必須以新的中繼資料/內容格式或中繼/fuchsia.pkg 中的新檔案變更套件格式。
  • 如果本機攻擊者能夠利用解壓縮器直接攻擊將本機 blob 直接寫入 blobf,將無法防範本機攻擊者。
  • 因為我們不會覆寫任何本機上的 blob,因此無法保證所有裝置都有相同的 blob。
  • 必須變更固定套件網址格式,才能加入壓縮雜湊。

簽署交付 Blob

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

不過,為了使這個串流變得可串流,我們必須在傳遞 blob 中納入壓縮資料的完整 Merkle 樹狀結構,並驗證每個區塊的雜湊。我們也必須考慮金鑰管理、金鑰輪替等。

這會對串流流程和推送 blob 產生程序造成大量複雜度,但不清楚這與第三方 blob 伺服器搭配運作的方式。

我們認為使用經過安全性核准的剖析程式庫和解壓縮沙箱,確實能降低這個風險。

既有圖片與參考資料

ChromeOS

crostini 圖片是採用 gzip 壓縮的 Squashfs,puffin 用於將定義串流的 Huffman 編碼解碼為自訂格式,在兩個壓縮圖片之間產生有效率的差異值修補。

Android

Android 中的非 A/B 更新會使用 imgdiff 與修補 APK 的方式執行,而在 Android 移至 A/B 更新時,針對 APK 中的未壓縮檔案執行 bsdiff 就會由 puffin 取代。