BigQuery 2 — Lưu trữ cột & các loại bảng

13 thg 7, 2026 2 lượt xem
#sql
#storage
#bigquery
#external-table
#columnar
#materialized-view

BigQuery 2 — Lưu trữ cột & các loại bảng

Ở bài Tổng quan ta đã thấy đặc điểm kiến trúc nổi bật nhất của BigQuery: tách rời lưu trữ (storage) và tính toán (compute). Dữ liệu nằm yên một chỗ trên hệ thống file phân tán của Google, còn hàng nghìn "slot" tính toán được huy động linh hoạt lúc bạn chạy truy vấn. Bài này đi sâu vào nửa lưu trữ: dữ liệu thực sự được xếp trên đĩa như thế nào, vì sao BigQuery đọc nhanh và rẻ cho analytics, có những loại bảng nào, và khi nào chọn cái nào.

Hiểu tầng lưu trữ không phải kiến thức "cho vui": nó quyết định trực tiếp hoá đơn bạn trả (quét bao nhiêu byte, lưu bao nhiêu GB) và tốc độ truy vấn. Các quyết định sau này — nạp vào native hay query external trên GCS? dùng view hay materialized view? — đều bắt nguồn từ mô hình lưu trữ ở đây.

Lưu ý sandbox của series là PostgreSQL nên các câu lệnh BigQuery bên dưới không chạy được ở đây; chúng được ghi (minh hoạ) để mang sang Google Cloud Console hoặc bq CLI.

Lưu trữ cột: Capacitor trên Colossus

Câu hỏi nền tảng: khi bạn có một bảng 500 cột, 10 tỷ dòng, dữ liệu được ghi ra đĩa theo thứ tự nào?

Hàng (row) vs cột (column)

Một RDBMS truyền thống (PostgreSQL, MySQL, Oracle) lưu theo hàng: mọi cột của một dòng nằm cạnh nhau trong một block. Cách này tuyệt vời cho OLTP — "lấy toàn bộ đơn hàng #12345" chỉ cần đọc một chỗ. Nhưng với truy vấn phân tích kiểu SELECT AVG(amount) FROM orders, hệ thống buộc phải đọc cả dòng dù chỉ cần đúng một cột amount, lãng phí I/O cho hàng trăm cột không dùng.

BigQuery lưu theo cột: mỗi cột được cất riêng thành các khối dữ liệu độc lập. Định dạng cột của BigQuery tên là Capacitor — thế hệ định dạng lưu trữ cột do Google phát triển. Hai lợi ích lớn:

  • Column pruning (chỉ đọc cột cần): truy vấn chỉ chạm vào các cột xuất hiện trong SELECT, WHERE, GROUP BY, JOIN. Chọn 3 cột trong bảng 500 cột thì chỉ đọc 3 cột. Đây là lý do bạn nên tránh SELECT * trong BigQuery — nó phá vỡ column pruning và biến truy vấn "rẻ" thành "đắt".
  • Nén tốt hơn nhiều: dữ liệu trong cùng một cột đồng nhất về kiểu và thường lặp lại (cùng mã quốc gia, cùng trạng thái, ngày tháng gần nhau...). Sự dư thừa (redundancy) trong một cột cao hơn nhiều so với qua một hàng, nên thuật toán nén ăn rất sâu. Nén tốt vừa giảm chi phí lưu trữ, vừa giảm số byte phải đọc khi quét.

Colossus — hệ thống file bên dưới

Capacitor là định dạng; nơi các file đó thực sự nằm là Colossus, hệ thống file phân tán thế hệ mới của Google (kế nhiệm GFS). Colossus lo replication, phục hồi sau lỗi đĩa, mã hoá và mở rộng gần như vô hạn — BigQuery bọc nó lại nên bạn không thao tác trực tiếp. Điều đáng nhớ: dữ liệu không nằm trên máy làm tính toán. Slot compute và Colossus nối qua mạng nội bộ Jupiter băng thông cực lớn, cho phép hàng nghìn slot cùng đọc song song một bảng.

Sơ đồ cho thấy đường đi: truy vấn chỉ kéo về đúng những cột cần, giải nén từ Capacitor, đọc từ Colossus; phần còn lại của bảng nằm yên.

Storage billing: active, long-term, logical vs physical

Vì storage tách khỏi compute, BigQuery tính tiền lưu trữ riêng với tiền quét truy vấn (chi tiết giá quét ở bài Chi phí & hiệu năng). Có vài khái niệm cần nắm.

Active vs long-term storage

BigQuery tự động giảm giá cho dữ liệu "nguội":

  • Active storage: giá đầy đủ, áp dụng cho bảng (hoặc phân vùng) có bị sửa đổi trong 90 ngày gần nhất.
  • Long-term storage: nếu một bảng — hoặc từng phân vùng riêng lẻ của bảng phân vùng — không bị chỉnh sửa suốt 90 ngày liên tục, giá lưu trữ tự động giảm khoảng 50%.

Vài điểm quan trọng để tránh hiểu sai:

  • Giảm giá này tự động, không cần khai báo, không cần chuyển bảng đi đâu.
  • Long-term không ảnh hưởng hiệu năng, độ bền hay tính khả dụng — vẫn cùng một bảng, query y hệt, chỉ khác cái giá lưu trữ.
  • "Sửa đổi" nghĩa là ghi dữ liệu: INSERT, UPDATE, DELETE, MERGE, load thêm, streaming... Bất kỳ thao tác ghi nào reset đồng hồ 90 ngày và đưa bảng/phân vùng về active. Ngược lại, đọc (SELECT) không reset — bạn có thể query một bảng lịch sử bao nhiêu tuỳ thích mà nó vẫn hưởng giá long-term.
  • Vì đồng hồ tính theo từng phân vùng, một bảng phân vùng theo ngày có thể có phân vùng mới (active) lẫn phân vùng cũ (long-term) cùng lúc — rất lợi cho dữ liệu time-series.

Logical vs physical storage billing

Đây là lựa chọn ở cấp dataset về cách đo dung lượng:

  • Logical (mặc định): tính theo kích thước dữ liệu chưa nén (kích thước logic của các byte). Thời gian lưu cho time travel và fail-safe đã gộp vào giá cơ bản, không tính thêm.
  • Physical (nén): tính theo dung lượng đã nén thực tế trên đĩa — thường nhỏ hơn nhiều nhờ Capacitor nén tốt, nên đơn giá GB cao hơn nhưng tổng có thể rẻ hơn cho dữ liệu nén tốt. Bù lại, storage cho time travelfail-safe bị tính riêng.

Không có đáp án chung: dữ liệu nén rất tốt (nhiều cột lặp lại) thường lợi với physical; dữ liệu khó nén hoặc có nhiều time-travel churn có thể lợi với logical. Lưu ý: đổi mô hình billing của một dataset phải cách nhau 14 ngày giữa hai lần đổi, nên hãy cân nhắc trước.

Các loại bảng trong BigQuery

BigQuery gom các đối tượng lưu trữ thành vài nhóm. Ta đi từ "dữ liệu nằm trong BigQuery" đến "dữ liệu nằm ngoài".

Native (standard) table

Bảng chuẩn: dữ liệu có cấu trúc, được nạp vào BigQuery storage và lưu ở định dạng cột Capacitor. Đây là loại bạn dùng nhiều nhất — hiệu năng tốt nhất, hỗ trợ đầy đủ partition, cluster, DML, time travel.

-- (minh hoạ) tạo bảng native
CREATE TABLE `analytics.sales` (
  order_id   INT64,
  order_ts   TIMESTAMP,
  city       STRING,
  amount     NUMERIC,
  items      ARRAY<STRUCT<sku STRING, qty INT64>>
)
OPTIONS (
  description = "Đơn hàng đã chốt",
  labels = [("team", "data"), ("tier", "hot")]
);

Chú ý kiểu ARRAY<STRUCT<...>>: BigQuery hỗ trợ kiểu lồng và lặp (nested & repeated), cho phép nhúng cả một bảng con vào một dòng thay vì phải join. Chi tiết STRUCT/ARRAY và cách unnest sẽ ở bài SQL features; ở đây chỉ cần biết native table lưu được chúng nguyên bản.

External table & BigLake

External table cho phép query dữ liệu nằm ngoài BigQuery storage mà không nạp vào. Nguồn phổ biến: file trên Cloud Storage (Parquet, CSV, JSON, ORC, Avro), Google Sheets, Bigtable, Google Drive.

-- (minh hoạ) external table đọc thẳng Parquet trên GCS
CREATE EXTERNAL TABLE `lake.events_ext`
OPTIONS (
  format = "PARQUET",
  uris   = ["gs://my-lake/events/*.parquet"]
);

BigLake table là biến thể "cao cấp" của external: cũng trỏ ra dữ liệu ngoài (Cloud Storage, hoặc cả S3/Azure Blob), nhưng thêm bảo mật chi tiết (fine-grained: cột, dòng), cache metadata để nhanh hơn, và hỗ trợ định dạng bảng mở như Apache Iceberg. Ngoài ra còn object table — trỏ tới dữ liệu phi cấu trúc (ảnh, video) trên Cloud Storage để dùng với ML/AI.

Đánh đổi của external/BigLake:

  • Ưu: không tốn bước nạp và không nhân đôi dữ liệu; dữ liệu vẫn nằm ở "hồ" (data lake) cho công cụ khác dùng; BigQuery không tính phí storage cho phần dữ liệu ngoài này.
  • Nhược: thường chậm hơn native (phải đọc file thô, ít tận dụng được thống kê/nén nội bộ), có thể không hỗ trợ đầy đủ DML/streaming, và một số tính năng bị hạn chế. Materialized view cũng không được xây trên external table.

Partitioned & clustered table

Đây là hai kỹ thuật tối ưu áp lên bảng native, chỉ giới thiệu ở đây:

  • Partitioned table: chia bảng thành các phân vùng theo cột thời gian (ngày/giờ/tháng) hoặc theo dải số nguyên. Truy vấn có điều kiện trên cột phân vùng chỉ quét đúng phân vùng liên quan (partition pruning) → quét ít byte, rẻ hơn. Đồng hồ long-term cũng tính theo từng phân vùng.
  • Clustered table: sắp xếp dữ liệu trong mỗi phân vùng theo một hay nhiều cột, gom giá trị gần nhau lại. Lọc/GROUP BY trên cột cluster giúp bỏ qua bớt khối dữ liệu (block pruning).

Chi tiết cách chọn cột, thứ tự cluster và các bẫy nằm ở bài Partition & cluster.

Table snapshots & clones

  • Table snapshot: bản chụp chỉ đọc của bảng tại một thời điểm. Rẻ vì chỉ lưu phần chênh lệch so với bảng gốc, không copy toàn bộ. Hợp để lưu bản backup logic, hoặc chốt trạng thái cuối kỳ.
  • Table clone: bản nhân bản ghi được. Ban đầu cũng chỉ lưu chênh lệch (lightweight), chỉ tốn thêm storage khi bạn sửa clone. Rất tiện để tạo môi trường test/dev từ dữ liệu production mà không nhân đôi dung lượng.
-- (minh hoạ)
CREATE SNAPSHOT TABLE `analytics.sales_snap_2025q2`
  CLONE `analytics.sales`;                 -- bản chụp chỉ đọc

CREATE TABLE `sandbox.sales_dev`
  CLONE `analytics.sales`;                 -- clone ghi được để thử nghiệm

Temporary & wildcard

  • Temporary table: BigQuery tự tạo bảng tạm để chứa kết quả trung gian; ngoài ra kết quả của một truy vấn trong một phiên/script cũng có thể là bảng tạm với vòng đời ngắn.
  • Wildcard table: dùng dấu * trong tên để query nhiều bảng cùng tiền tố như một bảng gộp, ví dụ FROM \project.dataset.events_2025*`để quét mọi bảngevents_2025...`. Tiện cho dữ liệu bị chia bảng theo ngày (sharded) trước khi có partition.

View vs Materialized view

Cả hai đều "đóng gói" một truy vấn dưới tên gọi, nhưng bản chất lưu trữ khác hẳn.

View (logical view)

View chỉ lưu định nghĩa SQL, không lưu dữ liệu. Mỗi lần bạn query view, BigQuery chạy lại truy vấn nền trên bảng gốc. Lợi ích là trừu tượng hoá (giấu join phức tạp, đổi tên cột, phân quyền theo view) và luôn phản ánh dữ liệu mới nhất; nhược điểm là không tăng tốc — quét vẫn tốn như chạy truy vấn gốc.

-- (minh hoạ) view: tính lúc query
CREATE VIEW `analytics.v_daily_sales` AS
SELECT DATE(order_ts) AS d, city, SUM(amount) AS revenue
FROM `analytics.sales`
GROUP BY d, city;

Materialized view

Materialized view (MV) lưu kết quả đã tính sẵn trong BigQuery storage, và BigQuery tự cập nhật tăng dần (incremental refresh): khi bảng gốc thay đổi, hệ thống kiểm tra xem thay đổi có làm kết quả cũ vô hiệu không — nếu không, nó chỉ đọc phần delta mới cộng với kết quả đã cache, thay vì tính lại toàn bộ.

Điểm mạnh nhất là smart tuning / tự động viết lại truy vấn: ngay cả khi bạn query thẳng bảng gốc (không nhắc tên MV), nếu một phần truy vấn có thể trả lời bằng MV, BigQuery tự động định tuyến sang MV để nhanh và rẻ hơn — hoàn toàn trong suốt với người viết SQL. Rất mạnh cho các aggregate lặp đi lặp lại (dashboard, báo cáo).

-- (minh hoạ) materialized view: kết quả tiền tính, tự refresh
CREATE MATERIALIZED VIEW `analytics.mv_daily_sales` AS
SELECT DATE(order_ts) AS d, city, SUM(amount) AS revenue
FROM `analytics.sales`
GROUP BY d, city;

Cần nhớ các giới hạn: MV không dựng được trên external table, view logic, snapshot, wildcard; cú pháp bị hạn chế (nhất là MV incremental — thường mạnh cho GROUP BY/aggregate); không sửa được truy vấn sau khi tạo (phải tạo lại); và max staleness cấu hình được (khoảng 30 phút đến 3 ngày) để cân bằng độ tươi với chi phí refresh. MV tốn storage và phí refresh, đổi lấy quét truy vấn rẻ hơn — bài toán đánh đổi này bàn kỹ ở Chi phí & hiệu năng.

Dataset, region và schema

Dataset là đơn vị gom nhóm bảng/view (giống schema/namespace). Vài đặc điểm quan trọng:

  • Location/region gắn khi tạo và KHÔNG đổi được về sau. Mọi bảng trong dataset nằm cùng region với dataset. Query nối bảng giữa hai region khác nhau sẽ lỗi — đây là lý do phải chọn region cẩn thận ngay từ đầu (đặt gần nguồn dữ liệu và tuân thủ quy định lưu trú dữ liệu).
  • Quyền (IAM) thường gán ở cấp dataset; có thể siết thêm ở cấp bảng, hoặc cột/dòng qua BigLake.
  • Default table expiration: đặt hạn tự xoá cho bảng mới trong dataset — rất hữu ích cho dataset staging/tạm để tránh "rác" tích tụ và phình hoá đơn storage.

Về schema và kiểu dữ liệu, BigQuery có các kiểu quen thuộc (INT64, NUMERIC, BIGNUMERIC, FLOAT64, STRING, BYTES, BOOL, DATE, TIMESTAMP, GEOGRAPHY, JSON) cộng với hai kiểu "đắt giá" cho dữ liệu bán cấu trúc: STRUCT (bản ghi lồng) và ARRAY (mảng lặp). Chúng cho phép mô hình hoá dữ liệu phân cấp mà không phải chuẩn hoá ra nhiều bảng — chi tiết ở bài SQL features.

Nạp và kết nối dữ liệu (giới thiệu)

Có hai con đường đưa dữ liệu vào tầm với của BigQuery:

  • Nạp vào native table: batch load từ GCS/local (Parquet, CSV, JSON, Avro, ORC), hoặc streaming insert cho dữ liệu thời gian thực.
  • Kết nối external: để dữ liệu nguyên tại nguồn và query qua external/BigLake table như đã nói.

Chi tiết về batch load, streaming, schema autodetect và các bẫy thường gặp nằm ở bài Nạp & streaming.

Use case thực tế: external cho lake, native cho bảng nóng

Một pipeline dữ liệu điển hình thường phối hợp cả hai, không chọn một:

  • Vùng landing/raw (external): dữ liệu thô đổ về data lake trên Cloud Storage dạng Parquet/JSON, được nhiều công cụ (Spark, Dataflow, BigQuery) dùng chung. Ở đây dùng external/BigLake table để BigQuery đọc thẳng, không nhân đôi dữ liệu và không tốn phí storage BigQuery. Chấp nhận query chậm hơn vì tầng này chủ yếu để khám phá, kiểm tra chất lượng, hoặc nạp một lần.
  • Vùng phục vụ/hot (native): các bảng fact/dim mà dashboard và API truy vấn liên tục thì nên nạp vào native table, cấu hình partition theo ngàycluster theo cột lọc chính. Native lấy được hiệu năng cột Capacitor tối đa, partition pruning, và giá long-term tự động cho phân vùng cũ. Trên đó dựng thêm materialized view cho các aggregate dashboard hay dùng để smart tuning tự tăng tốc.

Quy tắc ngón tay cái: external khi dữ liệu chủ yếu để ở lake và ít query nóng; native khi query nóng, lặp lại, cần tốc độ và DML. Bảng lịch sử ít đụng tới thì để native nhưng "quên nó đi" — long-term storage tự lo phần giá.

Ghi nhớ

  • BigQuery lưu theo cột bằng định dạng Capacitor trên hệ file Colossuscolumn pruning + nén tốt → nhanh, rẻ cho analytics. Tránh SELECT *.
  • Storage tách compute, tính tiền riêng. Bảng/phân vùng không sửa 90 ngày tự xuống long-term (~50% giá), không ảnh hưởng hiệu năng; ghi reset đồng hồ, đọc thì không.
  • Logical vs physical billing ở cấp dataset — nén tốt thường lợi physical; đổi mô hình cách nhau 14 ngày.
  • Loại bảng: native (nhanh nhất), external/BigLake (query tại chỗ GCS/Parquet/Iceberg, không tốn storage nhưng chậm hơn), partitioned/clustered (tối ưu quét), snapshot (chụp chỉ đọc, rẻ) và clone (nhân bản ghi được, rẻ), cùng temporary/wildcard.
  • View = lưu SQL, tính lúc query, không tăng tốc. Materialized view = kết quả tiền tính, tự refresh tăng dần, smart tuning tự viết lại truy vấn — mạnh cho aggregate; nhưng tốn storage + phí refresh và có giới hạn cú pháp.
  • Dataset gắn region không đổi được — chọn kỹ để gần nguồn và tuân thủ. Đặt default expiration cho dataset tạm.
  • Kiến trúc dữ liệu thực tế: external cho lake, native cho hot, phủ materialized view lên bảng nóng.

Bài tiếp theo trong series đi sâu vào Partition & cluster — cách biến "quét cả bảng" thành "quét đúng vài phân vùng", đòn bẩy chi phí lớn nhất trong BigQuery.

Bài viết liên quan

Cách index hoạt động (B-Tree), đọc EXPLAIN, seq scan vs index scan và mẫu tối ưu truy vấn.

13 thg 7, 2026 4

Khoá, ràng buộc, quan hệ và chuẩn hoá 1NF/2NF/3NF — thiết kế lược đồ đúng từ đầu.

13 thg 7, 2026 4

Kết nhiều bảng đúng cách: các loại JOIN, bẫy thường gặp và phép hợp tập.

13 thg 7, 2026 3

Truy vấn lồng, CTE (WITH), CTE đệ quy và hàm cửa sổ — vũ khí cho phân tích nâng cao.

13 thg 7, 2026 3