Khắc phục lỗi AWS Glue OOM: Xử lý 'java.lang.OutOfMemoryError: Java heap space'

intermediate☁️ AWS2026-06-25| AWS Glue ETL Jobs (môi trường Apache Spark), thường sử dụng các loại worker G.1X hoặc G.2X.

Error Message

Command failed with exit code 1. java.lang.OutOfMemoryError: Java heap space
#aws-glue#spark#oom#khắc-phục-lỗi

Thông báo lỗi

Công việc (job) AWS Glue ETL của bạn đang chạy trơn tru thì đột ngột dừng lại. Bạn kiểm tra log trên CloudWatch và thấy lỗi cụ thể này:

Command failed with exit code 1. java.lang.OutOfMemoryError: Java heap space

Sự cố này xảy ra khi Spark driver hoặc một executor bị hết bộ nhớ JVM. Về cơ bản, dữ liệu bạn đang cố gắng xử lý lớn hơn dung lượng RAM hiện có trong các worker node của Glue.

Tại sao các job Glue bị hết bộ nhớ Heap Space

Các vấn đề về bộ nhớ Spark hiếm khi xảy ra ngẫu nhiên. Chúng thường bắt nguồn từ cách cấu trúc dữ liệu hoặc cách mã nguồn xử lý dữ liệu đó. Dưới đây là những nguyên nhân phổ biến nhất:

  • Lệch dữ liệu (Data Skew): Một partition lớn gấp 10 lần các partition khác. Điều này buộc một worker duy nhất phải xử lý toàn bộ công việc nặng nhọc trong khi các worker khác rảnh rỗi, cuối cùng khiến node bị quá tải đó gặp sự cố.
  • Large Broadcast Joins: Spark cố gắng thực hiện một broadcast join bằng cách gửi một bảng "nhỏ" đến mọi worker. Nếu bảng đó thực tế nặng 500MB hoặc 1GB, nó có thể nhanh chóng vượt quá ngưỡng broadcast mặc định là 10MB và ngốn hết heap.
  • Vấn đề tệp nhỏ (The Small File Problem): Nếu bạn đang đọc 50.000 tệp mà mỗi tệp chỉ nặng 10KB, Spark driver sẽ bị "nghẹt" khi cố gắng theo dõi metadata của từng tệp một.
  • Các thao tác tốn bộ nhớ: Sử dụng .collect() hoặc .toPandas() sẽ kéo toàn bộ tập dữ liệu vào bộ nhớ của driver node. Nếu tập dữ liệu của bạn là 5GB và driver chỉ có 4GB heap space, nó sẽ thất bại ngay lập tức.

Bước 1: Nâng cấp loại Worker (Worker Type)

Đôi khi bạn chỉ cần một "con thuyền" lớn hơn. Nếu các worker G.1X (16GB RAM) của bạn thất bại, việc nâng cấp lên G.2X (32GB RAM) là cách nhanh nhất để ổn định công việc. Điều này giúp Spark có thêm không gian để "thở" trong quá trình shuffle và join.

Để thay đổi điều này trong AWS Console:

  • Điều hướng đến cấu hình Glue Job của bạn.
  • Tìm Worker type dưới tab Job details.
  • Chuyển từ G.1X sang G.2X.
  • Cân nhắc bật Flex execution cho các job không khẩn cấp để tiết kiệm tới 34% chi phí trong khi sử dụng các worker lớn này.

Bước 2: Khắc phục tình trạng lệch dữ liệu và tệp nhỏ

Nếu việc nâng cấp không hiệu quả, có khả năng dữ liệu của bạn đang được phân bổ không đồng đều. Bạn có thể buộc Spark phân chia tải trọng bằng cách repartition dữ liệu dựa trên một cột có độ đa dạng cao (high-cardinality).

# Phân bổ dữ liệu trên 200 partition đều nhau
df = df.repartition(200, "user_id")

Để khắc phục vấn đề tệp nhỏ trong S3, hãy sử dụng tính năng groupFiles của Glue. Tính năng này gộp các tệp nhỏ thành các khối lớn hơn, dễ quản lý hơn (ví dụ: 128MB) trước khi Spark bắt đầu xử lý chúng.

datasource = glueContext.create_dynamic_frame.from_catalog(
    database = "sales_db", 
    table_name = "transactions", 
    additional_options = {"groupFiles": "inPartition", "groupSize": "134217728"} # Các nhóm 128MB
)

Bước 3: Tinh chỉnh bộ nhớ Spark

Các thiết lập Spark mặc định không phải lúc nào cũng tối ưu cho mọi mô hình ETL. Bạn có thể ghi đè các thiết lập này bằng cách thêm tham số vào job Glue. Những cài đặt này giúp ngăn driver cố gắng thực hiện quá nhiều việc cùng một lúc.

  • --conf spark.sql.autoBroadcastJoinThreshold: Đặt thành -1 để tắt tính năng tự động broadcasting nếu bạn có các bảng tra cứu lớn.
  • --conf spark.driver.maxResultSize: Tăng giá trị này lên 4g hoặc cao hơn nếu driver của bạn bị crash trong quá trình thu thập dữ liệu cuối cùng.

Thêm các tham số này vào phần Job parameters trong cấu hình Glue của bạn:

Key: --conf
Value: spark.sql.autoBroadcastJoinThreshold=-1 --conf spark.driver.maxResultSize=4g

Bước 4: Tận dụng Glue Dynamic Frames

Native Spark DataFrames yêu cầu một schema cố định, điều này có thể gây tốn bộ nhớ khi xử lý JSON lồng nhau hoặc các trường dữ liệu thay đổi. Glue Dynamic Frames được tính toán theo kiểu lazy evaluation và xử lý các thay đổi schema linh hoạt hơn. Hãy sử dụng chúng cho các tác vụ nặng ban đầu trước khi chuyển đổi sang Spark DataFrame cho các phép tính toán phức tạp.

# Sử dụng Filter transform gốc của Glue thay vì Spark SQL
dynamic_frame = glueContext.create_dynamic_frame.from_options(...)
filtered_frame = Filter.apply(frame = dynamic_frame, f = lambda x: x["status"] == "active")

Xác minh

Làm thế nào để biết lỗi đã thực sự được khắc phục? Hãy kiểm tra ba khu vực sau:

  • CloudWatch Metrics: Theo dõi glue.driver.jvm.heap.usage. Nếu thông số này duy trì dưới 70-80%, job của bạn đang hoạt động tốt. Nếu nó vọt lên 95% và duy trì ở đó, bạn vẫn đang gặp rủi ro.
  • Spark UI: Mở Spark History Server. Tìm một task đơn lẻ mất nhiều thời gian hơn đáng kể so với các task khác; điều này xác nhận bạn vẫn gặp vấn đề lệch dữ liệu.
  • Log Status: Job sẽ kết thúc với trạng thái Succeeded và không có "Exit Code 1" trong log.

Mẹo chuyên nghiệp để ổn định lâu dài

  • Bật Auto-scaling: Hãy để Glue quản lý số lượng worker. Nó sẽ thêm node khi áp lực bộ nhớ tăng lên và giảm bớt khi công việc sắp hoàn thành.
--enable-auto-scaling: true
  • Ngừng sử dụng .collect(): Đây là nguyên nhân số 1 gây ra lỗi OOM cho driver. Nếu bạn cần xem dữ liệu, hãy sử dụng df.show(10) thay vì kéo toàn bộ tập dữ liệu vào driver.
  • Pushdown Predicates: Lọc dữ liệu của bạn ngay tại cấp độ S3 để không bao giờ phải tải các hàng không cần thiết vào bộ nhớ.

Related Error Notes