Mục tiêu học tập
- Phân biệt row-based (CSV, JSON, Avro) và columnar (Parquet, ORC)
- Chọn định dạng đúng theo use case: analytics, streaming, exchange
- Hiểu các codec nén (Snappy, GZIP, ZSTD, BZIP2) và tính splittable
- Nắm partitioning và bucketing trên S3 để giảm data scanned
1. Row-based vs Columnar
- Analytics (SELECT vài cột, aggregate trên hàng triệu row) → columnar: chỉ scan cột cần, nén tốt hơn (dữ liệu cùng kiểu nằm cạnh nhau), có statistics (min/max) để skip block (predicate pushdown)
- Ghi liên tục / đọc cả bản ghi (streaming, message exchange) → row-based
2. Từng định dạng
CSV
- Text, đơn giản, mọi tool đọc được; không có schema, không kiểu dữ liệu, escape phức tạp
- Query trên Athena scan toàn bộ file → đắt và chậm
- Chỉ nên dùng ở raw zone / trao đổi với hệ thống cũ
JSON (và JSON Lines)
- Semi-structured, nested; phổ biến cho API/log
- Athena đọc được (SerDe) nhưng vẫn scan nhiều; nên convert sang Parquet ở curated zone
- JSON Lines (mỗi dòng một object) là dạng thân thiện với xử lý phân tán hơn một JSON array khổng lồ
Apache Avro
- Row-based, binary, có schema đi kèm (schema embedded trong file / schema registry)
- Mạnh về schema evolution — chuẩn phổ biến cho streaming (Kafka/MSK + AWS Glue Schema Registry)
- Splittable; phù hợp làm định dạng landing của dữ liệu streaming trước khi convert columnar
Apache Parquet
- Columnar, binary, mặc định của hệ sinh thái Spark/Athena/Glue
- Nén và encode theo cột; lưu statistics per row-group → predicate pushdown
- Định dạng khuyến nghị cho analytics trên S3 — hầu hết đáp án "most cost-effective query" liên quan tới "convert to Parquet"
Apache ORC
- Columnar tương tự Parquet, tối ưu cho hệ Hive; có index nhẹ (bloom filter) trong file
- Trên AWS, Parquet phổ biến hơn; đề thi thường coi Parquet/ORC tương đương về columnar
Bảng so sánh
| CSV | JSON | Avro | Parquet | ORC | |
|---|---|---|---|---|---|
| Kiểu | Row, text | Row, text | Row, binary | Columnar | Columnar |
| Schema | Không | Không (tự mô tả) | Có, evolution tốt | Có (embedded) | Có (embedded) |
| Nén hiệu quả | Kém | Kém | Khá | Tốt | Tốt |
| Splittable | Có (uncompressed) | Từng dòng | Có | Có | Có |
| Use case | Trao đổi đơn giản | API/log | Streaming, Kafka | Analytics S3 | Analytics (Hive) |
3. Compression codecs
| Codec | Tỷ lệ nén | Tốc độ | Splittable? | Ghi chú |
|---|---|---|---|---|
| Snappy | Trung bình | Rất nhanh | Không (standalone)* | Mặc định của Parquet/ORC |
| GZIP | Cao | Chậm hơn | Không | File .csv.gz không chia được cho nhiều worker |
| ZSTD | Cao | Nhanh | Không (standalone)* | Cân bằng tốt nhất hiện nay, Glue/Athena hỗ trợ |
| BZIP2 | Rất cao | Rất chậm | Có | Hiếm dùng, chỉ khi cần splittable text |
| LZO | Trung bình | Nhanh | Có (nếu indexed) | Hệ Hadoop cũ |
* Quan trọng: với Parquet/ORC, tính splittable đến từ cấu trúc file (row groups/stripes), nên Parquet + Snappy vẫn xử lý song song được. Vấn đề "GZIP không splittable" chỉ nghiêm trọng với text file (CSV/JSON): một file .gz 10GB chỉ 1 worker đọc được → bottleneck.
Exam tip: "large gzip-compressed CSV files, Spark job slow" → nguyên nhân là GZIP không splittable; giải pháp: chia file nhỏ, dùng BZIP2, hoặc (tốt nhất) convert sang Parquet.
4. Partitioning trên S3
Khái niệm
Tổ chức dữ liệu theo thư mục dạng key=value (Hive-style) để query engine bỏ qua (prune) các partition không liên quan:
-- Chỉ scan partition year=2026/month=01 (partition pruning)
SELECT SUM(amount) FROM sales
WHERE year = '2026' AND month = '01';
Nguyên tắc chọn partition key
- Chọn cột hay xuất hiện trong WHERE: thường là ngày (
year/month/day), region, tenant - Tránh high-cardinality (vd
user_idhàng triệu giá trị) → quá nhiều partition nhỏ, chậm cả metadata lẫn query - Tránh quá nhiều small files (< 128MB); gộp file (compaction) định kỳ — kích thước file tối ưu cho S3 analytics ~128MB-1GB
- Partition projection (Athena): tính partition từ pattern thay vì lưu trong Glue Catalog — hết cảnh phải
MSCK REPAIR TABLE/crawler cho partition mới
Bucketing
Chia dữ liệu trong mỗi partition thành N bucket theo hash của một cột (vd user_id). Dùng khi cột lọc có cardinality cao không thể làm partition; giúp join/filter hiệu quả. Athena/Glue/Spark đều hỗ trợ.
5. Convert định dạng trên AWS — cách nào?
| Tình huống | Công cụ |
|---|---|
| Batch ETL convert CSV/JSON → Parquet | Glue ETL job (Spark) |
| Streaming: convert JSON → Parquet khi ghi S3 | Amazon Data Firehose (record format conversion, cần schema trong Glue Catalog) |
| Ad-hoc, một lần, bằng SQL | Athena CTAS (CREATE TABLE AS SELECT ... STORED AS PARQUET) |
| Trong pipeline Spark tự quản | EMR |
-- Athena CTAS: convert + partition + compress trong 1 câu SQL
CREATE TABLE curated.sales_parquet
WITH (
format = 'PARQUET',
write_compression = 'SNAPPY',
external_location = 's3://datalake-curated/sales/',
partitioned_by = ARRAY['year', 'month']
) AS
SELECT id, name, amount, year, month FROM raw.sales_csv;
Câu hỏi ôn tập
-
Vì sao Parquet giảm chi phí Athena so với CSV cho cùng một query?
Xem đáp án
Athena tính phí theo lượng dữ liệu scan ($5/TB). Parquet giảm scan nhờ: (1) columnar — chỉ đọc các cột trong SELECT/WHERE, (2) nén hiệu quả — dữ liệu cùng kiểu nén tốt hơn, (3) statistics per row group — skip block không thỏa điều kiện (predicate pushdown). Thực tế thường giảm 90%+ dữ liệu scan so với CSV.
-
Pipeline streaming qua MSK cần định dạng có schema evolution tốt. Chọn gì?
Xem đáp án
Avro kết hợp AWS Glue Schema Registry. Avro là row-based binary có schema tách biệt, hỗ trợ backward/forward compatibility khi producer/consumer nâng schema khác thời điểm. Parquet là columnar — tốt cho analytics at-rest chứ không phù hợp serialize từng message streaming.
-
Spark job đọc 1 file CSV nén GZIP 20GB rất chậm dù cluster nhiều worker. Nguyên nhân và cách xử lý?
Xem đáp án
GZIP không splittable — toàn bộ file chỉ 1 executor đọc và giải nén được, các worker khác ngồi chờ. Xử lý: chia nguồn thành nhiều file nhỏ (~128MB-1GB), dùng codec splittable (BZIP2 cho text), hoặc tốt nhất convert sang Parquet + Snappy (splittable nhờ row groups, lại nhanh hơn cho analytics).
-
Nên và không nên chọn cột nào làm partition key trên S3?
Xem đáp án
Nên: cột thường xuất hiện trong WHERE và có cardinality vừa phải — ngày (
year/month/day), region, loại sự kiện. Không nên: cột high-cardinality (user_id,order_id) vì sinh hàng triệu partition nhỏ → small files, metadata chậm, query chậm hơn. Với cột high-cardinality cần lọc/join, dùng bucketing thay vì partitioning. -
Partition projection của Athena giải quyết vấn đề gì?
Xem đáp án
Bình thường mỗi partition mới trên S3 phải được đăng ký vào Glue Catalog (chạy crawler,
MSCK REPAIR TABLE, hayALTER TABLE ADD PARTITION) thì Athena mới thấy. Partition projection khai báo pattern (vd date range, format) trong table properties để Athena tự suy ra partition khi query — không cần cập nhật catalog cho partition mới, giảm cả độ trễ metadata với bảng có rất nhiều partition. -
Cần convert nhanh một bảng CSV có sẵn trong Glue Catalog sang Parquet mà không viết code. Cách "least effort"?
Xem đáp án
Athena CTAS — một câu SQL
CREATE TABLE ... WITH (format='PARQUET', ...) AS SELECT ...vừa convert định dạng, vừa nén, vừa partition, kết quả tự đăng ký vào Glue Catalog. Không cần viết hay vận hành Glue/Spark job.
Bài tập thực hành
- Upload 1 file CSV lên S3, dùng Athena tạo external table và query — ghi lại "Data scanned"
- Chạy CTAS convert sang Parquet (Snappy), query lại cùng câu SQL — so sánh data scanned
- Tạo cấu trúc thư mục
year=/month=trên S3 và thử query có/không có điều kiện partition - Đọc Athena partition projection
Tài liệu tham khảo chính thức
- Athena — Columnar storage formats
- Athena compression support
- Partitioning data in Athena
- AWS Glue Schema Registry
Tiếp theo: IAM và Lake Formation cơ bản cho Data