BigQuery 13 — Đọc Execution Graph (biểu đồ thực thi)
Vì sao cần một biểu đồ thay vì một bảng số
Ở bài trước — Query plan & Execution details bạn đã học cách đọc query plan dưới dạng bảng: mỗi stage một hàng, với các cột recordsRead, slotMs, waitMs, computeMs… Dạng bảng rất tốt để tra cứu con số chính xác, nhưng nó có một nhược điểm: bạn không thấy được hình dạng của query. Stage 5 phụ thuộc stage nào? Dữ liệu chảy từ đâu tới đâu? Chỗ nào là nút thắt cổ chai? Muốn biết, bạn phải tự ghép inputStages trong đầu — mệt và dễ sai với query nhiều chục stage.
BigQuery console thế hệ mới bổ sung tab Execution graph (biểu đồ thực thi): cùng dữ liệu query plan đó, nhưng vẽ thành một DAG trực quan — mỗi node là một stage, mỗi cạnh là luồng dữ liệu (shuffle) giữa các stage. Chỉ liếc qua, bạn thấy ngay cây thực thi: bảng nào được đọc, join xảy ra ở đâu, node nào "đỏ" vì tốn slot. Đây là cách nhanh nhất để hiểu query đang làm gì trước khi soi vào từng con số.
Mô hình tinh thần: Dạng bảng (bq-12) trả lời "bao nhiêu"; Execution graph trả lời "hình dạng ra sao, chảy đi đâu". Hai tab bổ trợ nhau — graph để định hướng, bảng để đo đạc. Bài này tập trung vào graph; đọc sâu từng thông số node để lại cho bài kế — Các pha & thông số của stage.
DAG: node là stage, cạnh là shuffle
Trước khi nhìn graph thật, cần nắm đúng ngữ nghĩa của hai thành phần:
- Node = một stage. Mỗi stage là một nhóm công việc thực hiện song song trên nhiều worker (parallel inputs). Node hiển thị
namestage (ví dụS00: Input,S04: Join+), phần trăm slot-time nó chiếm, và số records vào/ra. - Cạnh = luồng dữ liệu giữa hai stage. Khi stage A đổ kết quả cho stage B, dữ liệu được shuffle (ghi ra lớp trung gian rồi phân phối lại) qua mạng Jupiter. Cạnh trong graph chính là quan hệ
inputStagestrong query plan: B có A tronginputStagesthì có mũi tên A → B. Con số ghi trên cạnh là số records chảy qua (khớprecordsWrittencủa stage nguồn ≈recordsReadcủa stage đích).
Chiều đọc: từ dưới/đầu vào (input stages đọc bảng) đi lên đầu ra (output stage trả kết quả). Input stages là các node không có cạnh vào (leaf) — chúng chạy step READ trên một bảng gốc. Output là node không có cạnh ra, thường tên Output, gom kết quả cuối cùng.
Graph trên là một query join transactions với customers rồi GROUP BY chi nhánh theo tháng. Chỉ nhìn hình, bạn đọc được câu chuyện: hai bảng được đọc (S00, S01) → join lại (S02) → gộp nhóm hai pha (S03 partial, S04 final) → ghi ra (S05). Node đỏ đậm (S00, S02) là nơi tốn slot nhất; node xanh (S05) gần như miễn phí. Đó là toàn bộ ý tưởng.
Màu và độ đậm node: đọc nhiệt độ query
Console tô màu/độ đậm node theo mức độ tốn tài nguyên, thường là slot-time (slotMs) hoặc thời gian của stage — stage càng "nặng" node càng đậm/đỏ. Đây là bản đồ nhiệt (heatmap) để mắt bạn nhảy ngay tới chỗ đáng lo mà không cần đọc số.
| Sắc độ node | Ý nghĩa thường gặp | Phản xạ khi thấy |
|---|---|---|
| Đỏ / đậm nhất | Stage chiếm phần lớn slotMs của cả job | Đây là nghi phạm số 1 — soi kỹ ở graph rồi mở thông số |
| Cam / trung bình | Stage tốn vừa phải | Xem xét nếu tối ưu được stage đỏ mà chưa đủ |
| Xanh / nhạt | Stage nhẹ, gần như miễn phí | Bỏ qua, đừng phí công tối ưu |
Quan trọng: màu là tương đối trong một job, không phải tuyệt đối giữa các query. Một node đỏ trong query nhẹ có thể vẫn rẻ hơn node xanh của query khổng lồ. Màu chỉ giúp bạn xếp thứ tự ưu tiên bên trong một lần chạy. Muốn con số thật để so sánh liên-job, bạn quay lại dạng bảng ở bq-12 hoặc truy vấn INFORMATION_SCHEMA.JOBS.
Phóng to một node: đọc gì trên mỗi stage
Click (hoặc hover) vào một node, console bung ra thẻ thông số của stage đó. Đây là "danh thiếp" của node — mô phỏng cách hiển thị (tên stage + %slot + records + steps):
┌─────────────────────────────────────────────────────────┐
│ S02: Join+ ▓▓▓▓▓▓▓░░ 38% │ ← tên stage + thanh %slot-time của job
├─────────────────────────────────────────────────────────┤
│ Records read 49.2M Records written 48.0M │ ← recordsRead / recordsWritten
│ Parallel inputs 1,024 Completed 1,024 │ ← parallelInputs / completedParallelInputs
│ Slot time 41,800 ms │ ← slotMs (đóng góp vào %slot ở trên)
│ Shuffle out 6.1 GB Spilled 0 B │ ← shuffleOutputBytes / ...BytesSpilled
├─────────────────────────────────────────────────────────┤
│ Steps │
│ • READ FROM __stage00_output (transactions) │
│ • READ FROM __stage01_output (customers) │
│ • JOIN HASH JOIN ON customer_id │
│ • WRITE TO __stage02_output │
└─────────────────────────────────────────────────────────┘
Cùng thông tin ấy, khi vẽ trong graph, mỗi node được chú thích gọn — dưới đây là bản "phóng to" node S02 dạng Mermaid:
Ý nghĩa nhanh của các field trên thẻ (chi tiết ngưỡng cảnh báo để dành cho bq-14):
| Field | Ý nghĩa | Đọc gì từ nó |
|---|---|---|
recordsRead / recordsWritten | Số bản ghi vào / ra stage | So sánh in vs out biết stage lọc/nở dữ liệu bao nhiêu |
parallelInputs / completedParallelInputs | Số đơn vị việc song song / đã xong | Chênh nhau = còn đang chạy hoặc bị chờ |
slotMs | Tổng slot-time stage tiêu thụ | Nguồn của màu node; số càng lớn node càng đỏ |
shuffleOutputBytes | Bytes ghi ra lớp shuffle để chuyển stage sau | Lớn = nhiều dữ liệu chảy qua mạng |
shuffleOutputBytesSpilled | Phần shuffle phải tràn xuống đĩa | > 0 là cờ đỏ — thiếu bộ nhớ/slot, cần chú ý |
Đọc số records trên cạnh: nơi dữ liệu nở ra hay co lại
Con số trên cạnh là thứ kể chuyện rõ nhất về "chuyện gì đang xảy ra với dữ liệu". Đọc dọc theo luồng, bạn thấy dữ liệu nở ra (join tạo tích, nhân bản) hay co lại (filter, aggregate gộp nhóm):
Cách đọc luồng records này cho bạn ba tín hiệu quan trọng:
- Số co lại đột ngột ở cạnh vào một node aggregate (48.0M → 12.4K) là bình thường và tốt:
GROUP BYđang gộp nhóm. Nếu số không co ở nơi bạn mong nó co, có thể khóa nhóm sai hoặc thiếu pre-aggregation. - Số nở ra sau một join (ví dụ 48M → 300M) cảnh báo join fan-out — quan hệ nhiều-nhiều hoặc thiếu điều kiện join, tạo ra tích khổng lồ. Đây là nguyên nhân kinh điển khiến query nổ slot.
- Số vào input stage lớn hơn nhiều số ra nghĩa là stage đọc bảng đang quét thừa rồi mới FILTER. Cờ này gợi ý thiếu phân vùng hoặc clustering để cắt bytes ngay từ đầu.
Fan-in tại join/aggregate và bước repartition
Hai hình dạng bạn phải nhận ra ngay trên graph:
Fan-in — nhiều cạnh cùng đổ vào một node. Xảy ra ở join (hai nhánh bảng gặp nhau) hoặc aggregate final (nhiều stage partial gộp về một stage cuối). Fan-in là điểm dữ liệu hội tụ, thường là node đậm màu vì phải xử lý tất cả đầu vào. Trong graph tổng ở đầu bài, S02 (join) và S04 (final aggregate) đều là điểm fan-in.
Repartition — một bước phân phối lại dữ liệu giữa các stage để chuẩn bị cho phép toán sau. Ví dụ trước một GROUP BY branch, BigQuery cần đưa mọi bản ghi cùng branch về cùng một worker, nên chèn một stage Repartition (băm theo khóa rồi shuffle lại). Trên graph, repartition hiện ra như một node trung gian mảnh, với shuffleOutputBytes lớn nhưng records vào ≈ records ra (không lọc gì, chỉ xếp lại chỗ).
Nhận ra repartition quan trọng vì nó là chi phí ẩn: nó không tính toán logic nghiệp vụ nào, chỉ di chuyển dữ liệu qua Jupiter. Nếu bảng đã được clustered đúng khóa join/nhóm, BigQuery có thể giảm shuffle này. Nhìn thấy một node repartition khổng lồ ngay dưới một join là gợi ý điển hình để cân nhắc clustering.
Quy trình 6 bước: nhìn graph này là hiểu query làm gì
Gộp lại thành một checklist để bạn áp dụng mỗi khi mở tab Execution graph:
- Tìm các leaf (input stages) — node không có cạnh vào. Đọc step
READcủa chúng để biết query chạm những bảng gốc nào và đọc bao nhiêu records. - Đi ngược lên output — theo mũi tên tới node không có cạnh ra (
Output). Đây là "mạch truyện" của query. - Quét màu — node nào đỏ/đậm nhất? Đó là nơi tốn slot nhất, ưu tiên số 1.
- Đọc số trên cạnh — dữ liệu nở ra ở đâu (coi chừng join fan-out), co lại ở đâu (aggregate hoạt động).
- Đánh dấu fan-in & repartition — mỗi join/aggregate-final là một điểm hội tụ; mỗi repartition là shuffle tốn kém có thể tối ưu bằng clustering.
- Zoom node đỏ — mở thẻ thông số của node nặng nhất, kiểm nhanh
shuffleOutputBytesSpilled > 0và chênhparallelInputschưa hoàn tất.
Xong 6 bước, bạn đã có giả thuyết về việc query làm gì và điểm nghẽn ở đâu — mà chưa cần đọc một bảng số nào. Bước tiếp theo là định lượng giả thuyết đó: mở từng node và đọc các pha thời gian (waitMs, readMs, computeMs, writeMs) cùng dấu hiệu skew, được trình bày ở bq-14 — Các pha & thông số của stage; rồi biến chẩn đoán thành hành động tối ưu ở bq-15 — Chẩn đoán & tinh chỉnh query.
Execution graph khác gì dạng bảng (bq-12)
| Execution graph (bài này) | Execution details dạng bảng (bq-12) | |
|---|---|---|
| Hình thức | DAG trực quan, node + cạnh | Bảng, mỗi stage một hàng |
| Mạnh ở | Thấy hình dạng: phụ thuộc, luồng, fan-in, nút nóng | Đọc con số chính xác từng field |
| Đọc nhanh | Rất nhanh — liếc là thấy nút thắt | Chậm hơn — phải tự ghép inputStages |
| Màu/heatmap | Có — theo slot-time | Không (chỉ số thô) |
| Records trên cạnh | Có, nhìn thấy luồng | Có nhưng dạng cột rời recordsRead/Written |
| Khi nào dùng | Định hướng, tìm nghi phạm | Đo đạc, so sánh, trích số để báo cáo |
Cả hai tab đọc cùng một query plan (cùng mảng job_stages); chúng chỉ là hai cách trình bày. Thói quen tốt: bắt đầu bằng graph để định hướng, rồi nhảy sang bảng (hoặc thẻ node) khi cần con số.
Use case thực tế
Đội Data của NCB nhận cảnh báo một report cuối ngày về doanh số theo chi nhánh chạy chậm bất thường: bình thường ~40 giây, nay lên gần 5 phút. Analyst mở tab Execution graph của job.
Chỉ liếc graph, họ thấy ngay một node đỏ rực ở giữa với nhãn Join+, và điều bất thường là cạnh ra của nó ghi 1.4B records trong khi hai cạnh vào chỉ 48M (transactions) và 1.2M (customers). Dữ liệu nở gần 30 lần sau join — dấu hiệu kinh điển của join fan-out. Zoom node đỏ, thẻ thông số cho thấy shuffleOutputBytesSpilled đã lớn hơn 0 (shuffle tràn đĩa) và slotMs chiếm ~72% cả job.
Nguyên nhân: một thay đổi ở bảng customers khiến customer_id bị nhân bản (có bản ghi trùng do lỗi cập nhật), biến join 1-nhiều thành nhiều-nhiều. Fix bằng cách khử trùng customers trước khi join (thêm QUALIFY ROW_NUMBER() ... = 1). Sau sửa, cạnh ra của node join về lại 48M, node từ đỏ chuyển cam, thời gian chạy về ~38 giây. Toàn bộ chẩn đoán ban đầu chỉ mất một cái liếc vào con số trên cạnh — đúng thứ dạng bảng khó thấy ngay.
Ghi nhớ
- Execution graph = DAG: node là stage, cạnh là shuffle/luồng dữ liệu; đọc từ input stages (leaf, READ bảng) đi lên output.
- Màu/độ đậm node thể hiện mức tốn tài nguyên (thường
slotMs), tương đối trong một job — dùng để xếp ưu tiên, không so sánh liên-job. - Số trên cạnh là số records: nở ra sau join = coi chừng fan-out; co lại trước aggregate = gộp nhóm bình thường.
- Fan-in = nhiều cạnh đổ vào một node (join hoặc aggregate-final); repartition = node trung gian băm-shuffle lại dữ liệu, chi phí ẩn có thể giảm bằng clustering.
shuffleOutputBytesSpilled > 0trên thẻ node là cờ đỏ thiếu bộ nhớ/slot.- Quy trình: tìm leaf → lên output → quét màu → đọc cạnh → đánh dấu fan-in/repartition → zoom node đỏ.
- Graph để định hướng, bảng bq-12 để đo đạc — cùng một query plan, hai cách trình bày.
- Sau khi có giả thuyết, định lượng bằng các pha thời gian ở bq-14 rồi tối ưu ở bq-15.
Nguồn tham khảo
- BigQuery — Query plan and timeline / execution details: https://cloud.google.com/bigquery/docs/query-plan-explanation
- BigQuery documentation (tổng quan): https://cloud.google.com/bigquery/docs
- INFORMATION_SCHEMA JOBS (job_stages, các cột plan): https://cloud.google.com/bigquery/docs/information-schema-jobs
- Best practices — performance overview: https://cloud.google.com/bigquery/docs/best-practices-performance-overview
- Clustered tables (giảm shuffle/repartition): https://cloud.google.com/bigquery/docs/clustered-tables
- Partitioned tables (cắt bytes ở input stage): https://cloud.google.com/bigquery/docs/partitioned-tables
- "Dremel: Interactive Analysis of Web-Scale Datasets", Melnik et al., VLDB 2010
- "Google BigQuery: The Definitive Guide", Lakshmanan & Tigani, O'Reilly
Bài viết liên quan
So sánh các định dạng dữ liệu (CSV, JSON, XML, Avro, Parquet, ORC) và lý do lưu theo cột nhanh hơn cho phân tích. Bài đi sâu vào row vs columnar storage, nén (Snappy/gzip/zstd), schema evolution, OLTP vs OLAP, object storage và partitioning để tối ưu chi phí lẫn tốc độ truy vấn.
Data Engineering là ngành xây dựng và vận hành hệ thống biến dữ liệu thô thành dữ liệu sạch, tin cậy, sẵn sàng cho phân tích và AI. Bài giới thiệu vai trò Data Engineer trong vòng đời dữ liệu (nguồn → ingestion → storage → transformation → serving), phân biệt với Analyst/Scientist/ML Engineer, bức tranh hệ sinh thái công cụ và bài toán đưa dữ liệu core banking sang kho phân tích.
Stream processing là gì, khác biệt batch vs stream (bounded/unbounded), micro-batch (Spark) vs true streaming, Apache Flink là gì và định vị so với Spark Structured Streaming và Kafka Streams. Kiến trúc runtime JobManager/TaskManager, các tầng API, triết lý streaming-first và bối cảnh phát hiện gian lận ngân hàng.
Vì sao một máy không đủ và cần xử lý phân tán: từ MapReduce, Hadoop/HDFS đến Apache Spark in-memory. Kiến trúc driver–executor–cluster manager, các mức trừu tượng RDD/DataFrame/Dataset, cơ chế lazy evaluation với DAG, và vì sao shuffle (wide dependency) là phần tốn kém nhất. Kèm PySpark, Spark SQL và các kỹ thuật tối ưu (partition, broadcast join, cache, chống skew) cùng khi nào KHÔNG nên dùng Spark.
Cảm nhận của bạn
Bình luận
Chưa có bình luận. Hãy là người đầu tiên chia sẻ!