排解 Dataflow 記憶體不足錯誤

本頁面說明如何找出並解決 Dataflow 中的記憶體不足 (OOM) 錯誤。

找出記憶體不足錯誤

如要判斷管道是否即將耗盡記憶體,請使用下列其中一種方法。

  • 在「Jobs details」(工作詳細資料) 頁面的「Logs」(記錄) 窗格中,查看「Diagnostics」(診斷) 分頁標籤。 這個分頁會顯示與記憶體問題相關的錯誤,以及錯誤發生的頻率。
  • Dataflow 監控介面中,使用「記憶體使用率」圖表監控工作站記憶體容量和用量。
  • 在「工作詳細資料」頁面的「記錄」窗格中,選取「工作站記錄」,找出工作站記錄中的記憶體不足錯誤。
  • 系統記錄中也可能會顯示記憶體不足錯誤。如要查看這些項目,請前往記錄檔探索工具,並使用下列查詢:

    resource.type="dataflow_step"
    resource.labels.job_id="JOB_ID"
    "out of memory" OR "OutOfMemory" OR "Shutting down JVM"
    

    JOB_ID 替換為工作 ID。

  • 如果是 Java 工作,Java 記憶體監控器會定期回報垃圾收集指標。如果用於垃圾收集的 CPU 時間比例在一段時間內超過 50% 的門檻,SDK 測試架構就會失敗。您可能會看到類似以下範例的錯誤:

    Shutting down JVM after 8 consecutive periods of measured GC thrashing. Memory is used/total/max = ...
    

    即使實體記憶體仍有可用空間,也可能發生這個錯誤,通常表示管道的記憶體用量效率不彰。如要解決這個問題,請最佳化管道

    Java 記憶體監控器是透過 MemoryMonitorOptions 介面設定。

如果作業的記憶體用量偏高或發生記憶體不足錯誤,請按照本頁的建議,最佳化記憶體用量或增加可用記憶體量。

解決記憶體不足錯誤

變更 Dataflow 管道可能可以解決記憶體不足錯誤,或減少記憶體用量。可能的變更包括下列動作:

下圖顯示本頁所述的 Dataflow 疑難排解工作流程。

顯示疑難排解工作流程的圖表。

請嘗試下列解決方法:

  • 盡可能最佳化管道,減少記憶體用量。
  • 如果工作是批次工作,請依序嘗試下列步驟:
    1. 使用每個 vCPU 記憶體較多的機器類型。
    2. 將執行緒數量減少至每個工作站的 vCPU 數量以下。
    3. 使用自訂機器類型,為每個 vCPU 分配更多記憶體。
  • 如果工作是使用 Python 的串流工作,請將執行緒數量減少至 12 以下。
  • 如果工作是使用 Java 或 Go 的串流工作,請嘗試下列做法:
    1. 將執行緒數量減少至 Runner v2 工作少於 500 個,或減少至未使用 Runner v2 的工作少於 300 個。
    2. 使用記憶體較大的機器類型。

最佳化管道

多項管道作業都可能導致記憶體不足錯誤。本節提供多種方法,可減少管道的記憶體用量。如要找出耗用最多記憶體的管道階段,請使用 Cloud Profiler 監控管道效能

您可以採用下列最佳做法,提升管道成效:

使用 Apache Beam 內建的 I/O 連接器讀取檔案

請勿在 DoFn 中開啟大型檔案。如要讀取檔案,請使用 Apache Beam 內建的 I/O 連接器。在 DoFn 中開啟的檔案必須符合記憶體大小。由於多個 DoFn 執行個體會同時執行,在 DoFn 中開啟大型檔案可能會導致記憶體不足錯誤。

使用 GroupByKey PTransforms 時重新設計作業

在 Dataflow 中使用 GroupByKey PTransform 時,系統會在單一執行緒上處理每個鍵和每個時間區間的值。由於這項資料會以串流形式從 Dataflow 後端服務傳遞至工作站,因此不需要納入工作站記憶體。不過,如果值是在記憶體中收集,處理邏輯可能會導致記憶體不足錯誤。

舉例來說,如果您有一個包含視窗資料的鍵,並將鍵值新增至記憶體內物件 (例如清單),可能會發生記憶體不足錯誤。在這種情況下,工作站可能沒有足夠的記憶體容量來保存所有物件。

如要進一步瞭解 GroupByKey PTransforms,請參閱 Apache Beam Python GroupByKeyJava GroupByKey 說明文件。

下列清單提供管道設計建議,可協助您在使用 GroupByKey PTransform 時,盡量減少記憶體用量。

  • 如要減少每個鍵和每個視窗的資料量,請避免使用具有多個值的鍵,也就是熱鍵。
  • 如要減少每個視窗收集的資料量,請縮小視窗大小。
  • 如果您要使用視窗中的鍵值計算數字,請使用 Combine 轉換。收集值後,請勿在單一 DoFn 執行個體中進行計算。
  • 先篩選值或重複項目,再進行處理。詳情請參閱 Python FilterJava Filter 轉換說明文件。

減少外部來源的輸入資料

如果您要呼叫外部 API 或資料庫來擴充資料,傳回的資料必須符合工作站記憶體大小。如果您要批次處理呼叫,建議使用 GroupIntoBatches 轉換。 如果遇到記憶體不足錯誤,請減少批次大小。如要進一步瞭解如何分批處理,請參閱 Python GroupIntoBatchesJava GroupIntoBatches 轉換說明文件。

在不同執行緒之間共用物件

DoFn 執行個體之間共用記憶體內資料物件,可提升空間和存取效率。在 DoFn 的任何方法中建立的資料物件 (包括 SetupStartBundleProcessFinishBundleTeardown) 都會針對每個 DoFn 叫用。在 Dataflow 中,每個工作站可能有多個DoFn執行個體。如要更有效率地使用記憶體,請將資料物件做為單例項傳遞,以便在多個 DoFn 之間共用。詳情請參閱「Cache reuse across DoFns」網誌文章。

使用節省記憶體的元素表示法

評估是否能使用耗用較少記憶體的 PCollection 元素表示法。在管道中使用編碼器時,請考慮編碼和解碼的 PCollection 元素表示法。稀疏矩陣通常可從這類最佳化中獲益。

縮減側邊輸入的大小

如果 DoFn 使用側邊輸入,請縮減側邊輸入的大小。如果是元素集合的側邊輸入內容,請考慮使用可疊代檢視畫面 (例如 AsIterableAsMultimap),而非同時具體化整個側邊輸入內容的檢視畫面 (例如 AsList)。

減少執行緒數量

您可以減少執行 DoFn 執行個體的執行緒數量上限,增加每個執行緒可用的記憶體。這項變更會減少平行處理,但可為每個 DoFn 提供更多記憶體。

下表顯示 Dataflow 建立的預設執行緒數量:

工作類型 Python SDK Java/Go SDK
批次 每個 vCPU 1 個執行緒 每個 vCPU 1 個執行緒
使用 Runner v2 進行串流 每個 vCPU 12 個執行緒 每個工作站 VM 500 個執行緒
不使用 Runner v2 進行串流 每個 vCPU 12 個執行緒 每個工作站 VM 300 個執行緒

如要減少 Apache Beam SDK 執行緒數量,請設定下列管道選項

Java

使用 --numberOfWorkerHarnessThreads 管道選項。

Python

使用 --number_of_worker_harness_threads 管道選項。

Go

使用 --number_of_worker_harness_threads 管道選項。

如果是批次工作,請將值設為小於 vCPU 數的數字。

如果是串流工作,請先將值減半 (預設值的一半)。如果這個步驟無法解決問題,請繼續將值減半,並觀察每個步驟的結果。舉例來說,使用 Python 時,請嘗試 6、3 和 1。

使用每個 vCPU 記憶體較多的機器類型

如要選取每個 vCPU 具有更多記憶體的工作站,請使用下列其中一種方法。

  • 一般用途機器系列中使用高記憶體使用率機器類型。高記憶體使用率機型的每個 vCPU 記憶體都比標準機型高。使用高記憶體機器類型時,每個工作站可用的記憶體和每個執行緒可用的記憶體都會增加,因為 vCPU 數量維持不變。因此,使用高記憶體使用率機器類型,可有效率地選取每個 vCPU 具備較多記憶體的背景工作。
  • 如要更彈性地指定 vCPU 數量和記憶體容量,可以使用自訂機器類型。使用自訂機器類型時,您可以 256 MB 為單位增加記憶體。這些機器類型的價格與標準機器類型不同。
  • 部分機器系列可讓您使用擴充記憶體自訂機器類型。擴充記憶體可提高每個 vCPU 的記憶體比例。費用較高。

如要設定工作站類型,請使用下列管道選項。詳情請參閱「設定管道選項」和「管道選項」。

Java

使用 --workerMachineType 管道選項。

Python

使用 --machine_type 管道選項。

Go

使用 --worker_machine_type 管道選項。

只使用一個 Apache Beam SDK 程序

對於使用 Runner v2 的 Python 串流管道和 Python 管道,您可以強制 Dataflow 為每個工作站只啟動一個 Apache Beam SDK 程序。嘗試這個選項前,請先試著使用其他方法解決問題。如要將 Dataflow 工作站 VM 設定為只啟動一個容器化的 Python 程序,請使用下列管道選項

--experiments=no_use_multiple_sdk_containers

完成這項設定後,Python 管道會為每個工作站建立一個 Apache Beam SDK 程序。這項設定可避免為每個 Apache Beam SDK 程序多次複製共用物件和資料。不過,這會限制工作站上可用運算資源的有效使用。

將 Apache Beam SDK 程序數量減少為一,不一定會減少工作站啟動的執行緒總數。此外,如果單一 Apache Beam SDK 程序上的所有執行緒都處於忙碌狀態,可能會導致處理速度緩慢,或管道停滯。因此,您可能也必須減少執行緒數量,如本頁「減少執行緒數量」一節所述。

您也可以使用只有一個 vCPU 的機器類型,強制工作站只使用一個 Apache Beam SDK 程序。

瞭解 Dataflow 記憶體用量

如要排解記憶體不足錯誤,瞭解 Dataflow 管道如何使用記憶體會很有幫助。

Dataflow 執行管道時,處理作業會分配到多個 Compute Engine 虛擬機器 (VM),通常稱為工作站。工作站會處理 Dataflow 服務中的工作項目,並將工作項目委派給 Apache Beam SDK 程序。Apache Beam SDK 程序會建立 DoFn 的執行個體。DoFn 是 Apache Beam SDK 類別,用於定義分散式處理函式。

Dataflow 會在每個工作站啟動多個執行緒,且每個工作站的記憶體會由所有執行緒共用。執行緒是在較大型程序中執行的單一可執行工作。預設執行緒數量取決於多項因素,且批次和串流作業各有不同。

如果管道需要的記憶體超過工作站可用的預設記憶體量,可能會發生記憶體不足錯誤。

Dataflow pipeline 主要有三種使用工作站記憶體的方式:

工作站運作記憶體

Dataflow 工作站需要記憶體才能執行作業系統和系統程序。工作站記憶體用量通常不會超過 1 GB。用量通常不到 1 GB。

  • worker 上的各種程序會使用記憶體,確保管道正常運作。這些程序可能都會保留少量記憶體供作業使用。
  • 如果管道未使用 Streaming Engine,額外的工作站程序會使用記憶體。

SDK 程序記憶體

Apache Beam SDK 程序可能會建立物件和資料,並在程序內的執行緒之間共用,在本頁中稱為 SDK 共用物件和資料。這些 SDK 共用物件和資料的記憶體用量稱為 SDK 程序記憶體。以下列出 SDK 共用物件和資料的範例:

  • 側邊輸入
  • 機器學習模型
  • 記憶體內單例模式物件
  • 使用 apache_beam.utils.shared 模組建立的 Python 物件
  • 從外部來源載入的資料,例如 Cloud Storage 或 BigQuery

未使用 Streaming Engine 的串流工作會將側邊輸入儲存在記憶體中。如果是 Java 和 Go 管道,每個工作站都會有一份旁側輸入內容。如果是 Python 管道,每個 Apache Beam SDK 程序都會有一份側邊輸入內容。

使用 Streaming Engine 的串流工作,側邊輸入大小上限為 80 MB。側邊輸入內容會儲存在工作站記憶體以外的位置。

SDK 共用物件和資料的記憶體用量會隨著 Apache Beam SDK 程序數量線性成長。在 Java 和 Go 管道中,每個工作站都會啟動一個 Apache Beam SDK 程序。在 Python 管道中,每個 vCPU 會啟動一個 Apache Beam SDK 程序。在同一個 Apache Beam SDK 程序中,SDK 共用物件和資料會在各個執行緒之間重複使用。

DoFn 記憶體用量

DoFn 是 Apache Beam SDK 類別,用於定義分散式處理函式。每個工作人員都可以執行並行的 DoFn 執行個體。每個執行緒都會執行一個 DoFn 例項。評估記憶體總用量、計算工作集大小,或應用程式繼續運作所需的記憶體量時,這項資訊可能會有幫助。舉例來說,如果個別 DoFn 最多使用 5 MB 的記憶體,且工作站有 300 個執行緒,則 DoFn 記憶體用量最高可達 1.5 GB,也就是記憶體位元組數乘以執行緒數。視工作站使用記憶體的方式而定,記憶體用量暴增可能會導致工作站記憶體不足。

很難估算 Dataflow 會建立多少個 DoFn 執行個體。數量取決於各種因素,例如 SDK、機器類型等。此外,DoFn 可能會由多個執行緒接連使用。Dataflow 服務不保證 DoFn 的叫用次數,也不保證在整個管道過程中會確切建立多少個 DoFn 執行個體。不過,下表會提供一些深入分析,說明您可預期的平行處理程度,並估算 DoFn 執行個體數量的上限。

Beam Python SDK

批次 不使用 Streaming Engine 進行串流 Streaming Engine
平行處理工作數量 每個 vCPU 1 個程序

每個程序 1 個執行緒

每個 vCPU 1 個執行緒

每個 vCPU 1 個程序

每個程序 12 個執行緒

每個 vCPU 12 個執行緒

每個 vCPU 1 個程序

每個程序 12 個執行緒

每個 vCPU 12 個執行緒

並行 DoFn 執行個體數量上限 (所有這些數字隨時可能變更) 每個執行緒 1 個 DoFn

每 vCPU 1 DoFn

每個執行緒 1 個 DoFn

每個 vCPU 12 DoFn

每個執行緒 1 個 DoFn

每個 vCPU 12 DoFn

Beam Java/Go SDK

批次 沒有 Runner V2 的串流設備和串流引擎 搭配 Runner v2 使用 Streaming Engine
平行處理工作數量 每個工作者 VM 1 個程序

每個 vCPU 1 個執行緒

每個工作者 VM 1 個程序

每個程序 300 個執行緒

每個工作站 VM 300 個執行緒

每個工作者 VM 1 個程序

每個程序 500 個執行緒

每個工作站 VM 500 個執行緒

並行 DoFn 執行個體數量上限 (所有這些數字隨時可能變更) 每個執行緒 1 個 DoFn

每 vCPU 1 DoFn

每個執行緒 1 個 DoFn

每個工作站 VM 300 個 DoFn

每個執行緒 1 個 DoFn

每個工作站 VM 500 個 DoFn

舉例來說,如果搭配 Python SDK 使用 n1-standard-2 Dataflow 工作站,則適用下列情況:

  • 批次工作:Dataflow 會為每個 vCPU 啟動一個程序 (本例為兩個)。每個程序會使用一個執行緒,而每個執行緒會建立一個 DoFn 執行個體。
  • 使用 Streaming Engine 的串流工作:Dataflow 會為每個 vCPU 啟動一個程序 (共兩個)。不過,每個程序最多可產生 12 個執行緒,每個執行緒都有自己的 DoFn 執行個體。

設計複雜的管道時,請務必瞭解DoFn生命週期。請確保 DoFn 函式可序列化,並避免直接在函式中修改元素引數。

如果您有多語言管道,且工作站上執行多個 Apache Beam SDK,工作站會盡可能使用最低程度的每個程序執行緒平行處理。

Java、Go 和 Python 的差異

Java、Go 和 Python 管理程序和記憶體的方式不同。因此,排解記憶體不足錯誤時,您應採取的做法會因管線使用的語言 (Java、Go 或 Python) 而異。

Java 和 Go pipeline

在 Java 和 Go 管道中:

  • 每個工作站都會啟動一個 Apache Beam SDK 程序。
  • SDK 共用物件和資料 (例如輔助輸入和快取) 會在工作站的所有執行緒之間共用。
  • SDK 共用物件和資料使用的記憶體通常不會根據工作站的 vCPU 數量調整。

Python 管道

在 Python 管道中:

  • 每個工作站會為每個 vCPU 啟動一個 Apache Beam SDK 程序。
  • SDK 共用物件和資料 (例如輔助輸入和快取) 會在每個 Apache Beam SDK 程序中的所有執行緒之間共用。
  • 工作站上的執行緒總數會根據 vCPU 數量線性擴充。 因此,SDK 共用物件和資料使用的記憶體會隨著 vCPU 數量線性成長。
  • 執行工作的執行緒會分散在各個程序中。新的工作單元會指派給沒有工作項目的程序,或是目前指派工作項目最少的程序。