Spark Application
Một lần chạy Spark cụ thể: driver, executors, jobs, stages, tasks và lifecycle cơ bản của application.
Motivation
Sau Spark Execution Model, tớ có một mental model trước:
user mô tả computation bằng Spark abstraction
-> Spark giữ lại plan / lineage
-> action trigger execution
-> Spark chia work thành jobs, stages, tasksCâu hỏi tiếp theo là: phần execution đó sống ở đâu?
Đây là lúc cần tới khái niệm Spark Application.
Spark application không phải là API surface, cũng không phải chỉ là một job. Nó là một lần chạy cụ thể của một chương trình Spark, nơi driver, executors, jobs, stages và tasks cùng tồn tại trong một runtime boundary.
What Is a Spark Application?
Theo Learning Spark 2e:
A user program built on Spark using its APIs. It consists of a driver program and executors on the cluster.
Nói bằng lời của mình:
Spark application là toàn bộ một lần chạy chương trình Spark: từ lúc user program bắt đầu, tạo driver, xin executors, chạy jobs/tasks, cho tới khi application kết thúc.
Ví dụ mình có một file Python nhỏ:
from pyspark.sql import SparkSession
spark = SparkSession.builder.appName("example").getOrCreate()
df = spark.read.parquet("/data/events")
result = df.groupBy("country").count()
result.show()
spark.stop()Rồi chạy:
spark-submit app.pyNhìn bên ngoài nó giống một script Python. Nhưng từ góc nhìn của Spark, đây là một Spark application.
user program
-> Spark application
-> driver process
-> executor processes
-> jobs / stages / tasksWhy Application Before Lifecycle?
Tớ nghĩ nên gom Spark Application và Spark Application Lifecycle vào cùng một page vì hai phần này tách ra dễ bị lủng củng.
Lifecycle là vòng đời của một object. Nếu chưa rõ application là object gì, lifecycle sẽ bị nhảy cóc. Ngược lại, nếu chỉ định nghĩa application mà không nói nó bắt đầu/chạy/kết thúc ra sao, page lại thành glossary.
Nên flow hợp hơn là:
Spark Execution Model
-> Spark Application
-> application lifecycle
-> driver/executor
-> job/stage/taskDriver
Driver là process chạy chương trình chính và điều phối Spark application.
Trong ví dụ trên, những dòng như:
spark = SparkSession.builder.getOrCreate()
df = spark.read.parquet(...)
result = df.groupBy(...).count()
result.show()được bắt đầu từ driver.
Driver không phải nơi xử lý toàn bộ data. Với data lớn, driver không tự đọc hết data rồi xử lý. Driver giống chỗ điều phối hơn:
- tạo
SparkSession/SparkContext; - giữ thông tin chính của application;
- phân tích computation cần chạy;
- chia computation thành jobs, stages, tasks;
- nói chuyện với cluster manager để xin executors;
- gửi tasks xuống executors;
- nhận trạng thái và kết quả từ executors.
Cách nhớ:
Driver biết toàn bộ kế hoạch.
Executor làm từng phần việc cụ thể.Executors
Executors là các worker processes chạy trên cluster.
Executor nhận task từ driver, chạy task trên một phần dữ liệu, rồi báo kết quả lại. Ví dụ một dataset có nhiều partitions. Khi Spark chạy job, nó có thể tạo nhiều tasks, mỗi task xử lý một partition hoặc một phần dữ liệu tương ứng.
Executor thường làm các việc như:
- chạy task;
- giữ data cache nếu mình gọi
cache(); - giữ shuffle data trung gian;
- gửi heartbeat/status về driver.
Một điểm dễ nhầm:
node/machine != executorNode là máy/pod/container nơi process chạy. Executor là process Spark chạy trên node đó. Một node có thể có một hoặc nhiều executors tùy config và cluster manager.
Cluster Manager
Spark cần một hệ thống đứng ra cấp tài nguyên. Hệ đó gọi là cluster manager.
Cluster manager có thể là:
- Spark Standalone;
- YARN;
- Kubernetes;
- local mode trên máy mình.
Vai trò của cluster manager là quản lý resource và launch executors cho application. Nó không quyết định query chạy thành plan gì; driver mới là nơi điều phối execution logic.
Flow đơn giản:
spark-submit
-> driver starts
-> driver asks cluster manager for resources
-> cluster manager launches executors
-> executors connect back to driver
-> driver sends tasks to executorsJobs, Stages, and Tasks
Một Spark application không đồng nghĩa với một job.
Application là toàn bộ lần chạy Spark. Trong application đó có thể có nhiều jobs.
Job thường được tạo khi có action:
df.count()
df.collect()
df.show()
df.write.parquet("/output")Một job có thể được chia thành nhiều stages. Stage thường bị tách ra khi có shuffle, ví dụ groupBy, join, repartition.
Mỗi stage lại gồm nhiều tasks. Task thường tương ứng với một partition dữ liệu.
Application
-> Job
-> Stage 1
-> Task 1
-> Task 2
-> Stage 2
-> Task 3
-> Task 4Ở mức mới học, chưa cần tối ưu vội. Chỉ cần nhớ thứ tự này để đọc Spark UI đỡ rối:
Application > Job > Stage > TaskApplication Lifecycle
Ghép lại, một Spark application bình thường có lifecycle như sau:
spark-submit app.py
-> driver process starts
-> user code creates SparkSession
-> SparkContext starts
-> driver asks cluster manager for executors
-> executors start and connect back to driver
-> transformations build plan / lineage
-> action triggers job
-> job is split into stages
-> stages are split into tasks
-> executors run tasks
-> result/status goes back to driver
-> application finishes
-> SparkContext stops
-> executors stop
-> driver exitsNếu chỉ nhớ một flow trong page này thì nhớ flow này.
Local Mode vs Cluster Mode
Khi mới học Spark, mình có thể chạy local mode:
pyspark
# hoặc
spark-submit --master local[*] app.pyLúc này driver và executor-like worker threads có thể nằm trên cùng một máy. Nó tiện để học API và test logic.
Nhưng mental model vẫn nên giữ như cluster:
driver điều phối
worker/executor chạy taskVì khi chuyển sang cluster thật, code vẫn dựa trên model đó, chỉ khác là executor chạy trên nhiều process/machine hơn.
What Happens When Something Fails?
Ở mức cơ bản, chỉ cần phân biệt hai loại failure.
Nếu executor chết:
- task đang chạy trên executor đó fail;
- driver có thể retry task trên executor khác;
- cache/shuffle data trên executor đó có thể mất;
- application chưa chắc chết ngay.
Nếu driver chết:
SparkContextmất;- scheduling state mất;
- executors không còn nơi nhận lệnh;
- application thường fail.
Cách nhớ:
Executor chết: mất worker, có thể retry.
Driver chết: mất đầu não của application.What This Page Is Not Yet
Trang này chưa cần giải thích sâu:
- Catalyst optimizer;
- DataFrame vs Dataset vs RDD;
- shuffle internals;
- storage/file format;
- deployment mode chi tiết;
- performance tuning;
- Databricks Runtime.
Những phần đó sẽ đến sau. Ở đây chỉ cần chốt một điểm:
Spark application là runtime boundary của một lần chạy Spark program; lifecycle là cách boundary đó được tạo, chạy và kết thúc.
Next Questions
Sau page này, các câu hỏi tiếp theo nên là:
- driver và executor lifecycle chi tiết hơn ra sao;
- transformation/action biến thành job/stage/task như thế nào;
- partition và shuffle ảnh hưởng execution như thế nào;
SparkSessionvàSparkContextkhác nhau như thế nào;- Spark UI đọc như thế nào để hiểu application/job/stage/task.
My Summary
Sau khi hiểu Spark Execution Model, Spark Application là object runtime đầu tiên cần bám vào.
Spark execution model
-> user mô tả computation
Spark application
-> một lần runtime chạy computation đó
-> driver điều phối
-> executors chạy tasks
-> actions tạo jobs/stages/tasksVì vậy không cần tách Spark Application và Spark Application Lifecycle thành hai page riêng. Application là object; lifecycle là phần giải thích object đó sống/chạy/kết thúc thế nào.