Họ đang đo gì
Bạn có tư duy về bão hoà tài nguyên không. Tăng đồng thời quá điểm bão hoà làm thông lượng GIẢM, và rất nhiều người không tin điều đó cho tới khi tự đo.
Trả lời ngắn~30 giây
Thường là giảm. Database chỉ có bấy nhiêu lõi CPU và bấy nhiêu trục đĩa; vượt qua điểm đó, thêm kết nối chỉ thêm chuyển ngữ cảnh, tranh chấp khoá và tranh chấp bộ nhớ đệm — thông lượng đi ngang rồi tụt, còn độ trễ p99 thì bốc lên. Công thức khởi điểm quen thuộc là khoảng số lõi × 2 + số trục đĩa, thường ra một con số nhỏ đến mức gây ngạc nhiên, cỡ 10–20 cho một node.
Giải thích sâu
Trực giác sai ở đây đến từ việc nghĩ kết nối là chỗ chứa công việc. Thực ra nó là chỗ chờ. Nếu database xử lý được 20 truy vấn song song với hiệu suất tốt nhất, thì việc để 500 kết nối cùng vào không làm nó nhanh hơn — nó chỉ chuyển hàng đợi từ chỗ bạn kiểm soát được (pool trong ứng dụng) vào chỗ bạn không kiểm soát được (bộ lập lịch của hệ điều hành), nơi mỗi truy vấn giữ khoá và bộ nhớ trong lúc chờ.
Với Postgres còn một chi phí nữa: mỗi kết nối là một tiến trình riêng, tốn vài megabyte và một phần bộ nhớ chia sẻ. Vài trăm kết nối rỗi vẫn ăn RAM đáng lẽ dành cho page cache. Đó là lý do PgBouncer ở chế độ transaction pooling tồn tại — nó cho phép nghìn kết nối phía ứng dụng ánh xạ xuống vài chục kết nối phía server.
Nhưng trước khi chỉnh số, cần hỏi vì sao pool cạn. Rất thường là do transaction giữ quá lâu: một lời gọi HTTP nằm giữa BEGIN và COMMIT, hoặc một job chạy nền dùng chung pool với đường request. Sửa nguyên nhân đó thường làm nhu cầu kết nối giảm một bậc, và không cấu hình nào thay thế được việc đó.
Ở mức senior, câu trả lời đầy đủ có thêm phần tách pool: đường request, job nền và migration nên dùng pool riêng với hạn mức riêng, để một job chạy loạn không nuốt hết kết nối của người dùng thật. Đây là kiểu bulkhead, và nó biến một sự cố toàn hệ thống thành một sự cố cục bộ.
Câu hỏi tiếp theo họ sẽ hỏi
?Serverless thì sao? Mỗi instance một pool.
Đó chính là chỗ mô hình pool truyền thống vỡ: 500 lambda × pool 5 là 2.500 kết nối. Phải có proxy đứng giữa (PgBouncer, RDS Proxy, Supabase pooler) hoặc dùng driver HTTP không giữ kết nối. Trả lời được điểm này cho thấy bạn theo kịp thực tế triển khai hiện nay.
?Đo bằng chỉ số nào để biết pool đủ hay thiếu?
Thời gian CHỜ lấy kết nối (acquire wait), chứ không phải số kết nối đang dùng. Chờ gần bằng 0 mà database chưa bão hoà thì pool đủ; chờ tăng trong khi CPU database vẫn thấp thì nghẽn nằm ở nơi khác — thường là khoá hoặc I/O.
Trả lời thế này là mất điểm
- Tăng
max_connectionslên 2.000 và coi là đã sửa. Bạn vừa dời hàng đợi vào chỗ tối hơn. - Không hỏi transaction đang giữ bao lâu. Đó gần như luôn là nguyên nhân thật.
Trả lời thế này là ghi điểm
- Nhắc rằng pool nhỏ làm p99 TỐT hơn dù thông lượng không đổi, vì hàng đợi có kiểm soát thì thời gian chờ đoán được.