BigQuery 8 — Cách tính tiền & bytes

15 thg 7, 2026 3 lượt xem
#data-engineering
#bigquery
#cost
#finops
#pricing

Vì sao phải hiểu cách BigQuery tính tiền

BigQuery bán một trải nghiệm gần như kỳ diệu: bạn gõ một câu SQL, không cần dựng cluster, không cần chọn kích cỡ máy, và vài giây sau có kết quả trên hàng terabyte dữ liệu. Nhưng chính vì không có "cái nút bật máy" nào để bạn nhìn thấy, nên mỗi câu query là một khoản chi tiêu vô hình — và với mô hình on-demand mặc định, một câu SELECT * vô ý trên bảng lịch sử giao dịch có thể quét vài TB và đội hóa đơn lên đáng kể chỉ trong một lần nhấn Enter.

Với một ngân hàng như NCB, nơi hàng trăm analyst, mô hình rủi ro và pipeline ETL cùng đâm vào một project BigQuery, câu hỏi không phải "hệ thống chạy được không" mà là "câu query này quét bao nhiêu bytes, và ai đang trả cho nó". Bài này xây mô hình tinh thần về chi phí BigQuery tới từng khái niệm gốc — bytesslot-time — rồi trang bị các công cụ để ước lượng và chặn trước khi tiền thực sự chảy đi.

Lưu ý về đơn giá: bài này không trích đơn giá USD cụ thể vì giá thay đổi theo thời gian và theo region. Mọi con số tiền tệ hãy tra trực tiếp trang BigQuery pricing. Bài tập trung vào cơ chế tính tiền — thứ ít thay đổi và là chỗ bạn thực sự tối ưu được.

Hai dòng tiền tách biệt: storage và compute

Điều đầu tiên và quan trọng nhất: BigQuery tách rời storage và compute, cả về kiến trúc lẫn hóa đơn. Dữ liệu nằm trên Colossus (storage), còn truy vấn chạy trên Dremel (compute) — hai hệ thống riêng, tính tiền riêng. Bạn có thể lưu 100 TB mà không chạy query nào (chỉ trả storage), hoặc quét đi quét lại cùng một bảng nhỏ (chủ yếu trả compute). Hiểu chi tiết tách biệt này ở BigQuery — Storage & định dạng cột.

Storage pricing — âm thầm nhưng dễ hiểu

Storage tính theo dung lượng đã lưu (đơn vị GB/tháng), và BigQuery chia làm hai bậc:

  • Active storage: bảng (hoặc partition) có thay đổi trong 90 ngày gần nhất.
  • Long-term storage: bảng/partition không bị chỉnh sửa suốt hơn 90 ngày sẽ tự động chuyển sang giá long-term rẻ hơn — không cần thao tác gì, không giảm hiệu năng, không đổi cách truy vấn. Đây là "khuyến mãi tự động" cho dữ liệu lịch sử.

Một điểm cần nhớ về billing model của storage: BigQuery cho chọn giữa logical bytes (dung lượng chưa nén, theo kiểu dữ liệu) và physical bytes (dung lượng thực đã nén trên đĩa, gồm cả time travel & fail-safe). Chọn model nào rẻ hơn tùy độ nén của dataset — tra bảng so sánh trên trang pricing. Storage thường chiếm phần nhỏ hóa đơn so với compute, nên phần còn lại của bài tập trung vào compute.

Compute pricing — hai mô hình, hai đơn vị đo

Đây là chỗ tiền thực sự chảy, và BigQuery có hai mô hình tính compute khác hẳn nhau:

On-demandCapacity / Editions (reservations)
Đơn vị tính tiềnBytes billed (dữ liệu query quét)Slot-time (slot-hour / slot-second)
Trả theoMỗi query, theo lượng bytesSố slot bạn đặt trước, theo thời gian
Phù hợpWorkload thưa, khó đoán, mới bắt đầuWorkload đều, khối lượng lớn, cần chi phí dự đoán được
Rủi ro chi phíMột query quét nhiều = đắt ngayCố định theo reservation, không phụ thuộc bytes
Cách tối ưuGiảm bytes quétTăng hiệu quả dùng slot, xếp workload

Slot là đơn vị tính toán ảo của Dremel — chi tiết về slot và mô hình thực thi xem BigQuery — Slots & mô hình thực thi. Với mô hình capacity/Editions, bạn mua trước một số slot (reservation) và trả theo slot-time bất kể query quét bao nhiêu bytes; ở đây tối ưu nghĩa là dùng slot cho hiệu quả. Với mô hình on-demand — mặc định và phổ biến nhất khi bắt đầu — bạn trả theo bytes billed, nên toàn bộ nghệ thuật tối ưu chi phí quy về một câu: quét càng ít bytes càng tốt. Phần còn lại của bài đào sâu vế on-demand này.

bytes_processed vs bytes_billed — đừng nhầm hai con số

Hai thuật ngữ nghe giống nhau nhưng khác nhau, và bạn cần phân biệt rạch ròi:

  • totalBytesProcessed (bytes_processed): lượng dữ liệu BigQuery thực sự đọc và xử lý để trả lời query. Đây là con số kỹ thuật phản ánh query chạm vào bao nhiêu dữ liệu.
  • totalBytesBilled (bytes_billed): lượng bytes bạn bị tính tiền — đây mới là con số vào hóa đơn ở mô hình on-demand.

Vì sao hai số lệch nhau? Vì on-demand có làm tròn tối thiểu: mỗi query bị tính ít nhất một lượng bytes tối thiểu (mức sàn nhỏ, ngay cả query quét gần như không có gì), và bytes billed được làm tròn lên theo đơn vị. Ngoài ra:

  • Query cache trúng (cacheHit = true): bytes_billed = 0miễn phí hoàn toàn (xem mục cache bên dưới).
  • Với query rất nhỏ, bytes_billed có thể lớn hơn bytes_processed vì sàn tối thiểu.
  • Với query bị lỗi hoặc LIMIT mà vẫn phải quét cả bảng, bytes_processed phản ánh dữ liệu quét thật — LIMIT không làm giảm bytes quét (xem cảnh báo dưới).

Quy tắc thực dụng: muốn ước lượng hóa đơn on-demand, nhìn bytes_billed; muốn hiểu query nặng hay nhẹ, nhìn bytes_processed.

Vì sao chọn cột và partition giảm bytes billed

BigQuery lưu dữ liệu ở định dạng cột (Capacitor trên Colossus). Hệ quả tài chính trực tiếp: khi bạn SELECT một vài cột, BigQuery chỉ đọc các cột đó, không đụng tới phần còn lại của bảng. Đây là lý do câu khuyên "đừng bao giờ SELECT *" không phải chuyện phong cách mà là chuyện tiền bạc — SELECT * buộc đọc mọi cột, kể cả cột TEXT lớn bạn chẳng cần.

Tầng giảm bytes thứ hai là partition: nếu bảng được phân vùng theo ngày (ví dụ theo txn_date), một câu query có điều kiện WHERE txn_date phù hợp sẽ cắt tỉa partition (partition pruning) — BigQuery chỉ đọc các partition khớp, bỏ qua toàn bộ phần còn lại trước khi tính bytes. Chi tiết cơ chế ở BigQuery — Phân vùng bảng (Partitioning).

Con số trong sơ đồ là minh hoạ định tính để thấy độ dốc: đi từ SELECT * toàn bảng xuống chọn cột giảm hàng chục lần, rồi thêm partition pruning giảm tiếp hàng chục lần nữa. Cùng một câu hỏi nghiệp vụ, cùng một kết quả, nhưng bytes billed — tức tiền — chênh nhau hai bậc độ lớn.

Cảnh báo về LIMIT: LIMIT 100 không làm giảm bytes quét/billed ở on-demand. BigQuery vẫn phải đọc dữ liệu các cột được chọn (trên các partition khớp) rồi mới cắt lấy 100 dòng. Muốn giảm bytes phải giảm cộtpartition đọc vào, không phải giảm dòng xuất ra.

Dry-run — ước lượng bytes TRƯỚC khi chạy

Công cụ chống sốc hóa đơn quan trọng nhất: dry run. BigQuery cho bạn hỏi "câu query này sẽ quét bao nhiêu bytes" mà không thực sự chạy và không tốn tiền. Optimizer trả về ước lượng totalBytesProcessed dựa trên metadata.

Bằng bq CLI:

# Dry run: chỉ ước lượng bytes, không chạy, không tính tiền
bq query --use_legacy_sql=false --dry_run \
'SELECT customer_id, amount, txn_date
 FROM `ncb-dwh.core.transactions`
 WHERE txn_date BETWEEN "2026-06-01" AND "2026-06-30"'
# → "Query successfully validated. Assuming the tables are not modified,
#    running this query will process 15804112384 bytes of data."

Trong console UI, mỗi lần bạn gõ SQL, góc phải trên hiện sẵn dòng "This query will process X when run" — đó chính là dry-run tự động. Nhìn con số này trước mỗi query lớn là thói quen tiết kiệm tiền rẻ nhất. Trong pipeline, client library cũng có cờ dry-run (JobConfig(dry_run=True)) để ước lượng và log bytes trước khi thực thi thật.

maximum_bytes_billed — cầu dao chặn query đắt

Dry-run cảnh báo, nhưng con người vẫn có thể lỡ tay. maximum_bytes_billedcầu dao tự động: đặt một trần bytes, query nào vượt trần sẽ bị hủy ngay và không tính tiền, thay vì chạy tới cùng rồi mới thấy hóa đơn.

-- Đặt trần cho phiên: query nào quét quá 20 GB sẽ FAIL, không chạy, không tốn tiền
SET @@maximum_bytes_billed = 20000000000;  -- 20 GB

SELECT customer_id, SUM(amount) AS total_spent
FROM `ncb-dwh.core.transactions`
WHERE txn_date BETWEEN "2026-06-01" AND "2026-06-30"
GROUP BY customer_id;

Nếu câu trên thực tế quét 15 GB, nó chạy bình thường. Nếu ai đó sửa thành SELECT * không có WHERE khiến quét 2 TB, query bị chặn với lỗi vượt maximum_bytes_billed — cầu dao đã ngắt trước khi tiền chảy. Trần này có thể đặt ở nhiều cấp:

Cấp đặtCáchPhạm vi
Từng query--maximum_bytes_billed (bq CLI) hoặc JobConfigMột job
Phiên / scriptSET @@maximum_bytes_billed = ...Cả session
Mặc định projectCấu hình default trong project settingsMọi job trong project

Với môi trường ngân hàng, đặt trần mặc định ở cấp project (kết hợp custom cost control quota theo user/project) là hàng rào an toàn nên có ngay từ đầu. Giám sát tổng thể chi phí và cảnh báo bất thường xem BigQuery — Giám sát & chi phí.

Query cache — trúng cache là miễn phí

BigQuery tự động cache kết quả mỗi query. Nếu bạn (hoặc người khác trong cùng điều kiện) chạy lại đúng câu query đó và bảng nguồn không đổi, BigQuery trả kết quả từ cache: cacheHit = true, bytes_billed = 0hoàn toàn miễn phí và gần như tức thì.

Điều kiện để trúng cache (định tính, tra doc để đủ danh sách):

  • Text query giống hệt (kể cả khoảng trắng/comment cũng ảnh hưởng ở một số trường hợp).
  • Bảng nguồn không thay đổi kể từ lần chạy trước.
  • Query không chứa hàm phi tất định (CURRENT_TIMESTAMP(), RAND(), CURRENT_DATE()…) — vì kết quả khác nhau mỗi lần nên không cache được.
  • Không dùng wildcard table theo cách phá cache, không ghi ra bảng đích, không truy vấn nguồn external thay đổi liên tục.

Hệ quả thực dụng: dashboard làm mới mỗi 5 phút bằng đúng một câu query trên bảng chỉ cập nhật mỗi đêm sẽ trúng cache gần như cả ngày → chi phí gần như bằng 0. Ngược lại, thêm CURRENT_TIMESTAMP() vào query là vô tình tắt cache và trả tiền lại từ đầu mỗi lần.

Truy vết bytes đã tốn qua INFORMATION_SCHEMA

Sau khi query đã chạy, bạn kiểm tra bytes billed thực tế và soi ai đang tốn nhiều nhất qua INFORMATION_SCHEMA.JOBS:

-- Top 10 query tốn bytes billed nhất trong 7 ngày qua
SELECT
  job_id,
  user_email,
  total_bytes_billed,
  total_bytes_processed,
  cache_hit,
  ROUND(total_bytes_billed / POW(1024, 4), 3) AS tib_billed,
  creation_time,
  SUBSTR(query, 0, 80) AS query_preview
FROM `region-asia-southeast1`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
  AND job_type = 'QUERY'
  AND state = 'DONE'
ORDER BY total_bytes_billed DESC
LIMIT 10;

Cột total_bytes_billed là con số vào hóa đơn on-demand; so nó với total_bytes_processedcache_hit để hiểu query nào quét nhiều thật, query nào đã được cache miễn phí. Chi tiết các cột và cách khai thác job_stages xem BigQuery — Giám sát & chi phí.

Use case thực tế

Đội Data của NCB có một dashboard rủi ro tín dụng làm mới mỗi 15 phút, chạy trên bảng transactions 2 năm lịch sử (~2.4 TB). Bản đầu tiên dùng SELECT * ... WHERE 1=1 rồi lọc ở tầng BI, mỗi lần chạy quét gần 2.4 TB; với ~96 lần/ngày ở on-demand, đây là một trong những khoản tốn nhất project.

Ba can thiệp, không đổi kết quả nghiệp vụ:

  1. Chọn cột: chỉ lấy customer_id, amount, txn_date, risk_flag → bytes quét rớt từ 2.4 TB xuống ~180 GB/lần.
  2. Partition pruning: bảng phân vùng theo txn_date, dashboard chỉ cần 90 ngày gần nhất → mỗi lần chỉ đọc ~45 GB.
  3. Query cache: bảng chỉ nạp mới lúc 2h sáng; bỏ CURRENT_TIMESTAMP() khỏi query → sau lần chạy đầu buổi, cả ngày trúng cache, bytes_billed = 0 cho hầu hết lần làm mới.

Kết quả: từ ~230 TB billed/ngày (2.4 TB × 96) xuống còn ~45 GB thực quét cho lần chạy đầu tiên mỗi ngày, phần còn lại miễn phí nhờ cache — giảm bytes billed hơn 99%. Đội đặt thêm maximum_bytes_billed = 100 GB mặc định cho service account của dashboard làm cầu dao, và --dry_run bắt buộc trong CI cho mọi query mới trước khi lên production.

Ghi nhớ

  • BigQuery tách storage (Colossus, theo GB/tháng) và compute (Dremel) thành hai dòng tiền riêng; dữ liệu không đụng >90 ngày tự chuyển long-term storage rẻ hơn.
  • Compute có hai mô hình: on-demand trả theo bytes billed (có sàn tối thiểu + làm tròn), capacity/Editions trả theo slot-time.
  • Phân biệt bytes_processed (dữ liệu thực đọc) với bytes_billed (con số vào hóa đơn on-demand); cache trúng thì bytes_billed = 0.
  • Giảm bytes billed bằng chọn cột (định dạng cột chỉ đọc cột cần) và partition pruning (WHERE theo cột phân vùng) — có thể giảm hai bậc độ lớn.
  • LIMIT không giảm bytes quét; muốn rẻ phải cắt cột và partition đọc vào, không phải dòng xuất ra.
  • Dry-run ước lượng bytes miễn phí trước khi chạy; maximum_bytes_billed là cầu dao chặn query vượt trần, hủy trước khi tính tiền.
  • Query cache miễn phí hoàn toàn khi query giống hệt + bảng không đổi + không có hàm phi tất định.
  • KHÔNG tra đơn giá USD từ trí nhớ — luôn xem trang pricing chính thức vì giá đổi theo thời gian & region.

Nguồn tham khảo

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.

13 thg 7, 2026 10

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.

13 thg 7, 2026 9

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.

13 thg 7, 2026 8

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.

13 thg 7, 2026 7

Cảm nhận của bạn

Bình luận

Bạn cần để viết bình luận.

Chưa có bình luận. Hãy là người đầu tiên chia sẻ!