Khi cơ sở dữ liệu đơn node của bạn chạm ngưỡng giới hạn
Tôi thường chọn PostgreSQL ngay khi dự án cần một cấu trúc dữ liệu (schema) tin cậy. Nó là một “con ngựa thồ” thực thụ. Nhưng cuối cùng, mọi ứng dụng tăng trưởng nhanh đều sẽ chạm tới trần giới hạn. Có thể cơ sở dữ liệu chính của bạn đã phình to lên tới 2TB, hoặc bạn đang phải trả 2.000 USD mỗi tháng cho một instance RDS 64-core khổng lồ nhưng CPU vẫn luôn ở mức 90% trong giờ cao điểm.
Đây chính là giới hạn kinh điển của việc mở rộng theo chiều dọc (vertical scaling). Bạn có thể tiếp tục đổ tiền vào các instance lớn hơn, nhưng hiệu quả mang lại sẽ giảm dần. Phân mảnh (sharding) thủ công ở cấp ứng dụng là một lối thoát, nhưng đó là một cơn ác mộng về bảo trì và buộc bạn phải viết lại logic ứng dụng. Citus mang đến một con đường gọn gàng hơn. Đây là một extension mã nguồn mở giúp biến Postgres thành một công cụ phân tán, trải rộng dữ liệu và tải truy vấn của bạn trên một cụm (cluster) gồm nhiều node.
Tôi thích Citus vì nó không phải là một bản fork. Vì là một extension tiêu chuẩn, bạn không bị mất đi các tính năng tuyệt vời của Postgres. Bạn vẫn có thể sử dụng JSONB cho dữ liệu bán cấu trúc, PostGIS cho các dịch vụ định vị, và tìm kiếm toàn văn (full-text search) trong khi vẫn có thể mở rộng ra hàng chục máy chủ.
Cài đặt: Thiết lập một Cluster trong vài phút
Cách hiệu quả nhất để thử nghiệm thiết lập phân tán là thông qua Docker Compose. Trong khi môi trường production yêu cầu cài đặt postgresql-16-citus-12.1 trên máy chủ vật lý hoặc máy ảo, Docker cho phép bạn hình dung kiến trúc ngay lập tức.
Một Citus cluster dựa trên một Coordinator và nhiều Workers.
Coordinator quản lý metadata và điều phối truy vấn. Các Worker thực hiện các công việc nặng nhọc, lưu trữ các phân mảnh dữ liệu (shards) thực tế. Bạn có thể coi Coordinator như một nhạc trưởng và các Worker là dàn nhạc.
Lưu tệp docker-compose.yml này để kiểm tra thiết lập:
version: '3.8'
services:
coordinator:
image: citusdata/citus:12.1
ports:
- "5432:5432"
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=mypassword
worker1:
image: citusdata/citus:12.1
depends_on: [coordinator]
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=mypassword
worker2:
image: citusdata/citus:12.1
depends_on: [coordinator]
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=mypassword
Khởi động cluster bằng lệnh docker-compose up -d. Khi các container đã sẵn sàng, hãy truy cập vào coordinator để đăng ký các worker. Bạn có thể thực hiện việc này thông qua bất kỳ SQL client tiêu chuẩn nào.
-- Đăng ký các node để coordinator biết nơi gửi dữ liệu
SELECT citus_add_node('worker1', 5432);
SELECT citus_add_node('worker2', 5432);
-- Kiểm tra xem cluster đã hoạt động chưa
SELECT * FROM citus_get_active_worker_nodes();
Kiến trúc: Phân mảnh dữ liệu đúng cách
Chỉ cài đặt extension thôi thì chưa thể giải quyết ngay các vấn đề hiệu năng. Bạn phải chọn một Distribution Column (Shard Key) để chỉ cho Citus cách chia nhỏ dữ liệu. Lựa chọn này quyết định hiệu suất của cluster khi chịu tải.
Hãy tưởng tượng một nền tảng SaaS theo dõi hàng triệu sự kiện cho các công ty khác nhau. Trong trường hợp này, tenant_id hoặc user_id là lựa chọn hợp lý. Bằng cách sharding trên một ID chung, bạn đảm bảo tất cả dữ liệu của một khách hàng cụ thể sẽ nằm trên cùng một node vật lý.
1. Định nghĩa Schema
Bắt đầu bằng việc tạo các bảng trên coordinator. Lưu ý yêu cầu về khóa chính: Citus yêu cầu shard key phải là một phần của bất kỳ ràng buộc duy nhất (unique constraint) nào.
CREATE TABLE users (
user_id bigserial PRIMARY KEY,
email text,
created_at timestamptz DEFAULT now()
);
CREATE TABLE events (
event_id bigserial,
user_id bigint,
event_type text,
payload jsonb,
created_at timestamptz DEFAULT now(),
PRIMARY KEY (user_id, event_id)
);
2. Phân phối khối lượng công việc
Tiếp theo, hãy sử dụng hàm create_distributed_table. Đối với các bảng tra cứu nhỏ—như danh sách mã quốc gia—hãy sử dụng create_reference_table. Các bảng tham chiếu (reference tables) được sao chép đến mọi worker, giúp việc join dữ liệu cực kỳ nhanh chóng.
-- Phân mảnh cả hai bảng theo user_id để cho phép đồng vị trí (co-location)
SELECT create_distributed_table('users', 'user_id');
SELECT create_distributed_table('events', 'user_id');
Đồng vị trí (Co-location) chính là bí quyết ở đây. Vì thông tin người dùng và các sự kiện của họ nằm trên cùng một worker, Postgres có thể thực hiện các phép join tại địa phương. Điều này ngăn chặn việc phải “tráo đổi” (shuffling) hàng gigabyte dữ liệu qua mạng, vốn là nguyên nhân chính gây ra độ trễ trong các hệ thống phân tán.
3. Tải dữ liệu minh bạch
Bạn không cần phải thay đổi các câu lệnh INSERT trong ứng dụng. Khi bạn gửi dữ liệu đến coordinator, nó sẽ băm (hash) user_id và điều hướng dòng dữ liệu đến đúng phân mảnh. Cảm giác giống như đang làm việc với một cơ sở dữ liệu duy nhất, ngay cả khi bạn đang ghi vào 50 máy chủ khác nhau.
Tính song song: Thấy rõ hiệu quả về hiệu năng
Lợi ích thực sự xuất hiện khi bạn chạy một truy vấn tổng hợp trên một tập dữ liệu khổng lồ. Thay vì một lõi CPU phải xử lý 500 triệu dòng, Citus sẽ chia nhỏ truy vấn thành các phân đoạn và thực thi chúng trên tất cả các worker cùng một lúc.
EXPLAIN ANALYZE
SELECT count(*) FROM events
WHERE event_type = 'checkout';
Kiểm tra đầu ra để tìm “Citus Adaptive Executor.” Nếu bạn có 10 worker, bạn sẽ nhận được gấp 10 lần băng thông I/O và sức mạnh xử lý cho lần quét đó. Một truy vấn từng mất 30 giây giờ đây có thể hoàn thành trong 3 giây.
Xử lý lệch dữ liệu (Data Skew)
Đôi khi một khách hàng lớn hơn nhiều so với những khách hàng khác, dẫn đến một phân mảnh bị quá tải (“hot” shard). Bạn có thể theo dõi điều này bằng cách truy vấn citus_shards để xem phân bổ kích thước. Nếu một worker đang gặp khó khăn, hãy sử dụng hàm rebalance_table_shards(). Nó di chuyển dữ liệu giữa các node trong khi cơ sở dữ liệu vẫn đang online, giữ cho hệ thống cân bằng mà không bị gián đoạn.
Giám sát nâng cao
Tôi luôn khuyên bạn nên bật citus_stat_statements. Đây là một phần mở rộng của pg_stat_statements tiêu chuẩn giúp theo dõi hiệu suất truy vấn phân tán. Nó giúp bạn xác định xem các truy vấn của mình đang truy cập vào một node duy nhất (nhanh) hay yêu cầu phát sóng (broadcast) toàn cluster (chậm hơn).
-- Xác định các truy vấn phân tán nặng nhất
SELECT query, calls, total_exec_time
FROM citus_stat_statements
ORDER BY total_exec_time DESC
LIMIT 5;
Mở rộng cơ sở dữ liệu hiếm khi là công việc “làm một lần là xong”. Tuy nhiên, Citus loại bỏ sự phức tạp của việc phân mảnh thủ công và cho phép bạn mở rộng quy mô khi lưu lượng truy cập tăng lên. Nếu instance Postgres của bạn bắt đầu quá tải, chuyển sang mô hình phân tán là bước đi hợp lý tiếp theo.

