BigQuery 3 — Lưu trữ cột (Capacitor)

15 thg 7, 2026 3 lượt xem
#data-engineering
#storage
#bigquery
#columnar
#capacitor

BigQuery 3 — Lưu trữ cột (Capacitor)

bài kiến trúc ta đã thấy BigQuery tách rời storage (Colossus) và compute (Dremel). Bài này zoom vào đúng chỗ dữ liệu "nằm nghỉ": định dạng lưu trữ cột Capacitor. Đây là mảnh ghép giải thích vì sao BigQuery quét được hàng terabyte trong vài giây, và — quan trọng với ví tiền — vì sao viết SELECT * lại đắt hơn nhiều so với chỉ chọn đúng cột bạn cần.

Mô hình tinh thần cần dựng trước: một bảng BigQuery không được lưu như một chồng "dòng" nối tiếp nhau. Nó được xé nhỏ theo từng cột, mỗi cột lưu và nén riêng. Khi truy vấn, engine chỉ chạm vào những cột xuất hiện trong câu lệnh. Mọi thứ trong bài — tốc độ, chi phí, mẹo tối ưu — đều chảy ra từ ý tưởng đó.


1. Row-oriented vs Column-oriented — khác biệt cốt lõi

Hãy tưởng tượng một bảng giao dịch ngân hàng transactions với các cột: txn_id, customer_id, amount, channel, created_at, description, device_id… tổng cộng 40 cột. Có hai cách đặt các ô dữ liệu này xuống đĩa.

Row-oriented (hàng) — như PostgreSQL/Oracle OLTP: các ô của cùng một dòng nằm cạnh nhau. Đọc một giao dịch đầy đủ (mọi cột) rất nhanh, chỉ cần một lần seek. Phù hợp cho OLTP: "lấy nguyên bản ghi khách hàng #123", "cập nhật một dòng".

Column-oriented (cột) — như Capacitor/Parquet/ORC: các ô của cùng một cột nằm cạnh nhau. Muốn tính SUM(amount) trên 1 tỉ dòng? Chỉ cần đọc đúng khối dữ liệu của cột amount, bỏ qua 39 cột còn lại. Phù hợp cho OLAP: quét rộng vài cột trên rất nhiều dòng.

Điểm mấu chốt: với câu hỏi phân tích điển hình — "tổng số tiền giao dịch theo kênh trong tháng 6" — bạn chỉ động tới 3 cột (amount, channel, created_at). Row-store buộc phải đọc qua cả 40 cột của mỗi dòng để nhặt ra 3 giá trị; column-store đọc thẳng 3 khối cột và không chạm phần còn lại.

Tiêu chíRow-orientedColumn-oriented (BigQuery/Capacitor)
Đơn vị lưu liền kềCả một dòngMột cột
Lấy nguyên 1 bản ghiRất nhanhPhải ghép nhiều cột lại
Quét vài cột trên tỉ dòngPhải đọc thừaChỉ đọc cột cần
Tỉ lệ nénTrung bìnhRất cao (dữ liệu đồng nhất)
Thế mạnhOLTP, ghi/sửa từng dòngOLAP, phân tích tổng hợp
Ví dụPostgres, MySQL, OracleBigQuery, Parquet, ORC, Snowflake

2. Capacitor — định dạng cột của BigQuery

Capacitor là định dạng lưu trữ cột độc quyền của Google, kế thừa và vượt qua ColumnIO trong bài báo Dremel gốc. Đây là định dạng BigQuery dùng để ghi mọi bảng managed lên Colossus. Bạn không thao tác trực tiếp với Capacitor — nó ẩn sau lớp API — nhưng hiểu nó giúp bạn viết query rẻ hơn.

Vài đặc điểm quan trọng của Capacitor:

  • Mỗi cột được mã hoá và nén độc lập. Vì dữ liệu trong một cột có cùng kiểu và thường lặp lại (ví dụ cột channel chỉ có vài giá trị: ATM, MOBILE, POS, IB), thuật toán nén đạt hiệu quả cực cao.
  • Encoding thông minh theo dữ liệu. Capacitor phân tích dữ liệu để chọn cách mã hoá tối ưu: dictionary encoding (thay chuỗi lặp bằng số nguyên nhỏ), run-length encoding (RLE — nén chuỗi giá trị giống nhau liên tiếp), và sắp xếp lại thứ tự dòng để tối đa hoá tính nén mà không đổi ngữ nghĩa bảng.
  • Hỗ trợ kiểu lồng/lặp (nested & repeated). Capacitor lưu cấu trúc STRUCT/ARRAY bằng kỹ thuật repetition & definition levels — nền tảng cho kiểu dữ liệu bán cấu trúc mà bài về nested & repeated sẽ đi sâu.
  • Tự tối ưu nền (background optimization). BigQuery âm thầm gộp file, tái nén, tối ưu bố cục dữ liệu sau khi ghi — bạn không phải chạy "VACUUM" hay "OPTIMIZE" thủ công.

Lớp metadata đi kèm là mảnh ghép then chốt: BigQuery lưu thống kê (min/max, số block, vị trí) cho từng cột, nên khi lập kế hoạch truy vấn nó biết chính xác cần đọc những block nào và ước lượng được lượng byte sẽ quét trước khi chạy. Đây là cơ sở cho dòng "This query will process X bytes when run" hiển thị trên console.


3. Chỉ đọc cột được chọn — nơi tiền được tiết kiệm

Đây là hệ quả trực tiếp và quan trọng nhất của lưu trữ cột. Với mô hình tính giá on-demand, BigQuery tính tiền theo số byte được quét trên các cột mà truy vấn tham chiếu — không phải theo kích thước cả bảng. Cột nào không xuất hiện trong SELECT, WHERE, JOIN, GROUP BY… thì engine không đọc, và bạn không trả tiền cho nó.

Giả sử bảng transactions có 40 cột, dung lượng logic 800 GB. Nếu 3 cột amount, channel, created_at chiếm khoảng 60 GB, thì truy vấn ở trên chỉ quét ~60 GB — chứ không phải 800 GB. Cùng câu hỏi đó nếu viết SELECT * sẽ quét gần như toàn bộ bảng.

3.1 SQL minh hoạ: cái giá của SELECT *

-- ❌ TỐN KÉM: kéo mọi cột, quét (gần) toàn bộ bảng
SELECT *
FROM `ncb-dwh.core.transactions`
WHERE DATE(created_at) = '2026-06-30';

-- ✅ RẺ: chỉ chạm đúng cột cần cho câu hỏi
SELECT
  channel,
  COUNT(*)        AS so_giao_dich,
  SUM(amount)     AS tong_tien
FROM `ncb-dwh.core.transactions`
WHERE created_at >= TIMESTAMP('2026-06-01')
  AND created_at <  TIMESTAMP('2026-07-01')
GROUP BY channel
ORDER BY tong_tien DESC;

Hai câu trên cho ra thông tin khác nhau, nhưng ý chính là: mỗi cột bạn thêm vào là byte cộng thêm vào hoá đơn. Trong column-store, chi phí gần như tỉ lệ thuận với số cột (và độ rộng của chúng) mà bạn tham chiếu.

3.2 Ước lượng byte trước khi chạy

BigQuery cho phép ước lượng chi phí mà không thực sự quét dữ liệu, qua chế độ dry run. Đây là thói quen nên có trước mỗi truy vấn lớn:

# bq CLI: ước lượng bytes sẽ quét, KHÔNG chạy thật, KHÔNG tốn tiền
bq query --use_legacy_sql=false --dry_run \
'SELECT channel, SUM(amount)
 FROM `ncb-dwh.core.transactions`
 WHERE created_at >= TIMESTAMP("2026-06-01")
 GROUP BY channel'
# → "Query successfully validated. Assuming the tables are not modified,
#    running this query will process 61,203,558,912 bytes of data."

Trên giao diện web, cùng con số này hiện ở góc phải trên trình soạn thảo ("This query will process … when run"). Đổi từ * sang danh sách cột và xem con số tụt xuống — đó là columnar storage đang làm việc cho bạn.

Lưu ý: BigQuery tính theo kích thước dữ liệu chưa nén của các cột được đọc, và làm tròn tối thiểu (mỗi cột có mức tối thiểu nhỏ). Vì vậy đọc thêm một cột rộng như description (TEXT dài) tốn hơn nhiều so với một cột INT64. Chi tiết cách quy đổi byte → tiền nằm ở bài pricing theo bytes.


4. Vì sao nén theo cột hiệu quả đến vậy

Nén tốt vừa giảm dung lượng lưu (rẻ hơn) vừa giảm byte phải đọc từ đĩa (nhanh hơn). Lý do column-store nén vượt trội row-store nằm ở tính đồng nhất dữ liệu trong một cột:

  • Một cột chỉ chứa một kiểu dữ liệu → thuật toán nén tối ưu cho đúng kiểu đó.
  • Giá trị trong cột thường lặp lại nhiều hoặc thay đổi ít → RLE và dictionary encoding "ăn" rất mạnh. Cột channel chỉ có 4-5 giá trị phân biệt: thay vì lưu chuỗi "MOBILE" hàng tỉ lần, Capacitor lưu một từ điển nhỏ và một dãy số nguyên 3-bit.
  • Dữ liệu gần nhau về mặt thống kê (ví dụ amount trong một khoảng, created_at tăng dần) → delta encoding và sắp xếp lại dòng cho tỉ lệ nén cao hơn.

Ngược lại, trong một dòng của row-store, các ô cạnh nhau lại khác kiểu, khác phân phối (txn_id số, channel chuỗi, amount số thập phân, created_at timestamp) — trộn lẫn khiến bộ nén khó tìm được mẫu để khai thác.

Kỹ thuậtÁp dụng khiVí dụ trong transactions
Dictionary encodingÍt giá trị phân biệt (low cardinality)channel, status, currency
Run-length (RLE)Giá trị lặp liên tiếpCột đã sắp xếp, cờ boolean
Delta encodingGiá trị tăng/giảm đềucreated_at, txn_id tuần tự
Kết hợp + nén khốiMặc định cho mọi cộtToàn bộ bảng

5. Cột hoá không phải "phép màu miễn phí"

Để mô hình tinh thần cân bằng, cần nhớ vài đánh đổi:

  • Ghi/sửa từng dòng đắt. Để lấy hay cập nhật một bản ghi đầy đủ, engine phải ghép giá trị từ nhiều block cột. BigQuery vì thế không hợp cho workload OLTP điểm (point lookup/update tần suất cao) — đó là việc của cơ sở dữ liệu giao dịch.
  • SELECT * là kẻ thù của cả tốc độ lẫn chi phí. Càng nhiều cột càng nhiều byte đọc; và một cột STRING rộng đơn lẻ có thể lớn hơn hàng chục cột số cộng lại.
  • Cột hoá chỉ cắt theo chiều cột. Muốn cắt cả theo chiều dòng (chỉ đọc dữ liệu tháng 6, không quét cả 5 năm lịch sử) bạn cần partitioningclustering — chủ đề của bài tối ưu. Columnar + partition + cluster là bộ ba giảm bytes scanned mạnh nhất.

Use case thực tế

Đội phân tích rủi ro của NCB có bảng transactions lưu 5 năm dữ liệu core: ~4 tỉ dòng, 48 cột, dung lượng logic ~1,2 TB. Mỗi sáng, một dashboard chạy truy vấn "tổng giá trị và số lượng giao dịch theo kênh trong 30 ngày gần nhất".

Bản đầu tiên do một bạn mới viết dùng SELECT * rồi lọc ở tầng BI:

  • Dry run báo quét ~1,2 TB mỗi lần chạy. Với on-demand ~5 USD/TB, mỗi lần chạy tốn khoảng 6 USD; chạy tự động mỗi giờ → gần 4.300 USD/tháng chỉ cho một dashboard.

Sau khi review, câu lệnh được viết lại chỉ chọn 4 cột (created_at, channel, amount, txn_id) và dựa vào partition theo ngày:

  • Chỉ quét đúng 4 cột trong cửa sổ 30 ngày ≈ ~9 GB/lần thay vì 1,2 TB.
  • Chi phí rơi xuống còn khoảng 0,05 USD/lần, dashboard cũng trả kết quả nhanh hơn nhiều lần.

Cùng một câu hỏi nghiệp vụ, cùng dữ liệu — chỉ khác ở việc tôn trọng bản chất cột hoá của storage. Đây là ví dụ điển hình cho thấy hiểu Capacitor không phải kiến thức hàn lâm mà là tiền thật trên hoá đơn.

Ghi nhớ

  • BigQuery lưu bảng bằng Capacitor — định dạng cột: mỗi cột được encode và nén độc lập trên Colossus.
  • Row-store tối ưu lấy/sửa nguyên dòng (OLTP); column-store tối ưu quét vài cột trên rất nhiều dòng (OLAP).
  • On-demand tính tiền theo byte quét trên các cột được tham chiếu, không theo kích thước cả bảng.
  • Vì vậy tránh SELECT *: mỗi cột thêm vào là byte cộng vào hoá đơn; cột STRING rộng đặc biệt đắt.
  • Nén theo cột hiệu quả nhờ dữ liệu đồng nhất: dictionary, RLE, delta encoding cho tỉ lệ nén cao.
  • Dùng --dry_run (hoặc chỉ báo trên web UI) để ước lượng bytes trước khi chạy — miễn phí.
  • Columnar chỉ cắt theo chiều cột; kết hợp partitioning + clustering để cắt thêm theo chiều dòng.
  • Xem pricing theo bytes để quy byte ra tiền, và tối ưu truy vấn để giảm bytes scanned tối đa.

Nguồn tham khảo

  • BigQuery documentation — Overview & storage: cloud.google.com/bigquery/docs
  • Best practices for performance & cost (tránh SELECT *, giảm dữ liệu quét): cloud.google.com/bigquery/docs/best-practices-performance-overview
  • Nested & repeated data (STRUCT/ARRAY trong Capacitor): cloud.google.com/bigquery/docs/nested-repeated
  • Partitioned tables: cloud.google.com/bigquery/docs/partitioned-tables
  • Clustered tables: cloud.google.com/bigquery/docs/clustered-tables
  • BigQuery pricing (on-demand theo bytes): cloud.google.com/bigquery/pricing
  • "Dremel: Interactive Analysis of Web-Scale Datasets" — Melnik et al., VLDB 2010 (nền tảng lưu trữ cột & ColumnIO)
  • "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.

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ẻ!