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ểmNhược Điểm
Vertical ScalingĐơn giản, không cần thay đổi ứng dụngGiới hạn phần cứng, chi phí cao
Horizontal ScalingKhả 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ây5 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 ScaleSau Khi Scale
Thời Gian Query300,000 ms5,000 ms
Số Shard132
Tài Nguyên Sử Dụng100% CPU, Disk I/OPhâ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:
  1. 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;
    
  2. 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íShardingOLAP Database
Độ Phức TạpCaoTrung bình
Chi PhíTrung bìnhCao
Use CaseOLTP + OLAPOLAP chuyên sâu
Ví DụPostgreSQL CitusClickHouse, 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!