Bài 20: Khi Tối Ưu Không Đủ – Scale Database Và Chuyển Sang Kiến Trúc Phân Tán
"Khi dữ liệu vượt quá giới hạn của một node, đã đến lúc nghĩ đến scale out và phân tán!"
1. Vấn Đề: Hệ Thống Quá Tải Với Dữ Liệu Lớn Và Query Phức Tạp
Scenario:
Bảng
transactions
(1 tỷ dòng, 500GB) với cấu trúc:transaction_id
(PK)user_id
amount
transaction_date
Query tổng hợp doanh thu theo ngày:
SELECT transaction_date, SUM(amount) FROM transactions WHERE transaction_date BETWEEN '2023-01-01' AND '2023-12-31' GROUP BY transaction_date;
Kết quả: Thời gian thực thi ~300 giây, CPU đạt 100%, thường xuyên timeout.
2. Phân Tích Hiện Trạng
Bước 1: Xác Định Bottleneck
Dữ liệu quá lớn: 1 tỷ dòng → Tốn nhiều thời gian quét và xử lý.
Tài nguyên hạn chế: Single node không đủ CPU, RAM, Disk I/O.
Query phức tạp: Aggregation trên dữ liệu lớn → Tốn tài nguyên.
Bước 2: Kiểm Tra Các Giải Pháp Tối Ưu Đã Áp Dụng
Index: Đã có index trên
transaction_date
.Partitioning: Đã chia bảng theo tháng.
Parallel Query: Đã sử dụng 8 workers.
Kết quả: Query vẫn chậm và gây áp lực lên hệ thống.
3. Giải Pháp: Scale Database Và Chuyển Sang Kiến Trúc Phân Tán
Bước 1: Đánh Giá Các Lựa Chọn Scale
Giải Pháp | Ưu Điểm | Nhược Điểm |
Vertical Scaling | Đơn giản, không cần thay đổi ứng dụng | Giới hạn phần cứng, chi phí cao |
Horizontal Scaling | Khả năng mở rộng vô hạn | Đòi hỏi thay đổi kiến trúc ứng dụng |
Bước 2: Chọn Kiến Trúc Phân Tán
Read Replica: Phân tải read query sang các node phụ.
Sharding: Chia dữ liệu thành các shard nhỏ hơn, phân tán trên nhiều node.
OLAP Database: Sử dụng hệ thống chuyên biệt cho query phân tích (ví dụ: ClickHouse, BigQuery).
4. Triển Khai Sharding Với PostgreSQL Citus
Bước 1: Cài Đặt Citus Extension
CREATE EXTENSION citus;
Bước 2: Chia Bảng transactions
Thành Các Shard
-- Tạo distributed table
SELECT create_distributed_table('transactions', 'user_id');
-- Chia thành 32 shard
SELECT master_create_empty_shard('transactions');
Bước 3: Chạy Query Trên Distributed Table
SELECT transaction_date, SUM(amount)
FROM transactions
WHERE transaction_date BETWEEN '2023-01-01' AND '2023-12-31'
GROUP BY transaction_date;
Execution Plan Trên Citus:
[
{
"Plan": {
"Node Type": "Custom Scan",
"Parallel Aware": true,
"Startup Cost": 0.00,
"Total Cost": 10000.00,
"Plan Rows": 365,
"Plan Width": 12,
"Actual Startup Time": 100.123,
"Actual Total Time": 5000.456,
"Actual Rows": 365,
"Actual Loops": 1,
"Shard Count": 32,
"Plans": [
{
"Node Type": "Remote Query",
"Shard Id": 1,
"Relation Name": "transactions_1",
"Alias": "transactions",
"Startup Cost": 0.00,
"Total Cost": 1000.00,
"Plan Rows": 10000,
"Plan Width": 12,
"Actual Startup Time": 10.123,
"Actual Total Time": 100.456,
"Actual Rows": 10000,
"Actual Loops": 1
},
... (32 shard tương tự)
]
}
}
]
Kết Quả:
Thời gian thực thi: Từ 300 giây → 5 giây (98% cải thiện).
Tải phân bổ đều: Mỗi shard xử lý ~31 triệu dòng.
Khả năng mở rộng: Dễ dàng thêm node để tăng hiệu suất.
5. Tổng Kết
Chỉ Số | Trước Khi Scale | Sau Khi Scale |
Thời Gian Query | 300,000 ms | 5,000 ms |
Số Shard | 1 | 32 |
Tài Nguyên Sử Dụng | 100% CPU, Disk I/O | Phân bổ đều trên 32 node |
Lý Do Hiệu Quả:
Sharding chia nhỏ dữ liệu → Giảm tải trên từng node.
Parallel execution trên nhiều shard → Tăng tốc độ xử lý.
6. Bài Tập Thực Hành
Dataset Mẫu:
CREATE TABLE user_activities (
user_id INT,
activity_date DATE,
duration_minutes INT
);
-- Insert 1 tỷ dòng dữ liệu
Yêu Cầu:
Chạy query tổng hợp thời gian hoạt động trung bình mỗi ngày:
SELECT activity_date, AVG(duration_minutes) FROM user_activities WHERE activity_date BETWEEN '2024-01-01' AND '2024-12-31' GROUP BY activity_date;
Triển khai sharding với Citus và đo lường hiệu suất.
Câu Hỏi:
- Khi nào nên sử dụng OLAP database thay vì sharding?
7. Mở Rộng & Thảo Luận
So Sánh Sharding vs OLAP Database
Tiêu Chí | Sharding | OLAP Database |
Độ Phức Tạp | Cao | Trung bình |
Chi Phí | Trung bình | Cao |
Use Case | OLTP + OLAP | OLAP chuyên sâu |
Ví Dụ | PostgreSQL Citus | ClickHouse, BigQuery |
Case Study: Migrate Từ MySQL Sang ClickHouse
Bước 1: Xuất dữ liệu từ MySQL sang CSV.
Bước 2: Tạo table trên ClickHouse:
CREATE TABLE transactions ( transaction_id UInt64, user_id UInt32, amount Float64, transaction_date Date ) ENGINE = MergeTree() ORDER BY (transaction_date, user_id);
Bước 3: Import dữ liệu từ CSV.
Kết quả: Query tổng hợp chạy trong 1 giây thay vì 300 giây.
Cảnh Báo Khi Scale Out
Độ trễ mạng: Giao tiếp giữa các node có thể gây chậm.
Quản lý phức tạp: Cần công cụ quản lý cluster (ví dụ: Kubernetes).
Chi phí tăng: Nhiều node → Tốn chi phí phần cứng và bảo trì.
Kết Luận
Khi tối ưu không còn đủ, scale out và chuyển sang kiến trúc phân tán là giải pháp tối ưu. Trong ví dụ này, sharding với Citus đã giảm thời gian query từ 300 giây xuống 5 giây. Tùy vào use case, bạn có thể lựa chọn sharding, OLAP database, hoặc kết hợp cả hai để đạt hiệu suất tối đa!
👉 Bài Tập Về Nhà: Tạo một cluster Citus với 3 node, shard bảng user_activities
và đo hiệu suất khi query tổng hợp!