QuestDB: Xử lý 4 triệu bản ghi/giây và dấu chấm hết cho nỗi lo sập Database lúc nửa đêm

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

Cứu nguy lúc 2 giờ sáng: Khi PostgreSQL và MySQL “đầu hàng” trước dữ liệu IoT

Điện thoại rung bần bật vào lúc 2 giờ sáng là cơn ác mộng của mọi kỹ sư vận hành. Dashboard Grafana trắng xóa, CPU server chạm ngưỡng 100%, còn IOPS thì nhảy kịch trần. Hệ thống giám sát chính thức tê liệt.

Tôi từng nếm trái đắng này trong một dự án theo dõi cảm biến nhà máy. Ban đầu, team chọn PostgreSQL vì sự quen thuộc. Tuy nhiên, khi quy mô tăng lên 5.000 sensor, mỗi giây đẩy về hơn 100.000 bản ghi, PostgreSQL bắt đầu hụt hơi. Index phình to hàng trăm GB, câu lệnh INSERT bị nghẽn cổ chai, còn truy vấn dữ liệu theo khoảng thời gian (time-range) mất cả phút mới phản hồi.

Thực tế, MySQL hay MongoDB đều tuyệt vời cho các nghiệp vụ thông thường. Nhưng với dữ liệu chuỗi thời gian (Time-series) cần tốc độ ghi hàng triệu dòng mỗi giây, chúng không phải đối thủ của QuestDB. QuestDB được tối ưu cực đoan: Lưu trữ dạng cột (columnar), tận dụng tập lệnh SIMD của CPU và hỗ trợ SQL thuần thục.

Quick Start: Chạy QuestDB trong vòng 5 phút

Đừng tốn thời gian cho các bước cấu hình rườm rà. Cách nhanh nhất để triển khai là sử dụng Docker. QuestDB cực kỳ gọn nhẹ và không kéo theo hàng tá thư viện phụ thuộc.

docker run -p 9000:9000 -p 8812:8812 -p 9009:9009 -p 9003:9003 \
  questdb/questdb

Dưới đây là các cổng (port) bạn cần lưu ý:

  • 9000: Web Console. Hãy truy cập localhost:9000 để quản lý database qua giao diện trực quan.
  • 8812: Postgres Wire Protocol. Cổng này cho phép bạn dùng chung thư viện của PostgreSQL để kết nối.
  • 9009: InfluxDB Line Protocol. Đây là “đường cao tốc” để đẩy dữ liệu với tốc độ lớn nhất.

Tại sao QuestDB lại nhanh đến mức vô lý?

Sự khác biệt nằm ở cách lưu trữ. Các database truyền thống như MySQL lưu dữ liệu theo dòng (row-based). Để tính trung bình nhiệt độ của 1 tỷ bản ghi, Postgres phải quét qua toàn bộ các cột không liên quan như sensor_id hay location. Việc này gây lãng phí tài nguyên trầm trọng.

QuestDB chọn hướng đi khác. Nó lưu trữ theo cột. Khi cần tính toán nhiệt độ, hệ thống chỉ đọc đúng cột đó trên ổ cứng. Kết hợp với cơ chế phân vùng theo thời gian (Time-partitioning), QuestDB loại bỏ hoàn toàn các vùng dữ liệu thừa.

Trong một bài test thực tế với 100 triệu dòng dữ liệu, phép tính AVG trên QuestDB hoàn thành chỉ trong vài trăm miligiây. Tốc độ này nhanh hơn PostgreSQL gấp 15 lần mà không cần tinh chỉnh cấu hình phức tạp.

Đẩy dữ liệu: Ưu tiên InfluxDB Line Protocol (ILP)

Dù QuestDB hỗ trợ SQL INSERT, nhưng đó không phải cách tối ưu cho IoT. Để đạt hiệu suất cao nhất, bạn nên dùng InfluxDB Line Protocol (ILP). Giao thức này đẩy dữ liệu trực tiếp vào RAM trước khi flush xuống đĩa cứng theo định kỳ.

Đoạn mã Python dưới đây mô phỏng việc đẩy dữ liệu từ sensor qua socket:

import socket
import time

HOST, PORT = 'localhost', 9009

def send_sensor_data():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((HOST, PORT))
        while True:
            # Cấu trúc: table_name,tags fields timestamp
            data = f"sensors,sensor_id=S001 temperature=25.5,humidity=60.2 {time.time_ns()}\n"
            s.sendall(data.encode())
            time.sleep(0.01) # Gửi 100 bản ghi mỗi giây

send_sensor_data()

Điểm cộng lớn là QuestDB hỗ trợ Schema-less. Nó tự động tạo bảng và thêm cột mới ngay khi nhận dữ liệu lần đầu, giúp bạn tiết kiệm thời gian thiết kế DB.

Truy vấn thông minh với SQL mở rộng

QuestDB không bắt bạn học ngôn ngữ mới như Flux. Nó sử dụng SQL tiêu chuẩn nhưng bổ sung thêm các từ khóa “đáng đồng tiền bát gạo” cho Time-series.

1. SAMPLE BY: Gom nhóm thời gian cực nhanh

Quên đi các hàm xử lý date-time phức tạp trong GROUP BY. Với QuestDB, bạn chỉ cần một dòng lệnh:

SELECT timestamp, avg(temperature)
FROM sensors
SAMPLE BY 1h; -- Tự động gom nhóm trung bình theo mỗi giờ

2. LATEST BY: Lấy trạng thái mới nhất tức thì

Để biết trạng thái hiện tại của toàn bộ sensor, SQL truyền thống yêu cầu JOINMAX(timestamp) rất nặng nề. QuestDB xử lý việc này trong chớp mắt:

SELECT * FROM sensors LATEST BY sensor_id;

3. ASOF JOIN: Kết hợp dữ liệu lệch pha

Đây là tính năng cực mạnh cho tài chính và IoT. Khi bảng prices và bảng trades không khớp chính xác từng mili giây, ASOF JOIN sẽ tự động lấy giá trị gần nhất trước đó để khớp lệnh.

Bài học xương máu khi triển khai thực tế

Dù mạnh mẽ, QuestDB vẫn có những quy tắc riêng mà bạn cần tuân thủ để tránh treo hệ thống:

  • Bắt buộc dùng Partitioning: Luôn khai báo PARTITION BY DAY hoặc MONTH khi tạo bảng. Điều này giúp việc xóa dữ liệu cũ (retention) diễn ra tức thì mà không gây lock bảng.
  • Quản lý dữ liệu Out-of-order: QuestDB hoạt động tốt nhất khi dữ liệu gửi lên theo đúng trình tự thời gian. Nếu dữ liệu bị trễ (lag) quá nhiều, hãy điều chỉnh tham số cairo.max.uncommitted.rows để tối ưu bộ nhớ đệm.
  • Phần cứng: Hãy đầu tư SSD hoặc NVMe. QuestDB thực hiện ghi tuần tự rất nhiều, tốc độ ổ cứng là yếu tố then chốt quyết định throughput.
  • Giới hạn SELECT *: Với bảng hàng tỷ dòng, việc chạy SELECT * không kèm LIMIT trên Web Console sẽ khiến trình duyệt của bạn bị treo do quá tải dữ liệu hiển thị.

Tổng kết

QuestDB không sinh ra để thay thế hoàn toàn PostgreSQL trong các bài toán giao dịch tài chính cần tính ACID khắt khe. Tuy nhiên, nếu bạn đang vật lộn với bài toán giám sát hạ tầng hoặc tracking hàng triệu thiết bị IoT, đây chính là giải pháp cứu cánh. Thay vì thức trắng đêm tối ưu index, hãy để QuestDB xử lý gánh nặng đó để bạn có thể ngủ ngon hơn.

Share: