Mở rộng SQL không còn nỗi lo Sharding: Hướng dẫn về CockroachDB

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

Bức tường rào cản khi mở rộng: Tại sao SQL truyền thống thất bại

Việc mở rộng một cơ sở dữ liệu thường đi theo một lộ trình đầy đau đớn và có thể dự đoán trước. Ứng dụng của bạn đạt mức tăng trưởng lưu lượng gấp 10 lần, và đột nhiên instance PostgreSQL hoặc MySQL chính của bạn luôn ở mức 100% CPU. Bản năng đầu tiên là mở rộng theo chiều dọc (vertical scaling)—nâng cấp lên một instance mạnh mẽ hơn với 128 core và 2TB RAM. Nhưng cuối cùng, bạn sẽ chạm tới một ngưỡng giới hạn cứng, nơi chi phí phần cứng trở nên thiên văn hoặc các giới hạn vật lý của một máy đơn lẻ đơn giản là không thể đáp ứng nổi.

Sharding thủ công là phương pháp “khắc phục” truyền thống, nhưng nó thường tệ hơn cả vấn đề mà nó giải quyết. Bạn kết thúc bằng việc chia nhỏ dữ liệu trên nhiều instance cơ sở dữ liệu dựa trên một khóa như user_id. Điều này buộc logic ứng dụng của bạn phải theo dõi vị trí của các shard, khiến các tác vụ join giữa các shard gần như không thể thực hiện được và biến các giao dịch ACID thành một cơn ác mộng về hệ thống phân tán. CockroachDB được xây dựng đặc biệt để loại bỏ sự phức tạp này.

Khởi đầu nhanh: Chạy một Cluster cục bộ trong 5 phút

CockroachDB được xây dựng từ đầu cho tính chất phân tán. Ngay cả trên một máy tính xách tay duy nhất, bạn có thể mô phỏng một cluster đa node để xem cách nó xử lý sao chép dữ liệu và chuyển đổi dự phòng (failover). Chúng ta có thể sử dụng Docker để khởi tạo một cluster ba node trong vài giây.

Đầu tiên, hãy thiết lập một mạng bridge để các node có thể giao tiếp với nhau:

docker network create -d bridge roachnet

Tiếp theo, khởi chạy ba node. Tham số --join là thành phần quan trọng ở đây; nó cho các node biết cách tìm thấy các node đồng hàng để hình thành một đơn vị logic duy nhất:

# Node 1
docker run -d --name=roach1 --hostname=roach1 --net=roachnet -p 26257:26257 -p 8080:8080 cockroachdb/cockroach:v23.1.10 start --insecure --join=roach1,roach2,roach3

# Node 2
docker run -d --name=roach2 --hostname=roach2 --net=roachnet cockroachdb/cockroach:v23.1.10 start --insecure --join=roach1,roach2,roach3

# Node 3
docker run -d --name=roach3 --hostname=roach3 --net=roachnet cockroachdb/cockroach:v23.1.10 start --insecure --join=roach1,roach2,roach3

Khởi tạo cluster bằng cách chạy lệnh này trên bất kỳ node đơn lẻ nào:

docker exec -it roach1 ./cockroach init --insecure

Cơ sở dữ liệu SQL phân tán của bạn hiện đã sẵn sàng. Truy cập http://localhost:8080 để xem DB Console, nơi cung cấp các chỉ số thời gian thực về sức khỏe của node và thông lượng truy vấn. Bạn có thể kết nối bằng Postgres client tiêu chuẩn hoặc shell tích hợp sẵn:

docker exec -it roach1 ./cockroach sql --insecure

Cơ chế vận hành: Cách CockroachDB quản lý dữ liệu

Các cơ sở dữ liệu tiêu chuẩn thường sử dụng mô hình leader-follower, trong đó follower chỉ là một bản dự phòng chờ sẵn. CockroachDB thay đổi cách tiếp cận này bằng cách sử dụng thuật toán đồng thuận Raft. Nó coi mọi node là một thành phần bình đẳng, có khả năng xử lý cả việc đọc và ghi.

Dữ liệu được chia thành các khối 64MB gọi là “ranges”. Mỗi range được sao chép trên ít nhất ba node. Khi có một lệnh ghi đến, node “Leaseholder” đảm bảo đa số các bản sao xác nhận thay đổi trước khi xác nhận thành công. Nếu một node gặp sự cố, cluster sẽ tự động sao chép lại dữ liệu bị thiếu để duy trì hệ số nhân bản mong muốn. Quá trình này diễn ra ngầm mà không cần bất kỳ sự can thiệp thủ công nào.

Khả năng tương thích PostgreSQL

CockroachDB sử dụng giao thức kết nối (wire protocol) của PostgreSQL (phiên bản 3.0), nghĩa là nó hoạt động ngay lập tức với các công cụ như TypeORM, Sequelize, hoặc GORM. Bạn không cần driver tùy chỉnh. Tuy nhiên, nó không phải là một nhánh (fork) của Postgres; nó được viết bằng Go và ưu tiên mức độ cô lập serializable. Điều này cung cấp mức độ nhất quán dữ liệu cao nhất, ngăn chặn các tình trạng tranh chấp (race conditions) thường gặp ở các mức độ cô lập yếu hơn.

Tối ưu hóa việc nhập dữ liệu

Di chuyển từ các hệ thống cũ thường liên quan đến các định dạng dữ liệu lộn xộn. Khi tôi cần chuyển đổi nhanh các tập dữ liệu thô cho việc di chuyển schema, tôi sử dụng toolcraft.app/vi/tools/data/csv-to-json. Nó xử lý mọi thứ ngay trên trình duyệt. Điều này giúp giữ dữ liệu nhạy cảm không bị đẩy lên các máy chủ bên ngoài trong khi tôi đang xây dựng các script nhập dữ liệu thử nghiệm.

Sử dụng nâng cao: Vượt qua sự cố vùng (Region)

Đối với các ứng dụng toàn cầu, việc sống sót sau khi một máy chủ bị sập là chưa đủ. Bạn cần sống sót ngay cả khi toàn bộ một region của AWS hoặc GCP bị mất điện. CockroachDB giải quyết vấn đề này bằng Locality Flags, cho phép bạn xác định vị trí địa lý vật lý cho phần cứng của mình.

Trong môi trường production, bạn xác định vị trí của node khi khởi động:

cockroach start \
--certs-dir=certs \
--locality=region=us-east,zone=us-east-1a \
--join=node1.example.com,node2.example.com \
--advertise-addr=node1.example.com

Các flag này cho phép đặt dữ liệu một cách thông minh. Bạn có thể ghim dữ liệu người dùng châu Âu vào các node ở Frankfurt và London trong khi giữ dữ liệu Mỹ ở New York. Điều này giải quyết bài toán “tốc độ ánh sáng” bằng cách giảm độ trễ cho người dùng địa phương trong khi vẫn duy trì một cơ sở dữ liệu thống nhất duy nhất cho các nhà phát triển. Đó chính là “chén thánh” của các hệ thống phân tán theo địa lý.

Những bài học kinh nghiệm xương máu cho môi trường Production

Quản lý một hệ thống phân tán là một thử thách hoàn toàn khác so với quản lý một VPS đơn lẻ. Dưới đây là một vài kinh nghiệm quan trọng từ thực tế:

  • Bắt buộc dùng TLS: Flag --insecure chỉ dành cho phát triển cục bộ. Trong production, hãy sử dụng lệnh cockroach cert để tạo chứng chỉ node và client nhằm mã hóa toàn bộ lưu lượng giữa các node.
  • Theo dõi Clock Skew: Các cơ sở dữ liệu phân tán sống còn nhờ sự đồng bộ thời gian. Nếu độ lệch đồng hồ giữa các node vượt quá 500ms, CockroachDB sẽ tự dừng tiến trình để ngăn chặn hư hỏng dữ liệu. Hãy sử dụng chrony hoặc NTP để giữ đồng hồ của bạn sai lệch trong khoảng vài mili giây.
  • Bỏ qua Auto-Increment: Việc sử dụng số nguyên tuần tự cho khóa chính sẽ tạo ra các “hotspots” (điểm nóng), nơi tất cả các lệnh ghi mới đều dồn vào một node duy nhất. Hãy sử dụng UUID hoặc hash_sharded_index để phân tán tải trọng trên toàn bộ cluster.
  • Load Balance mọi thứ: Đừng bao giờ trỏ ứng dụng của bạn vào IP của một node duy nhất. Hãy sử dụng HAProxy hoặc Nginx stream để phân phối các kết nối. Điều này đảm bảo rằng nếu một node bị sập, ứng dụng của bạn thậm chí còn không nhận ra sự khác biệt.

Tối ưu hiệu năng

Nếu các truy vấn có vẻ chậm chạp, hãy kiểm tra kết quả EXPLAIN ANALYZE. Trong môi trường phân tán, việc quét toàn bộ bảng (full table scan) cực kỳ tốn kém vì nó yêu cầu nhiều vòng truyền tải qua mạng. Việc đánh index đúng cách không chỉ là một lời khuyên; đó là sự khác biệt giữa phản hồi 10ms và timeout 2 giây.

CockroachDB là một kỳ quan kỹ thuật giúp chấm dứt nỗi sợ hãi về việc mở rộng. Nó cho phép bạn bắt đầu nhỏ và phát triển lên hàng triệu người dùng mà không bao giờ phải thiết kế lại lớp dữ liệu của mình.

Share: