← Bài trước | Series hub

Chương 9: Mở Rộng Điểm Thắt Cổ Chai Database Cuối Cùng

Đến ngưỡng ứng dụng của bạn gom được hàng chục triệu người dùng, Database sẽ tự động hóa thân thành điểm thắt cổ chai tối thượng. CPU giãy giụa chạm kịch trần 100%, RAM cạn kiệt, và mớ queries chạy mất tính bằng giây thay vì phần nghìn giây. Đây chính là cột mốc bắt buộc bạn phải tung ra các quân bài chiến lược phân tán database.

1. Tách Đọc/Ghi (Read/Write Splitting)

Answer-first: Vì đặc thù 80% lưu lượng đổ về chỉ thuần túy là thao tác Read-only (Chỉ đọc), hãy xẻ đôi hệ thống DB thành một con Master chuyên lo Ghi (Write) và một bầy Slaves chuyên lo Đọc (Read). Móc plugin dbresolver của GORM vào để hệ thống tự động bẻ lái định tuyến các queries mà khỏi phải đập đi xây lại mớ business logic.

Trong các mô hình ứng dụng tiêu chuẩn, các thao tác Đọc (Select) thường ngốn tới 80-90% tổng traffic, trong khi mớ lệnh Ghi (Insert/Update) lại chỉ lẹt đẹt quanh mốc 10-20%. Cố tình nhồi nhét chung một cục vào độc một cái DB sẽ làm ba cái query Select nặng chịch dính lỗi khóa bảng (lock tables), làm tê liệt toàn bộ dây chuyền ống xả (pipeline) mảng Ghi.

Kiến Trúc Nền: Dàn trận một cỗ máy Master (thiết quân luật CẤM ĐỌC chỉ để GHI) đóng đô sát cạnh một bầy Slave/Replica (CẤM GHI chỉ thuần ĐỌC). Cỗ máy Master sẽ liên tục chép bản sao dữ liệu (replicate) một cách bất đồng bộ truyền tay xuống đám Slaves.

Triển Khai Bằng Golang: Chớ có khờ dại cắm đầu gõ một rổ if-else hổ lốn hòng rẽ nhánh DB connections bằng tay. Xài luôn đồ chơi có sẵn plugin dbresolver từ ông lớn GORM:

import "gorm.io/plugin/dbresolver"

db.Use(dbresolver.Register(dbresolver.Config{
    Sources:  []gorm.Dialector{mysql.Open("master_dsn")},
    Replicas: []gorm.Dialector{mysql.Open("slave1_dsn"), mysql.Open("slave2_dsn")},
    Policy:   dbresolver.RandomPolicy{},
}))

GORM tự động ngửi hơi nhạy bén bóc tách code của bạn: dòng db.Create() lập tức dội ngược về Master, trong lúc mớ db.Find() được chia chác load-balance rải đều cho bầy Slaves. Khả năng scale mảng Đọc lúc này coi như bung xõa vô cực.

Báo Động Replication Lag (Trễ Sao Chép): Bởi vì quy trình chép Master-xuống-Slave luôn có độ trễ mất vài mili-giây, rủi có vị khách nào vừa lưu cái profile mới (Write vô Master) xong lại đè f5 tải lại trang tức thì (Read từ Slave), họ có rủi ro bị nhìn thấy cái dữ liệu cũ mèm. Cách xử lý: Cắm một cái cờ flag ngay tại session của người dùng ngay sau khi họ vừa thực thi phép lưu (mutation), ép cứng mọi lệnh read tiếp theo của tay này buộc phải nện thẳng xuống Master trong vòng 3 giây tới.

2. Băm Dữ Liệu (Database Sharding)

Answer-first: Tới chừng duy nhất một cái bảng phình ra cỡ hàng tỷ dòng (rows), chia tách vụ Đọc vẫn chưa thấm tháp vào đâu. Sharding ra đòn trảm vung ngang bổ làm nhiều mảnh cái bảng đó rải ra vất trên nhiều hệ thống physical servers khác nhau. Thuật toán Consistent Hashing là nước đi đỉnh cao hòng né mớ rắc rối phải khuân vác dời đô (data migrations) trong mỗi đợt co giãn tăng giảm kích cỡ cluster.

Tới hồi khối dữ liệu sưng vù cả tỷ dòng ghi (điển hình như mảng Lịch sử Giao dịch Transaction History), ổ cứng của con Master khét lẹt báo hết chỗ chứa, còn gánh nặng đúc Index (chỉ mục) thì đạp gãy lưng tốc độ của tiến trình Write. Kỹ nghệ Read/Write splitting giờ đây hóa phế nhân. Bạn bị dồn vào đường cùng phải “chặt” (slice) khối database đó vỡ ra thành vô vàn mảnh vụn lấm tấm bé hơn.

Cái môn đó gọi là Sharding. Loanh quanh có hai môn phái chọn Shard Key kinh điển:

A. Range-based Sharding (Băm theo mốc khoảng)

  • Băm (Shards) dọc theo dải ID hay là Ngày Tháng: Node số 1 vác hộ ID từ 1 cho tới 10M; Node số 2 gánh tiếp 10M trôi tới 20M.
  • Ưu Điểm: Scale out dễ như trở bàn tay. Chừng nào Node 2 nhét lèn cứng ngắc thì cứ vung tiền sắm thêm Node 3 thôi.
  • Nhược Điểm: Tật nguyền Hotspotting (Điểm nóng chết người). Đám dữ liệu vừa mới ra lò bao giờ cũng bị bâu vô coi xâu xé nhiều nhất, suy ra cục Node mới mua về lúc nào cũng è cổ ra gồng còng lưng trong khi đám Nodes cũ già lão lại thảnh thơi nằm ngáp ruồi.

B. Hash-based Sharding (Băm theo thuật toán Hash)

  • Nhúng tay xài phép toán modulo: Hash(User_ID) % Node_Count.
  • Ưu Điểm: Dữ liệu trôi dạt phân bổ dàn trải láng o hoàn hảo không một tì vết lên khắp bề mặt dăm ba con máy chủ. Cắt đứt hẳn dịch bệnh hotspots.
  • Tử Huyệt: Lỡ bạn đang có đúng 3 Nodes, bạn nhét chia modulo cho 3. Nhỡ may thèm thuồng vác thêm con Node số 4 về lắp vào, chao ôi cả cái mẫu số toán học lật ngược thay đổi hoàn toàn! 99% đống data nhà bạn tự nhiên bị thằng hash đá bay lộn tùng phèo qua cái server sai bét, bắt bạn phải triển luôn một thảm kịch dọn nhà di tản (data migration) toàn diện hệ thống.

C. Consistent Hashing (Băm Nhất Quán)

Đặng vá cái lổ hổng nứt toác của phép modulo, các kỹ sư ánh xạ kết quả đầu ra của cục hash lên một cái vòng khuyên kín (circular ring) chạy từ mốc $0$ lên đến $2^{32}-1$. Lũ Database Nodes lần lượt thả rớt ngẫu nhiên đặt đậu trên chiếc vòng này. Khi một hạt data bốc trúng một tọa độ hash, nó cứ cuốc bộ vòng quanh theo chiều kim đồng hồ chạy dọc theo chiếc khuyên cho tới mốc đạp trúng con Node đầu tiên cản ngang đường. Hồi sắm thêm một con Server mới ráp vào, sẽ chỉ có một vụn con con data dính sát rạt nó mới phải lục đục dời đi. Khoảng còn lại của cái cluster vẫn yên bình bất động chẳng sứt mẻ gì!

3. Chớ Dại Đi Chế Tạo Lại Bánh Xe (Don’t Reinvent the Wheel)

Tuyệt đối cấm không được nhúng tay viết logic sharding bằng đoạn code Golang thô. Hãy xài mớ middleware cấp hệ thống được nhào nặn riêng nhằm hậu thuẫn hệ thống distributed databases ví dụ như TiDB, ShardingSphere, hay gã khổng lồ Vitess (đang đôn hậu đài cho Slack với YouTube). Cái app Go nhà bạn chỉ việc kết nối vô Vitess y chang lúc gọi một cục MySQL database thông thường, chỉ có điều ở đằng sau hậu trường, tay Vitess âm thầm khôn lanh bẻ lái rẽ nhánh truy vấn lượn lách xuyên hầm qua cả chục mảnh backend shards.

(Khép lại Chuỗi Bài Giảng Mastering High-Concurrency Series (Làm Chủ Hệ Thống High-Concurrency) tại đây. Chúc các hệ thống Go của bạn sừng sững uy nghi đứng vững chẳng sờn lòng trước những trận bão táp traffic bạt ngàn!)