ClickHouse trên Docker: Cài đặt và Tối ưu hóa Truy vấn OLAP Hiệu năng cao

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

Khởi động nhanh (5 phút): Chạy ClickHouse với Docker Compose

Việc chạy một câu lệnh tổng hợp dữ liệu thông thường như COUNT(DISTINCT user_id) trên 50 triệu bản ghi trong PostgreSQL hoặc MySQL thường mất từ 20 đến 40 giây và đẩy CPU lên mức 100%. ClickHouse xử lý cùng một truy vấn đó chỉ trong chưa đầy 15 mili-giây. Hệ thống đạt được tốc độ này nhờ lưu trữ dữ liệu theo cột thay vì theo dòng, nén các kiểu dữ liệu tương đồng lên đến 90% và chỉ quét đúng các cột cần thiết cho truy vấn của bạn.

Để bắt đầu, hãy tạo thư mục dự án và một file docker-compose.yml:

mkdir clickhouse-docker && cd clickhouse-docker
touch docker-compose.yml

Thêm cấu hình sau vào docker-compose.yml. Thiết lập này mở port 8123 cho lưu lượng HTTP (REST client, Grafana và web dashboard) cùng port 9000 cho các kết nối TCP native:

version: '3.8'

services:
  clickhouse:
    image: clickhouse/clickhouse-server:24.3
    container_name: clickhouse-server
    ports:
      - "8123:8123"
      - "9000:9000"
    environment:
      CLICKHOUSE_DB: analytics
      CLICKHOUSE_USER: analyst
      CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1
      CLICKHOUSE_PASSWORD: StrongPassword123!
    volumes:
      - ./clickhouse_data:/var/lib/clickhouse/
      - ./clickhouse_logs:/var/log/clickhouse-server/
    ulimits:
      nofile:
        soft: 262144
        hard: 262144
    restart: unless-stopped

Khởi động container ở chế độ chạy nền (detached mode):

docker compose up -d

Khi container đã sẵn sàng hoạt động, hãy kết nối vào phiên client tương tác:

docker exec -it clickhouse-server clickhouse-client -u analyst --password StrongPassword123!

Kiểm tra quá trình cài đặt bằng cách xem phiên bản engine và database đang hoạt động:

SELECT version(), currentDatabase();

Tìm hiểu sâu: Table Engine và Nạp dữ liệu (Data Ingestion)

ClickHouse dựa vào các storage engine chuyên dụng. Họ engine MergeTree là nền tảng cho hầu hết mọi hệ thống phân tích trong môi trường production. Khi dữ liệu đến, ClickHouse sẽ ghi các phần dữ liệu (data parts) đã sắp xếp xuống đĩa và thực hiện merge bất đồng bộ ở chế độ nền để tối ưu dung lượng lưu trữ và tăng tốc quét index.

Thiết kế Schema MergeTree

Xét một bảng sự kiện ghi nhận tương tác của người dùng trên nền tảng web. Khóa sắp xếp (sorting key) được thiết lập bám sát theo các điều kiện lọc truy vấn phổ biến nhất:

CREATE TABLE analytics.page_views (
    event_date Date,
    event_timestamp DateTime,
    user_id UInt64,
    url LowCardinality(String),
    referrer LowCardinality(String),
    device_type LowCardinality(String),
    country LowCardinality(FixedString(2)),
    duration_seconds UInt16
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, country, user_id)
SETTINGS index_granularity = 8192;

Các lựa chọn kiến trúc quan trọng

  • LowCardinality(T): Thay thế các chuỗi ký tự lặp lại bằng các khóa từ điển số gọn nhẹ. Đối với các trường có dưới 10.000 giá trị duy nhất (như country, device_type hoặc HTTP status code), cơ chế này giúp giảm dung lượng ổ đĩa và mức tiêu thụ bộ nhớ từ 60% đến 80%.
  • PARTITION BY: Phân tách các bản ghi thành các thư mục riêng biệt theo tháng trên ổ đĩa (ví dụ: 202610_1_1_0). Khi truy vấn lọc theo event_date >= '2026-10-01', ClickHouse sẽ bỏ qua hoàn toàn các thư mục tháng cũ hơn mà không cần đọc dữ liệu của chúng. Hãy tránh phân vùng theo giờ hoặc theo ngày, vì việc tạo ra hơn 1.000 partition tổng thể sẽ làm giảm hiệu năng merge.
  • ORDER BY: Xây dựng sparse primary index (chỉ mục chính thưa). ClickHouse ghi một index mark sau mỗi 8.192 dòng (mức index_granularity mặc định). Nhờ đó, toàn bộ index cho 100 triệu dòng chỉ chiếm vài megabyte RAM, giúp việc quét theo dải giá trị (range scan) định vị các khối dữ liệu phù hợp trong thời gian chỉ tính bằng phần nghìn giây.

Import dữ liệu qua HTTP và công cụ Native

ClickHouse hỗ trợ parse native các định dạng CSV, TSV, Parquet và JSON. Khi chuẩn bị các file CSV tạm thời để stream JSON, bạn có thể chuyển đổi chúng cục bộ bằng toolcraft.app/vi/tools/data/csv-to-json. Công cụ này chạy trực tiếp trên trình duyệt, giúp dữ liệu nhạy cảm được giữ an toàn trên máy của bạn.

Để stream dữ liệu JSON phân tách bằng dòng mới (newline-delimited JSON) trực tiếp vào ClickHouse bằng curl:

curl -s -u "analyst:StrongPassword123!" -X POST "http://localhost:8123/?query=INSERT+INTO+analytics.page_views+FORMAT+JSONEachRow" \
  --data-binary '{"event_date":"2026-10-01","event_timestamp":"2026-10-01 10:00:00","user_id":1024,"url":"/pricing","referrer":"google.com","device_type":"desktop","country":"US","duration_seconds":45}
{"event_date":"2026-10-01","event_timestamp":"2026-10-01 10:05:12","user_id":2048,"url":"/docs","referrer":"direct","device_type":"mobile","country":"VN","duration_seconds":120}'

Sử dụng nâng cao: Materialized View và Tổng hợp dữ liệu theo thời gian thực

Việc quét 500 triệu sự kiện thô để tính toán tổng số liệu theo thời gian thực vẫn có thể mất 300 đến 800 mili-giây trong các đợt lưu lượng truy cập cao điểm. Materialized View trong ClickHouse giải quyết triệt để độ trễ này. Chúng hoạt động như các trigger khi insert, tự động tổng hợp dữ liệu trong quá trình ghi và lưu trữ các bản ghi tổng hợp vào một bảng đi kèm.

Tổng hợp trước các chỉ số theo thời gian thực

Tạo bảng đích sử dụng SummingMergeTree để lưu trữ các chỉ số đã được tổng hợp:

CREATE TABLE analytics.daily_country_stats (
    event_date Date,
    country LowCardinality(FixedString(2)),
    total_views UInt64,
    total_duration UInt64
)
ENGINE = SummingMergeTree()
ORDER BY (event_date, country);

CREATE MATERIALIZED VIEW analytics.mv_daily_country_stats
TO analytics.daily_country_stats AS
SELECT
    event_date,
    country,
    count() AS total_views,
    sum(duration_seconds) AS total_duration
FROM analytics.page_views
GROUP BY event_date, country;

Mỗi khi bạn insert các dòng vào page_views, materialized view sẽ xử lý các batch dữ liệu mới này và tăng dần các bộ đếm trong daily_country_stats. Các truy vấn trên bảng đã tổng hợp này trả về kết quả chỉ trong 2 đến 4 mili-giây:

SELECT 
    country,
    sum(total_views) AS views,
    round(sum(total_duration) / sum(total_views), 2) AS avg_duration_sec
FROM analytics.daily_country_stats
WHERE event_date >= today() - 7
GROUP BY country
ORDER BY views DESC
LIMIT 5;

Nguyên tắc triển khai cho Workload OLAP trong môi trường Production

1. Luôn thực hiện Insert theo Batch

Luôn gửi dữ liệu theo từng batch từ 10.000 đến 100.000 dòng. ClickHouse tạo ra một thư mục part vật lý riêng biệt cho mỗi lệnh INSERT. Việc insert từng dòng đơn lẻ hàng trăm lần mỗi giây sẽ nhanh chóng kích hoạt lỗi DB::Exception: Too many parts (300) in all data parts in table. Nếu dịch vụ thu thập của bạn nhận sự kiện từng cái một, hãy đặt một bộ đệm trung gian (như Apache Kafka, Vector hoặc Table Engine Buffer của ClickHouse) phía trước bảng đích.

2. Sắp xếp Primary Key theo mức độ chọn lọc của bộ lọc

Cấu trúc mệnh đề ORDER BY sao cho các cột được sử dụng thường xuyên nhất trong các bộ lọc so khớp bằng (equality filter) đứng đầu, sắp xếp từ độ biến thiên (cardinality) thấp đến cao. Một index được định nghĩa dưới dạng (event_date, country, user_id) cho phép ClickHouse bỏ qua dữ liệu hiệu quả khi lọc theo ngày và quốc gia. Việc đặt các trường có cardinality cao như user_id lên đầu sẽ phá vỡ cơ chế loại bỏ phạm vi ngày (date-range pruning) của index.

3. Xem các thao tác Mutation là tác vụ quản trị nặng

ClickHouse không được thiết kế cho các tác vụ cập nhật mang tính giao dịch (transactional update). Việc chạy ALTER TABLE ... UPDATE hoặc DELETE sẽ kích hoạt các mutation bất đồng bộ và ghi lại toàn bộ các data part trên đĩa, gây tiêu tốn đáng kể băng thông I/O. Để cập nhật trạng thái bản ghi hoặc xử lý khử trùng lặp (deduplication), hãy triển khai ReplacingMergeTree hoặc CollapsingMergeTree thay vì dùng các lệnh mutation SQL trực tiếp.

4. Cấu hình Ulimit trong Docker cho môi trường tải đồng thời cao

Một truy vấn phức tạp trải rộng trên nhiều cột và đa luồng có thể mở đồng thời hàng trăm file descriptor. Việc thiết lập nofile thành 262144 trong Docker Compose giúp ngăn tiến trình server bị crash với lỗi Too many open files khi xử lý các tải phân tích nặng.

Share: