[Tech Blog #24 | Data Engineering] Cloud Storage & Data Lake 101: Giải Mã S3/GCS, Data Lake vs. Data Warehouse & Định Dạng Cột Parquet
![[Tech Blog #24 | Data Engineering] Cloud Storage & Data Lake 101: Giải Mã S3/GCS, Data Lake vs. Data Warehouse & Định Dạng Cột Parquet](/uploads/ai-images/cover-cloud-storage-data-lake.png)
1. Đặt vấn đề: Khi CSDL quan hệ "vỡ trận" trước dữ liệu khổng lồ
Ở các bài viết trước, chúng ta đã quen với việc lưu trữ dữ liệu trong PostgreSQL ([Tech Blog #04]) và xây dựng Data Warehouse ([Tech Blog #08]).
Thế nhưng, khi quy mô dữ liệu của doanh nghiệp phình to lên hàng chục Terabyte hoặc Petabyte mỗi tháng (nhật ký click chuột của người dùng, file log máy chủ, hình ảnh, file âm thanh), bạn sẽ lập tức đối mặt với 2 bài toán nan giải:
- Chi phí ổ cứng Database quá đắt đỏ: Việc duy trì ổ đĩa SSD hiệu năng cao gắn liền với máy chủ CSDL tốn kém gấp 10–20 lần so với các dịch vụ lưu trữ đám mây dạng đối tượng (Cloud Object Storage như Amazon S3, Google Cloud Storage - GCS).
- Dữ liệu đa dạng không thể nhét ngay vào bảng: Dữ liệu thô gửi về ở nhiều định dạng lộn xộn (JSON lồng nhau, XML, file ảnh). Nếu bắt buộc phải chuẩn hóa và tạo bảng trước thì hệ thống sẽ bị nghẽn (bottleneck).
Đó là lý do kiến trúc Data Lake (Hồ dữ liệu) và các kho lưu trữ Cloud Storage ra đời, trở thành tầng lưu trữ trung tâm của mọi hạ tầng Big Data hiện đại.
2. Giải phẫu Cloud Object Storage & Kiến trúc Data Lake
2.1. Object Storage (S3 / GCS) là gì?
Khác với ổ cứng máy tính dùng hệ thống thư mục cây phân cấp, Object Storage là một kho lưu trữ phẳng dạng Key-Value:
- Bucket: Tương đương với một "chiếc thùng chứa" khổng lồ trên mây (ví dụ:
my-company-data-lake). - Object Key: Là đường dẫn định danh duy nhất của file (ví dụ:
raw/orders/year=2026/month=09/orders_01.parquet). - Khả năng mở rộng vô hạn: Bạn có thể ném vào đó 1 file hay 1 tỷ file mà không bao giờ sợ bị "đầy ổ đĩa".
2.2. Data Lake vs. Data Warehouse: Schema-on-Read vs. Schema-on-Write
[Mọi nguồn dữ liệu thô]
│
▼ (Nạp nguyên vẹn vào - Schema-on-Read)
+─────────────────────────────────────────────────────────+
| DATA LAKE (Amazon S3 / GCS / MinIO) |
| Lưu trữ: JSON, CSV, Parquet, Audio, Logs (Chi phí siêu rẻ)|
+─────────────────────────────────────────────────────────+
│
▼ (Làm sạch, mô hình hóa Star Schema - Schema-on-Write)
+─────────────────────────────────────────────────────────+
| DATA WAREHOUSE (ClickHouse / BigQuery / Snowflake) |
| Phục vụ: Dashboard BI, Báo cáo tài chính cho Ban Giám đốc |
+─────────────────────────────────────────────────────────+
- Data Warehouse (Schema-on-Write): Bạn bắt buộc phải tạo bảng trước (
CREATE TABLEvới đúng kiểu dữ liệu cột), sau đó mới được nạp dữ liệu vào. - Data Lake (Schema-on-Read): Bạn ném toàn bộ dữ liệu thô vào trước mà không cần tạo bảng. Khi nào ai cần đọc dữ liệu để phân tích thì lúc đó mới định nghĩa schema để trích xuất thông tin.
3. Cuộc cách mạng Parquet: Tại sao CSV và JSON bị "thất sủng"?
Trong môi trường phân tích dữ liệu lớn, file .csv và .json bộc lộ điểm yếu chết người: Chúng là định dạng lưu trữ theo dòng (Row-oriented).
Hãy tưởng tượng bạn có một bảng dữ liệu 100 triệu dòng gồm 50 cột (Tên, Tuổi, Địa chỉ, SĐT, Doanh thu...).
Bạn chạy câu lệnh: SELECT SUM(doanh_thu) FROM orders;
- Với file CSV / JSON: Máy tính bắt buộc phải đọc toàn bộ 50 cột từ đầu đến cuối file qua ổ cứng để nhặt ra con số doanh thu Tốn hàng chục phút và ngốn băng thông I/O khổng lồ.
- Với Apache Parquet (Columnar Storage - Lưu trữ theo cột): Dữ liệu được lưu trữ riêng biệt theo từng cột. Máy tính chỉ cần nhảy đến đúng vị trí cột
doanh_thuđể đọc, bỏ qua hoàn toàn 49 cột còn lại (Column Pruning). Tốc độ nhanh gấp hàng chục lần!
Sức mạnh nén dữ liệu: Vì toàn bộ giá trị trong cùng một cột có cùng kiểu dữ liệu (ví dụ: cột
tuoitoàn là số nguyên), các thuật toán nén như Snappy có thể nén chặt dữ liệu, giúp giảm từ 70% đến 85% dung lượng lưu trữ so với CSV!
4. Bảng so sánh: CSV vs. JSON vs. Apache Parquet
| Tiêu chí | CSV (Comma-Separated) | JSON (JavaScript Object) | Apache Parquet (Columnar) |
|---|---|---|---|
| Cơ chế lưu trữ | Dạng dòng (Row-based), văn bản thuần. | Dạng dòng (Row-based), cấu trúc lồng nhau. | Dạng cột (Columnar-based), mã nhị phân. |
| Dung lượng lưu trữ | Lớn (Không nén hiệu quả). | Rất lớn (Lặp lại tên trường ở mỗi dòng). | Siêu nhỏ (Nén Snappy/Gzip giảm tới 80%). |
| Lưu trữ kiểu dữ liệu (Schema) | ❌ Không (Số, chuỗi, ngày tháng đều là text). | 🟡 Hạn chế (Hỗ trợ số, chuỗi, boolean cơ bản). | ✅ Có sẵn Metadata Schema nhúng trong file. |
| Tốc độ truy vấn phân tích (OLAP) | 🐢 Rất chậm (Quét toàn bộ file). | 🐢 Chậm nhất (Phải parse từng chuỗi JSON). | 🚀 Siêu nhanh (Chỉ quét đúng cột cần dùng). |
| Khả năng đọc bằng mắt người | ✅ Mở bằng Notepad/Excel dễ dàng. | ✅ Dễ đọc hiểu cấu trúc. | ❌ Không thể (File nhị phân, phải dùng code đọc). |
5. Thực hành thực chiến: Đo lường sức mạnh Parquet & Tải lên S3 bằng Python
Đoạn mã Python dưới đây thực hiện 2 việc:
- Tạo 500.000 dòng dữ liệu, xuất ra cả
.csvvà.parquetđể chứng minh sự chênh lệch khủng khiếp về dung lượng và tốc độ đọc. - Sử dụng thư viện
boto3để tải file Parquet lên Cloud Storage (Amazon S3 / MinIO):
import os
import time
import numpy as np
import pandas as pd
import boto3
from botocore.exceptions import NoCredentialsError
# ==============================================================================
# BƯỚC 1: TẠO DỮ LIỆU MẪU QUY MÔ LỚN (500,000 DÒNG)
# ==============================================================================
print("Đang khởi tạo 500,000 dòng dữ liệu mẫu...")
np.random.seed(42)
n_rows = 500_000
df = pd.DataFrame({
"order_id": np.arange(1, n_rows + 1),
"customer_id": np.random.randint(1000, 9999, size=n_rows),
"city": np.random.choice(["Ha Noi", "Ho Chi Minh", "Da Nang", "Hai Phong"], size=n_rows),
"order_amount": np.random.uniform(50_000, 2_000_000, size=n_rows).round(2),
"status": np.random.choice(["COMPLETED", "CANCELLED", "PENDING"], size=n_rows)
})
csv_file = "benchmark_orders.csv"
parquet_file = "benchmark_orders.parquet"
# ==============================================================================
# BƯỚC 2: SO SÁNH DUNG LƯỢNG LƯU TRỮ VÀ TỐC ĐỘ ĐỌC
# ==============================================================================
# 1. Lưu ra CSV và Parquet
df.to_csv(csv_file, index=False)
df.to_parquet(parquet_file, engine="pyarrow", compression="snappy")
# So sánh dung lượng file trên ổ đĩa
csv_size_mb = os.path.getsize(csv_file) / (1024 * 1024)
parquet_size_mb = os.path.getsize(parquet_file) / (1024 * 1024)
print("\n--- 1. SO SÁNH DUNG LƯỢNG LƯU TRỮ (STORAGE SIZE) ---")
print(f"File CSV : {csv_size_mb:.2f} MB")
print(f"File Parquet : {parquet_size_mb:.2f} MB")
print(f"-> Parquet tiết kiệm được: {(1 - parquet_size_mb / csv_size_mb) * 100:.1f}% dung lượng ổ đĩa!")
# 2. Đo thời gian đọc duy nhất 1 cột 'order_amount' để tính tổng (Column Pruning)
print("\n--- 2. SO SÁNH TỐC ĐỘ ĐỌC PHÂN TÍCH (ANALYTICAL QUERY) ---")
start_csv = time.time()
df_csv = pd.read_csv(csv_file, usecols=["order_amount"])
total_csv = df_csv["order_amount"].sum()
time_csv = time.time() - start_csv
print(f"Đọc từ CSV : {time_csv:.4f} giây (Tổng: {total_csv:,.0f})")
start_parquet = time.time()
df_parquet = pd.read_parquet(parquet_file, columns=["order_amount"])
total_parquet = df_parquet["order_amount"].sum()
time_parquet = time.time() - start_parquet
print(f"Đọc từ Parquet : {time_parquet:.4f} giây (Tổng: {total_parquet:,.0f})")
print(f"-> Parquet nhanh gấp: {time_csv / time_parquet:.1f} lần so với CSV!")
# ==============================================================================
# BƯỚC 3: TẢI FILE PARQUET LÊN CLOUD STORAGE (AMAZON S3 / MINIO)
# ==============================================================================
def upload_to_s3(local_file_path: str, bucket_name: str, s3_key: str):
"""Hàm tải file lên S3 với xử lý ngoại lệ."""
print(f"\nĐang tải file '{local_file_path}' lên S3 bucket '{bucket_name}'...")
# Khởi tạo client kết nối S3 (Thông tin lấy từ biến môi trường AWS_ACCESS_KEY_ID...)
s3_client = boto3.client(
"s3",
region_name=os.environ.get("AWS_REGION", "ap-southeast-1")
)
try:
s3_client.upload_file(local_file_path, bucket_name, s3_key)
print(f"✅ Tải lên thành công! Đường dẫn S3: s3://{bucket_name}/{s3_key}")
except NoCredentialsError:
print("⚠️ Chưa cấu hình AWS Credentials, mô phỏng tải lên thành công trên môi trường cục bộ.")
except Exception as e:
print(f"❌ Lỗi tải lên S3: {e}")
if __name__ == "__main__":
# Cấu trúc partition chuẩn theo ngày tháng
target_s3_key = "data_lake/raw/orders/year=2026/month=09/orders_clean.parquet"
upload_to_s3(parquet_file, "my-enterprise-data-lake", target_s3_key)
# Dọn dẹp file tạm cục bộ sau khi benchmark xong
for f in [csv_file, parquet_file]:
if os.path.exists(f):
os.remove(f)
6. Câu hỏi phỏng vấn thực tế (Interview Q&A)
Câu 1 (Mức độ: Entry / Junior)
Câu hỏi: "Phân biệt sự khác nhau giữa Data Lake và Data Warehouse? Doanh nghiệp có nên bỏ Data Warehouse để dùng hoàn toàn Data Lake hay không?"
Gợi ý trả lời:
*"Data Lake và Data Warehouse không triệt tiêu nhau mà bổ trợ cho nhau:
- Data Lake (như Amazon S3, GCS) đóng vai trò là kho chứa dữ liệu thô tập trung ban đầu (Landing zone), lưu trữ mọi định dạng (structured, semi-structured, unstructured) với chi phí cực rẻ theo nguyên lý Schema-on-Read.
- Data Warehouse (như ClickHouse, Snowflake, BigQuery) là nơi chứa dữ liệu đã được làm sạch, mô hình hóa chuẩn Star Schema (Schema-on-Write) để phục vụ các báo cáo BI nhanh cho Ban Giám đốc.
Doanh nghiệp không nên bỏ Data Warehouse, bởi vì Data Lake thuần túy không thể đáp ứng được các yêu cầu về tốc độ phản hồi sub-second của dashboard, cũng như không có các cơ chế tối ưu hóa giao dịch ACID nghiêm ngặt như Data Warehouse. Kiến trúc hiện đại nhất hiện nay là Lakehouse – kết hợp tính linh hoạt, chi phí rẻ của Data Lake với hiệu năng tính toán mạnh mẽ của Data Warehouse."*
Câu 2 (Mức độ: Junior / Mid-level)
Câu hỏi: "Tại sao định dạng Parquet lại giúp doanh nghiệp tiết kiệm hàng nghìn USD chi phí truy vấn trên AWS Athena hay Google BigQuery so với việc lưu trữ bằng CSV?"
Gợi ý trả lời:
*"Các công cụ truy vấn Serverless như AWS Athena hay Google BigQuery tính tiền dựa trên Khối lượng dữ liệu thực tế mà câu truy vấn phải quét qua (Data Scanned) — ví dụ $5 cho mỗi 1 TB quét.
Định dạng Parquet giúp cắt giảm chi phí quét dữ liệu qua 2 cơ chế:
- Column Pruning (Chỉ quét cột cần dùng): Nếu bảng có 50 cột nhưng câu truy vấn chỉ
SELECT2 cột, Athena chỉ đọc đúng 4% dung lượng của 2 cột đó trên S3 và bỏ qua 96% dung lượng còn lại. Nếu dùng CSV, Athena buộc phải quét 100% toàn bộ file.- Nén dữ liệu cao (High Compression): Nhờ cơ chế lưu trữ theo cột có cùng kiểu dữ liệu, Parquet nén file giảm 70–80% kích thước so với CSV, trực tiếp làm giảm số lượng Gigabyte bị tính tiền khi quét."*
7. Key Takeaways (Ghi nhớ nhanh)
- Cloud Storage (S3/GCS) là kho chứa trung tâm: Chi phí cực rẻ, khả năng mở rộng vô hạn, lưu trữ mọi dữ liệu thô của Data Lake.
- Data Lake là Schema-on-Read; Data Warehouse là Schema-on-Write: Ném dữ liệu thô vào Data Lake trước, làm sạch nạp vào Data Warehouse sau.
- Parquet là vua của dữ liệu phân tích: Lưu trữ theo cột (Columnar), nén cực chặt, lưu sẵn metadata schema, tốc độ quét phân tích nhanh gấp 10 lần CSV.
- Phân vùng thông minh (Partitioning): Luôn chia nhỏ dữ liệu trên Cloud Storage theo cấu trúc ngày tháng (
year=YYYY/month=MM/day=DD) để các công cụ chỉ quét đúng thư mục cần thiết.