← Bài trước | Series hub | Tiếp theo →

Chương 5: Mở Khóa Hiệu Suất Database Thông Qua Connection Pooling

Nếu hệ thống Golang của bạn nghiền ngẫm business logic (logic nghiệp vụ) nhanh như chớp nhưng lại sặc nước nghẹn ứ ngay ở tầng Database, thì 90% nguyên nhân bắt nguồn từ một chiếc cấu hình *sql.DB lệch pha.

1. Thấu Hiểu *sql.DB

Answer-first: Trong thế giới Golang, hàm sql.Open() KHÔNG HỀ khởi tạo một đường truyền kết nối chọc thẳng xuống database. Nó đóng vai trò khởi chạy một cỗ máy quản đốc Connection Pool (hồ chứa kết nối) an toàn luồng (thread-safe). Điều kiện bắt buộc là bạn chỉ được phép khởi tạo cái biến db này đúng một lần duy nhất trong suốt chu kỳ khởi động app.

Cỗ máy Connection Pool này nhận lệnh quản lý chuyện tạo kết nối mới tinh, tái chế tận thu xài lại đồ cũ (idle), rồi vung tay bóp nát vứt xó mớ dôi dư không xài tới. Nó được sinh ra để đạt chuẩn an toàn luồng tuyệt đối mặc cho hàng nghìn Goroutines xúm vào xâu xé.

2. Các Tham Số Bầu Chọn Sinh Tử

Package database/sql chìa ra 4 tham số cấu hình, ác nỗi các chỉ số mặc định (defaults) của chúng lại là những quả bom nổ chậm đếm ngược trong các môi trường High-Concurrency:

A. SetMaxOpenConns (Ngưỡng Mở Tối Đa)

  • Mặc định: 0 (Không giới hạn - Unlimited).
  • Thảm Họa: Lỡ đâu 10.000 requests ập tới cùng lúc, Go sẽ hồn nhiên bóp cò đòi đẻ ra 10.000 TCP connections. Cái máy chủ DB Server sẽ bay màu trong nháy mắt cùng dòng thông báo ai oán too many clients (quá tải kết nối).
  • Thuốc Giải: Luôn luôn tự tay khóa chặt một giới hạn cứng. Các kinh nghiệm thực tiễn khuyên chọn giá trị neo khoảng 50 cho tới 200 tùy cấu hình sức mạnh phần cứng DB. Luật bất thành văn: Tham số MaxOpenConns của app BẮT BUỘC nhỏ hơn thiết lập max_connections dưới DB.

B. SetMaxIdleConns (Số Lượng Nằm Không)

  • Mặc định: 2.
  • Thảm Họa: Đây là thủ phạm hàng đầu gây ra nạn lag giật đứt quãng (intermittent lag) trong Go apps. 100 cái concurrent requests nhào vô, Go moi 2 connections rảnh rỗi (idle) ra xài đỡ, rồi lại phải gồng mình cõng cái penalty (tiền phạt) siêu tốn kém TCP Handshake để đúc mới 98 cái nữa. Ăn xong xả rác, nó băm nát vứt đi 98 thằng, lại giữ khư khư đúng 2. Cái vòng xoáy tạo mới rồi đập đi nhồi nhét này đẩy máy chủ vào địa ngục độ trễ (latency).
  • Thuốc Giải: Kéo mốc MaxIdleConns lấp tối thiểu vào khoảng 25% - 50% của cái MaxOpenConns. Ở những hệ thống cõng dòng traffic dày đặc bủa vây, giới kỹ sư đa phần chơi bài chốt cứng MaxIdleConns == MaxOpenConns.

C. Tuổi Thọ Chờ Sinh Tử Của Connection

Xin chớ bao giờ cưng nựng dung túng để một connection sống dai nhách mãi mãi. Mấy bức tường lửa Cloud Firewalls và gã canh cổng Load Balancers máu lạnh lắm, sẵn sàng xách đao chém phăng mấy kết nối TCP rảnh rỗi câm nín, hậu quả đẻ ra cơ man lỗi broken pipe văng vào mặt Go. Cấu hình chèn lệnh SetConnMaxLifetime hãm tua ở mức 5-10 phút để hệ thống tự giác làm mới hồ chứa theo chu kỳ.

db.SetMaxOpenConns(100)
db.SetMaxIdleConns(50) 
db.SetConnMaxLifetime(10 * time.Minute)
db.SetConnMaxIdleTime(5 * time.Minute)

3. Kiến Trúc Middleware Proxy Cấp Hệ Thống

Answer-first: Khi mà bạn rải quân chạy cày kéo cùng lúc trên hàng tá Kubernetes Pods, cái mớ kết nối cộng dồn đủ sức giẫm bẹp dí cái DB. Hãy thả ngõ cài cắm thêm một proxy cỡ như PgBouncer hay ProxySQL làm nhiệm vụ dồn kênh (multiplex) ép hàng ngàn ngõ gõ cửa từ app túm gọn lọt chui vô chỉ vài mống connections cốt lõi luồn cắm thực thụ vào DB.

Cho dẫu các thiết lập mã Go có đỉnh chóp tới đâu chăng nữa, thì vẫn lòi ra một lỗ hổng hóc búa ở mức độ rải quân kiến trúc (architectural scaling): Dưới môi trường Kubernetes cluster, thử đặt cược cái Microservice mang tên Order nở rộ ra 50 Pods, mà mỗi cái lại ghim thông số MaxOpenConns = 100. Vị chi hệ thống này có thừa năng lực cạp xả thẳng bạo lực 5.000 connections trút dội lên đầu Database. Lượng cổng gõ nườm nượp cỡ này đè bẹp xẹp lép cái Database CPU đơn thuần chỉ qua cái khâu trớ trêu gọi là OS Context Switching (Chuyển đổi ngữ cảnh Hệ Điều Hành).

Phao cứu sinh sách giáo khoa kinh điển là ngắt đường dội chặn giao thông thông qua giải pháp Connection Pool Middleware ở ngưỡng cluster-level, tiêu biểu có gã PgBouncer (cho giới Postgres) hoặc là ProxySQL (đi với MySQL). Mớ bòng bong 50 Go Pods mặc tình tha hồ tung hoành quăng quật giao tiếp đứt đoạn với PgBouncer qua tổng cộng 5.000 connections lỏng lẻo mỏng nhẹ (dạng RAM connections), rồi tên PgBouncer đóng vai trò gò nắn bóp dồn đống này luồn lách ép cho bằng được về quy mô chưa tới ~200 real TCP connections hàng thật giá thật luồn găm thẳng băng xuống chọc đáy Database.