OLAP vs OLTP: Tại sao các truy vấn phân tích đang “giết chết” hệ thống Production của bạn

Database tutorial - IT technology blog
Database tutorial - IT technology blog

Cuộc gọi đánh thức lúc 2 giờ sáng từ PagerDuty

Lúc đó là 2:14 sáng khi điện thoại của tôi bắt đầu reo inh ỏi. PagerDuty báo cáo rằng CPU của cơ sở dữ liệu production chính đã chạm ngưỡng 100%. Tình trạng này kéo dài liên tục trong mười phút. Các yêu cầu web bị timeout. Luồng thanh toán của chúng tôi bị tê liệt. Tôi vội vàng mở laptop với đôi mắt ngái ngủ và chạy lệnh SHOW PROCESSLIST; nhanh trên instance MySQL.

Tôi tìm thấy thủ phạm ngay lập tức. Một chuyên viên phân tích marketing đang chạy một truy vấn “nhanh”. Nó tính toán giá trị đơn hàng trung bình theo từng khu vực, nhóm theo tháng, dựa trên lịch sử của năm năm qua. Câu lệnh duy nhất đó đã kích hoạt việc quét toàn bộ bảng (full table scan) trên 50 triệu dòng dữ liệu. Nó khóa các bảng quan trọng và làm nghẽn các thao tác INSERTUPDATE đơn giản của ứng dụng.

Đây là một sai lầm kinh điển. Chúng ta đang đối xử với một hệ thống OLTP như một hệ thống OLAP. Tôi đã làm việc với MySQL, PostgreSQL và MongoDB trong hàng chục dự án. Mỗi loại đều có những thế mạnh riêng. Tuy nhiên, việc yêu cầu một cơ sở dữ liệu vừa xử lý các giao dịch tần suất cao vừa xử lý phân tích dữ liệu nặng nề chắc chắn là một công thức dẫn đến thảm họa.

Sự khập khiễng về kiến trúc

Vấn đề không chỉ đơn giản là một “truy vấn chậm”. Đó là sự không tương thích cơ bản trong cách lưu trữ dữ liệu. Các cơ sở dữ liệu truyền thống như PostgreSQL hay MySQL được xây dựng cho Xử lý giao dịch trực tuyến (OLTP – Online Transactional Processing). Chúng xuất sắc trong việc xử lý hàng ngàn thao tác nhỏ, nhanh như chớp. Hãy nghĩ đến việc chèn một người dùng mới, cập nhật mật khẩu hoặc lấy một đơn hàng cụ thể theo ID.

OLTP: Xương sống trong trải nghiệm người dùng của bạn

OLTP xử lý những gì đang diễn ra “ngay tại đây và ngay bây giờ”. Nó vận hành ứng dụng thực tế của bạn. Khi khách hàng nhấn nút “Mua hàng”, họ kích hoạt một thao tác OLTP. Ưu tiên ở đây là tính đồng thời (concurrency)tính toàn vẹn (integrity). Bạn cần cơ sở dữ liệu đảm bảo rằng hai người không thể cùng mua món đồ cuối cùng trong kho. Đây là lúc tính tuân thủ ACID trở nên sống còn.

Các chỉ số chính cho OLTP:

  • Loại truy vấn: Các lệnh INSERT, UPDATE, và DELETE với khối lượng lớn.
  • Độ trễ (Latency): Nên dưới 20–50 miligiây.
  • Khối lượng dữ liệu: Thường chỉ vài kilobyte cho mỗi giao dịch.
  • Lưu trữ: Hướng dòng (tối ưu hóa để tìm các bản ghi cụ thể).

Dưới đây là một giao dịch OLTP điển hình trong môi trường PostgreSQL:

-- Xử lý giao dịch mua hàng của người dùng
BEGIN;
UPDATE accounts SET balance = balance - 89.99 WHERE user_id = 5501;
INSERT INTO order_history (user_id, amount, status) VALUES (5501, 89.99, 'hoàn thành');
COMMIT;

Cách này rất hiệu quả. Cơ sở dữ liệu sử dụng index trên user_id để tìm một dòng cụ thể, khóa nó, cập nhật và tiếp tục xử lý.

OLAP: Nhà máy đưa ra quyết định

OLAP tập trung vào “bức tranh toàn cảnh”. Các nhà khoa học dữ liệu và chuyên viên phân tích kinh doanh sử dụng nó để tìm ra xu hướng. Bạn không dùng OLAP để vận hành website. Thay vào đó, bạn dùng nó để quyết định nên nhập kho mặt hàng nào cho quý tới. Các hệ thống này thường nằm trong các Kho dữ liệu (Data Warehouses).

Các chỉ số chính cho OLAP:

  • Loại truy vấn: Các câu lệnh SELECT phức tạp với nhiều lệnh JOINGROUP BY nặng nề.
  • Thông lượng (Throughput): Có thể quét hàng terabyte dữ liệu trong vài giây.
  • Độ trễ (Latency): Dao động từ vài giây đến vài phút tùy thuộc vào độ phức tạp.
  • Lưu trữ: Hướng cột (tối ưu hóa cho việc tổng hợp dữ liệu).

Một truy vấn OLAP sẽ trông như thế này:

-- Phân tích tăng trưởng theo từng năm theo danh mục sản phẩm
SELECT 
    EXTRACT(YEAR FROM order_date) as year,
    category_name,
    SUM(total_price) as revenue
FROM sales_data_warehouse
WHERE order_date > '2018-01-01'
GROUP BY 1, 2
ORDER BY 1 DESC;

Lưu trữ theo dòng so với Lưu trữ theo cột

Để hiểu tại sao các hệ thống như BigQuery, Snowflake, hay ClickHouse lại nhanh hơn nhiều đối với dữ liệu lớn, hãy nhìn vào cách lưu trữ trên đĩa. Trong một cơ sở dữ liệu Hướng dòng (Row-oriented), dữ liệu được đóng gói cùng nhau theo từng bản ghi:

Dòng 1: [ID, Tên, Email, Địa chỉ, Số dư]
Dòng 2: [ID, Tên, Email, Địa chỉ, Số dư]

Nếu bạn chỉ cần tính tổng Số dư, cơ sở dữ liệu vẫn phải đọc cả Tên, Email, và Địa chỉ. Chúng dính chặt với nhau về mặt vật lý trên đĩa. Đây là một sự lãng phí tài nguyên phần cứng khủng khiếp.

Trong một cơ sở dữ liệu Hướng cột (Column-oriented), dữ liệu được lưu trữ theo thuộc tính:

Cột ID: [1, 2, ...]
Cột Tên: [Alice, Bob, ...]
Cột Số dư: [100.00, 50.00, ...]

Để tính tổng Số dư, engine chỉ đọc khối dữ liệu Số dư. Nó bỏ qua mọi thứ khác. Điều này cho phép một hệ thống OLAP quét một tỷ dòng trong cùng khoảng thời gian mà một hệ thống OLTP cần để quét một triệu dòng.

Giải pháp: Xây dựng một Pipeline dữ liệu

Đừng bao giờ chạy các phân tích nặng nề trên cơ sở dữ liệu production của bạn. Bạn cần một pipeline để chuyển dữ liệu từ “Nguồn dữ liệu gốc” (Source of Truth) hệ OLTP sang một “Trung tâm phân tích” hệ OLAP. Đây chính là quy trình ETL (Trích xuất, Biến đổi, Nạp – Extract, Transform, Load).

  1. Trích xuất (Extract): Lấy dữ liệu từ các bản sao (replicas) của MySQL hoặc PostgreSQL production.
  2. Biến đổi (Transform): Làm sạch dữ liệu và loại bỏ các thông tin định danh cá nhân nhạy cảm (PII).
  3. Nạp (Load): Đưa dữ liệu đã tinh lọc vào một kho dữ liệu như BigQuery hoặc Snowflake.

Để tiếp cận theo cách hiện đại, many đội ngũ sử dụng Change Data Capture (CDC). Phương pháp này truyền mọi thay đổi của cơ sở dữ liệu đến kho dữ liệu theo thời gian thực. Dưới đây là một ví dụ khái niệm đơn giản sử dụng Python và SQLAlchemy:

import pandas as pd
import sqlalchemy

# 1. Trích xuất từ production (PostgreSQL)
prod_engine = sqlalchemy.create_engine('postgresql://user:pass@prod-db/app')
df = pd.read_sql("SELECT * FROM orders WHERE status = 'shipped'", prod_engine)

# 2. Biến đổi: Tính giá chưa bao gồm thuế
df['net_amount'] = df['total_price'] / 1.1

# 3. Nạp vào Kho dữ liệu phân tích (Snowflake/BigQuery)
warehouse_engine = sqlalchemy.create_engine('postgresql://user:pass@analytics-db/dw')
df.to_sql('fact_sales', warehouse_engine, if_exists='append')

Ma trận quyết định: Bạn cần loại nào?

Các startup thường phức tạp hóa vấn đề này quá sớm. Nếu tập dữ liệu của bạn dưới 100.000 dòng, một instance PostgreSQL duy nhất là quá đủ. Nhưng một khi bạn đụng phải bức tường “Production bị chậm”, hãy sử dụng hướng dẫn này:

Tiếp tục dùng OLTP (PostgreSQL, MySQL) nếu:

  • Bạn cần xử lý các lượt click và đăng nhập của người dùng theo thời gian thực.
  • Tính toàn vẹn dữ liệu là ưu tiên hàng đầu của bạn.
  • Các truy vấn thường nhắm vào một bản ghi duy nhất thông qua ID.
  • Khối lượng công việc của bạn thiên về ghi (nhiều lệnh cập nhật và chèn mới).

Chuyển sang OLAP (ClickHouse, BigQuery, Snowflake) nếu:

  • Bạn đang xây dựng các bảng điều khiển (dashboards) hoặc báo cáo nội bộ.
  • Bạn cần tổng hợp dữ liệu trên hàng triệu hoặc hàng tỷ dòng.
  • Dữ liệu chủ yếu là “chỉ thêm mới” (như nhật ký sự kiện hoặc dữ liệu cảm biến).
  • Các lệnh join phức tạp đang làm chậm ứng dụng chính của bạn.

Bài học rút ra sau những sự cố

Sự cố lúc 2 giờ sáng mà tôi kể trên đã có một giải pháp khắc phục nhanh. Chúng tôi đã dừng truy vấn gây lỗi và thiết lập một bản sao chỉ đọc (read-only replica) cho đội ngũ marketing. Tuy nhiên, đó chỉ là một miếng băng cá nhân tạm thời. Giải pháp thực sự là chuyển lịch sử năm năm của chúng tôi vào một môi trường OLAP chuyên dụng.

Đừng đợi cho đến khi cơ sở dữ liệu production của bạn bốc cháy. Nếu các truy vấn phân tích của bạn mất hơn năm giây, đã đến lúc ngừng sử dụng transactional engine như một kho dữ liệu. Hãy tách biệt các nhiệm vụ. Giữ OLTP cho người dùng và dùng OLAP để tìm kiếm thông tin chi tiết.

Share: