Xử lý dữ liệu phân tán hợp nhất với Apache Spark
1. Tổng quan
A. Định nghĩa
Apache Spark là một engine xử lý dữ liệu phân tán hợp nhất (unified), phân tán dữ liệu lớn trên nhiều node của cluster, tái sử dụng kết quả trung gian trong bộ nhớ bất cứ khi nào có thể, và tối ưu mọi phép toán dưới dạng đồ thị có hướng không chu trình (DAG) dựa trên đánh giá trễ (lazy evaluation).
Bản chất của Spark nằm ở tính hợp nhất: "một engine xử lý tất cả batch, truy vấn tương tác, streaming và học máy." Nhà phát triển thao tác các collection phân tán như thể là một collection đơn lẻ thông qua các trừu tượng RDD (Resilient Distributed Dataset), DataFrame và Dataset, còn engine dịch khai báo đó thành kế hoạch thực thi, đảm nhiệm việc chia vùng, lập lịch, shuffle và khôi phục lỗi. Nhờ đó nhà phát triển viết được pipeline dữ liệu mở rộng tới hàng trăm đến hàng nghìn node mà không tự cài đặt giao tiếp giữa các node hay thử lại khi lỗi.
Spark khởi đầu tại AMPLab của UC Berkeley năm 2009, trở thành dự án cấp cao nhất của Apache năm 2014, và đặt giá trị cốt lõi là khắc phục sự kém hiệu quả của tính toán lặp trong mô hình batch lấy đĩa làm trung tâm của MapReduce bằng xử lý dựa trên bộ nhớ. Ngày nay Spark là engine xử lý tiêu chuẩn trên thực tế cho data lakehouse, ETL/ELT, kỹ thuật đặc trưng và phân tích log.
B. Bối cảnh ra đời và sự cần thiết
MapReduce là một đổi mới đơn giản hóa batch quy mô lớn, nhưng nó buộc phải ghi kết quả trung gian của mỗi giai đoạn (Map→Reduce) vào hệ thống tệp phân tán (HDFS) rồi đọc lại ở giai đoạn sau. Vòng lặp đĩa này không thành vấn đề với batch tổng hợp một lần, nhưng với thuật toán lặp duyệt cùng dữ liệu hàng chục lần (gradient descent, PageRank, K-means) hoặc phân tích tương tác khi con người thay đổi truy vấn, chi phí I/O tích lũy và thời gian xử lý tăng lên phi thực tế.
Các tổ chức dữ liệu thực tế vận hành engine khác nhau cho mỗi loại tải (MapReduce cho batch, Impala cho truy vấn, Storm cho stream, Mahout cho ML), trả giá bằng di chuyển dữ liệu, lưu trữ trùng lặp và độ phức tạp vận hành. Spark ra đời để giải quyết cùng lúc cả hai nỗi đau bằng "một trừu tượng duy nhất giữ kết quả trung gian trong bộ nhớ" và "một stack hợp nhất chạy SQL, stream và ML trên cùng một engine."
Đặc biệt bước vào kỷ nguyên data lakehouse và AI tạo sinh, nhu cầu biến đổi dữ liệu có cấu trúc, bán cấu trúc và phi cấu trúc trong một engine và tạo đặc trưng quy mô lớn để cấp cho pipeline huấn luyện mô hình ngày càng tăng. Spark phù hợp với nhu cầu hội tụ "dữ liệu dị chất và tải dị chất" này vào một lớp xử lý.
C. Mục tiêu thiết kế cốt lõi
Mục tiêu thiết kế của Spark không dừng ở thực thi nhanh. Mục đích là đồng thời đạt được tốc độ qua tái sử dụng bộ nhớ, khả năng chịu lỗi dựa trên dòng dõi (lineage), sự đơn giản của lập trình qua API khai báo, tính hợp nhất dung nạp nhiều loại tải trên một engine, và tối ưu tự động qua bộ tối ưu.
Thứ nhất, nó cache dữ liệu trong bộ nhớ để loại bỏ chi phí truy cập lặp lại. Thứ hai, thay vì sao chép dữ liệu, nó khôi phục lỗi bằng dòng dõi ghi lại "được tạo ra như thế nào," giảm chi phí lưu trữ. Thứ ba, với đánh giá trễ, nó nắm toàn bộ đồ thị phép toán trước rồi áp dụng các tối ưu như giảm thiểu shuffle và đẩy điều kiện xuống. Ba trục này là nền tảng cho hiệu năng và sự ổn định của Spark.
2. Các trừu tượng cốt lõi và kiến trúc tổng thể
A. Các trừu tượng RDD, DataFrame và Dataset
Trừu tượng thế hệ đầu của Spark là RDD, một collection phân tán bất biến (immutable) chia thành các partition, biến đổi bằng các toán tử hàm như map, filter, reduceByKey. RDD ghi nhớ qua dòng dõi "mỗi biến đổi được dẫn xuất từ RDD cha như thế nào," nên dù một số partition mất do lỗi node, chỉ phần đó được tính lại để khôi phục. Đây là nguyên lý cốt lõi chịu lỗi mà không sao chép.
Trừu tượng thế hệ hai, DataFrame, là collection phân tán dạng bảng có hàng và cột (schema), bên trong nhận tối ưu từ bộ tối ưu Catalyst và engine thực thi Tungsten. Vì DataFrame cho người dùng khai báo "muốn gì" còn engine quyết định "thực thi ra sao," nó thường nhanh hơn viết cùng logic bằng RDD cấp thấp. Dataset bổ sung an toàn kiểu tại thời điểm biên dịch, là trừu tượng chỉ dành cho JVM (Scala/Java).
Trong thực tế, nên ưu tiên DataFrame/Dataset trừ khi cần kiểm soát cấp thấp đặc biệt. Ví dụ, khi tổng hợp doanh thu theo cửa hàng từ hàng chục tỷ log giao dịch mỗi ngày, groupBy().agg() của DataFrame để bộ tối ưu tự động chèn tổng hợp từng phần (partial aggregation) trước shuffle, giảm mạnh lượng truyền mạng. Viết cùng logic trực tiếp bằng RDD buộc nhà phát triển phải tự tay mã hóa tối ưu này.
B. Kiến trúc thực thi tổng thể
flowchart LR
subgraph Driver["Driver"]
SC["SparkContext / SparkSession"]
CAT["Bo toi uu Catalyst"]
DAG["Bo lap lich DAG"]
end
subgraph CM["Trinh quan ly cluster (YARN / K8s / Standalone)"]
RES["Cap phat tai nguyen"]
end
subgraph Workers["Node worker (Executor)"]
E1["Executor 1<br/>Task / cache"]
E2["Executor 2<br/>Task / cache"]
E3["Executor 3<br/>Task / cache"]
end
SRC["Nguon du lieu (HDFS / S3 / Kafka / JDBC)"] --> SC
SC --> CAT --> DAG
DAG -->|yeu cau tai nguyen| RES
RES -->|khoi dong Executor| E1
RES -->|khoi dong Executor| E2
RES -->|khoi dong Executor| E3
DAG -->|phan phoi Task| E1
DAG -->|phan phoi Task| E2
DAG -->|phan phoi Task| E3
E1 -->|ket qua / trang thai| SC
E2 -->|ket qua / trang thai| SC
E3 -->|ket qua / trang thai| SC
Ứng dụng Spark hoạt động theo cấu trúc: driver thực thi mã người dùng trong khi lập kế hoạch logic, yêu cầu tài nguyên từ trình quản lý cluster (YARN, Kubernetes, Standalone) để khởi động các Executor, rồi phân phối task tới từng Executor. Driver là bộ não của toàn bộ tính toán, đảm nhiệm lập lịch và thu thập kết quả, còn Executor đảm nhiệm tính toán theo partition thực tế và cache trong bộ nhớ. Vì toàn bộ ứng dụng kết thúc nếu driver chết, cấu hình bộ nhớ và HA của driver trở thành điểm quản lý quan trọng trong vận hành.
C. Đánh giá trễ và thực thi DAG
Phép toán Spark chia thành transformation và action. Các transformation như map, filter, join không thực thi ngay mà chỉ tích lũy dòng dõi; toàn bộ kế hoạch chỉ thực thi khi một action như count, collect, save được gọi. Nhờ đánh giá trễ này, engine có thể xem toàn bộ chuỗi phép toán rồi thực hiện các tối ưu như loại bỏ cột thừa (column pruning), đẩy điều kiện xuống (predicate pushdown) và hợp nhất các biến đổi hẹp liên tiếp.
flowchart TD
READ["1. doc du lieu read()"] --> T1["2. filter (phu thuoc hep)"]
T1 --> T2["3. select (phu thuoc hep)"]
T2 --> SH["4. groupBy / join (phu thuoc rong / shuffle)"]
SH --> T3["5. agg tong hop"]
T3 --> ACT["6. write / collect (action)"]
ACT -.kich hoat.-> JOB["tao Job"]
JOB --> ST["chia Stage (theo ranh gioi shuffle)"]
ST --> TASK["tao Task (moi partition mot Task)"]
Khi thực thi, Spark chia một action thành Job, chia thành các Stage tại ranh giới nơi shuffle (phụ thuộc rộng) xảy ra, và chia mỗi Stage thành số Task bằng số partition. Phụ thuộc hẹp (narrow dependency), nơi một partition chỉ phụ thuộc vào một partition cha, có thể gom thành pipeline không cần shuffle, trong khi phụ thuộc rộng (wide dependency) kích hoạt shuffle phân phối lại dữ liệu qua nhiều partition. Phần lớn công việc tinh chỉnh hiệu năng chính là giảm số lần shuffle và lượng dữ liệu shuffle này.
3. Stack thư viện hợp nhất và các mô hình xử lý
A. Spark SQL với Catalyst và Tungsten
Spark SQL hợp nhất SQL và DataFrame API trên cùng một engine thực thi. Truy vấn được bộ tối ưu Catalyst biến đổi qua các giai đoạn kế hoạch logic → kế hoạch logic tối ưu → nhiều kế hoạch vật lý → lựa chọn dựa trên chi phí, còn Tungsten nâng hiệu suất CPU và bộ nhớ bằng sinh mã toàn giai đoạn (whole-stage code generation) và quản lý bộ nhớ off-heap. Ví dụ, áp điều kiện WHERE dt='2026-10-31' lên 1 TB log Parquet lưu trong S3 khiến Catalyst đẩy điều kiện xuống giai đoạn quét nguồn (predicate pushdown) và dùng cắt tỉa partition (partition pruning) để chỉ đọc thư mục ngày cần thiết, giảm I/O thực tế xuống mức vài GB.
B. Structured Streaming
Structured Streaming mô hình hóa stream như một "bảng vô hạn luôn lớn lên," cho phép tái sử dụng gần như nguyên vẹn mã DataFrame dùng trong batch lên stream. Nó chạy ở chế độ micro-batch hoặc xử lý liên tục, xử lý dữ liệu đến trễ bằng cửa sổ theo thời gian sự kiện (event time) và watermark, và cung cấp ngữ nghĩa xử lý chính xác một lần (exactly-once) bằng checkpoint và log ghi trước (WAL). Chẳng hạn, một pipeline tổng hợp sự kiện thanh toán từ Kafka theo cửa sổ 5 phút để phát hiện giao dịch bất thường có thể được cài đặt bằng cùng mã tổng hợp với batch ban đêm—đây là cốt lõi của đơn giản hóa vận hành.
C. MLlib và GraphX
MLlib cung cấp các thuật toán học máy phân tán cho phân loại, hồi quy, phân cụm, lọc cộng tác cùng API pipeline, phân tán việc huấn luyện ngay cả trên dữ liệu đặc trưng vài TB. GraphX (và GraphFrames) phân tán các phép toán đồ thị như PageRank và tìm thành phần liên thông. Tuy nhiên, vì bản thân việc huấn luyện học sâu phù hợp với framework chuyên GPU hơn Spark, thực tế phổ biến là thực hiện tiền xử lý và tạo đặc trưng quy mô lớn trong Spark rồi chuyển việc huấn luyện sang cluster GPU riêng.
D. Quản lý bộ nhớ và shuffle
Bộ nhớ Executor được quản lý hợp nhất qua vùng thực thi (execution) và vùng cache lưu trữ (storage) (Unified Memory Management), và hai vùng mượn không gian của nhau khi cần. Ở giai đoạn shuffle, đầu ra map được ghi ra đĩa rồi gửi sang phía reduce, nên nếu lệch dữ liệu (data skew, giá trị tập trung vào một khóa nhất định) xảy ra, một số task làm trễ cả Stage. Để giảm nhẹ, AQE (Adaptive Query Execution) của Spark 3.x tự động điều chỉnh số partition shuffle từ thống kê thời gian chạy, chia tách join bị lệch, và chuyển các join nhỏ thành broadcast.
4. So sánh và các trường hợp ứng dụng
Vị trí của Spark trở nên rõ ràng qua so sánh với MapReduce và các engine stream thuần túy. Bảng dưới là tóm tắt bổ trợ; lý do khác biệt được giải thích ở các câu tiếp theo.
| Phân loại | MapReduce | Apache Spark | Apache Flink |
|---|---|---|---|
| Mô hình xử lý | Batch dựa trên đĩa | Batch dựa trên bộ nhớ + stream micro-batch | Stream gốc (batch là trường hợp đặc biệt) |
| Kết quả trung gian | Ghi vào HDFS | Giữ trong bộ nhớ, đĩa tùy chọn | Dựa trên trạng thái (state) |
| Tính toán lặp | Chậm (I/O mỗi giai đoạn) | Nhanh (tái dùng cache) | Nhanh |
| Độ trễ | Cao (phút) | Trung bình (giây đến phút) | Thấp (ms đến giây) |
| Dụng chính | Batch quy mô lớn | Batch / ETL / ML / cận thời gian thực hợp nhất | Thời gian thực độ trễ thấp |
Lý do Spark nhanh hơn MapReduce vài đến hàng chục lần ở tính toán lặp là nó thay vòng lặp đĩa bằng tái sử dụng bộ nhớ. Ngược lại, lý do Flink có lợi thế ở stream độ trễ cực thấp là micro-batch truyền thống của Spark vốn mang độ trễ bằng ranh giới batch, trong khi Flink lấy stream gốc cấp bản ghi và quản lý trạng thái tinh vi làm mặc định. Do đó hàm ý thực tiễn được rút ra: Spark có lợi nếu mục tiêu là "hợp nhất mọi thứ vào một," còn Flink có lợi nếu mục tiêu là "xử lý sự kiện cấp mili giây."
Như một trường hợp ứng dụng cụ thể, các hãng thương mại lớn trong và ngoài nước tổng hợp hàng chục tỷ log nhấp chuột và đặt hàng mỗi ngày bằng Spark batch để tạo đặc trưng cho gợi ý sản phẩm, đồng thời tính các chỉ số tồn kho và thanh toán gian lận thời gian thực bằng Structured Streaming theo cấu trúc giống Lambda. Trong tài chính, có báo cáo về các trường hợp quyết toán và tổng hợp rủi ro trên vài TB dữ liệu giao dịch mỗi đêm bằng Spark SQL, ổn định thời gian hoàn thành batch bằng cách để AQE tự động hấp thụ lệch shuffle tăng vọt vào giờ cao điểm cuối tháng. Các nhà mạng viễn thông tiền xử lý CDR (bản ghi chi tiết cuộc gọi) của hàng chục triệu thuê bao bằng Spark để làm mới các đặc trưng dự đoán rời bỏ dựa trên MLlib theo ngày.
5. Chuyên sâu: Spark trong kỷ nguyên lakehouse và AI, cùng xu hướng gần đây
Gần đây Spark đã tiến hóa vượt khỏi một engine xử lý đơn thuần để trở thành lớp xử lý của data lakehouse. Kết hợp với các định dạng bảng mở như Delta Lake, Apache Iceberg và Hudi, nó hỗ trợ giao dịch ACID, tiến hóa schema và time travel, qua đó cài đặt trên object storage những cập nhật đáng tin cậy vốn chỉ có ở data warehouse trước đây. Ví dụ, thực hiện MERGE (upsert) vào bảng Iceberg bằng Spark quản lý xung đột ghi đồng thời qua cô lập snapshot, và một lần nạp lỗi có thể được khôi phục về snapshot trước.
Về mặt vận hành, xu hướng Spark on Kubernetes thay thế YARN rất rõ nét. Kết hợp với cấp phát tài nguyên động (Dynamic Resource Allocation) của Kubernetes, nó tự động trả lại Executor nhàn rỗi để cắt chi phí đám mây, và bổ trợ độ ổn định bằng các kỹ thuật lưu riêng dữ liệu shuffle vào storage ngoài khi spot instance bị thu hồi. Thêm vào đó, Spark serverless (dịch vụ được quản lý) và các môi trường notebook tương tác (Databricks, EMR, Dataproc) đã trở nên phổ biến, giảm gánh nặng vận hành cluster.
Bước vào kỷ nguyên AI, vai trò của Spark như trung tâm tiền xử lý của pipeline AI tạo sinh đã lớn lên. Việc tuyển chọn dữ liệu huấn luyện LLM quy mô lớn như khử trùng lặp, lọc và token hóa hàng tỷ tài liệu, tạo embedding theo lô, và tiền xử lý chunk cho RAG được thực hiện bằng Spark. Ở các phiên bản gần đây, khả năng tiếp cận được cải thiện qua Spark Connect (kiến trúc tách client-server) cho phép client nhẹ kết nối tới cluster Spark từ xa, và pandas API on Spark chạy mã pandas một node theo cách phân tán. Dẫu vậy, việc phân chia vai trò giao tìm kiếm tương tự vector hoặc suy luận chuyên GPU cho các engine chuyên dụng vẫn được khuyến nghị.
6. Cân nhắc và hàm ý
Từ góc nhìn của Kỹ sư chuyên nghiệp, việc áp dụng và vận hành Spark cần cân nhắc toàn diện những điều sau.
Thứ nhất (chiến lược áp dụng), lựa chọn "hợp nhất hay chuyên biệt hóa" phải đi trước. Spark mạnh ở việc gom batch, ETL, cận thời gian thực và ML vào một, nhưng nếu thời gian thực cấp mili giây là trọng tâm thì hợp lý khi chạy kèm Flink, và nếu truy vấn OLAP độ trễ cực thấp là trọng tâm thì chạy kèm engine SQL phân tán. Nên đặt Spark làm engine trung tâm dựa trên tải chủ đạo của tổ chức đồng thời phân biệt rõ các engine bổ trợ, để tránh sự kém hiệu quả của hợp nhất thái quá.
Thứ hai (đánh đổi), phải nhìn cân bằng lợi ích và chi phí của tái sử dụng bộ nhớ. Cache tăng tốc tính toán lặp nhưng cache quá mức gây gánh nặng GC và OOM (hết bộ nhớ). Lạm dụng collect ở driver, số partition phi thực tế, và lệch dữ liệu là các nguyên nhân lỗi điển hình, nên cần phản ánh AQE, broadcast join, phân vùng hợp lý và phân tán khóa lệch (salting) ngay ở giai đoạn thiết kế.
Thứ ba (chi phí và vận hành), quản trị chi phí đám mây rất quan trọng. Có thể giảm mạnh chi phí bằng cách giảm lượng quét với cấp phát động Spark on Kubernetes, tận dụng spot instance, và data skipping (phân vùng, Z-Ordering). Từ góc nhìn FinOps, phải kết hợp khả năng nhìn rõ chi phí theo từng job với chính sách tự động mở rộng thì vận hành mới bền vững.
Thứ tư (độ tin cậy và quản trị dữ liệu), kết hợp với các định dạng bảng lakehouse, nên tích hợp ACID, tiến hóa schema, dòng dõi dữ liệu (lineage) và kiểm định chất lượng vào pipeline. Chỉ theo đuổi tốc độ xử lý mà bỏ qua chất lượng và quản trị dữ liệu có nguy cơ sản xuất hàng loạt "kết quả nhanh nhưng sai."
Thứ năm (công nghệ liên kết và triển vọng), Spark hình thành xương sống xử lý của nền tảng dữ liệu hiện đại khi kết hợp với Kafka (thu nạp), các định dạng bảng mở (lưu trữ), MLOps/feature store (sử dụng) và điều phối (Airflow). Về sau, việc làm nhẹ dựa trên Spark Connect, khả năng tương tác với các tải vector và AI, và serverless hóa sẽ sâu sắc hơn, và Kỹ sư chuyên nghiệp cần định vị Spark không phải là "engine vạn năng" mà là "lớp xử lý hợp nhất," thiết kế sự phân chia vai trò với các engine chuyên dụng từ góc nhìn kiến trúc.
Tài liệu tham khảo
- Tài liệu chính thức Apache Spark, https://spark.apache.org/docs/latest/
- M. Zaharia et al., "Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing", NSDI 2012, https://www.usenix.org/system/files/conference/nsdi12/nsdi12-final138.pdf
- Apache Spark, "Structured Streaming Programming Guide", https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html
- Databricks, "Adaptive Query Execution", https://www.databricks.com/blog/2020/05/29/adaptive-query-execution-speeding-up-spark-sql-at-runtime.html
Tóm tắt một câu: Apache Spark là nền tảng xử lý dữ liệu phân tán hợp nhất batch, SQL, streaming và ML trên một engine thông qua các trừu tượng RDD/DataFrame, khôi phục lỗi dựa trên dòng dõi, đánh giá trễ với tối ưu DAG và tái sử dụng bộ nhớ, và đang tiến hóa thành xương sống xử lý của lakehouse và tiền xử lý AI.