Skip to content

Zoom-in: Connection Pool

Karify98·
Cover Image for Zoom-in: Connection Pool

Ứng dụng của bạn đột ngột chậm hoặc sập khi traffic tăng cao, log xuất hiện lỗi "Too many connections" dù các câu query đã được tối ưu chỉ số index.

graph TD
    App(["💻 App Service"]) -->|"Query 1 (Tạo kết nối mới)"| DB[("🗄️ Database")]
    App -->|"Query 2 (Tạo kết nối mới)"| DB
    App -->|"Query N (Tạo kết nối mới)"| DB
    style App fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
    style DB fill:#1a3a2a,stroke:#22c55e,color:#86efac

Phóng to dần vào đó.


Layer 1 — Chi phí khởi tạo: 3-way Handshake và xác thực

Mỗi khi ứng dụng gửi một câu query lên database và đóng kết nối ngay sau khi nhận kết quả, một loạt các bước đắt đỏ xảy ra ở tầng mạng và hệ thống.

sequenceDiagram
    participant A as App Service
    participant D as Database

    A->>D: 1. TCP Syn (Bắt tay 3 bước)
    D-->>A: 2. TCP Syn-Ack
    A->>D: 3. TCP Ack
    A->>D: 4. TLS Handshake (Mã hóa)
    D-->>A: 5. Khóa bảo mật trao đổi xong
    A->>D: 6. Gửi username/password (Xác thực)
    D-->>A: 7. Xác thực thành công (Session được tạo)

Để thiết lập một kết nối mới, hệ thống cần thực hiện bắt tay 3 bước TCP, bắt tay TLS để mã hóa, và cuối cùng là gửi thông tin xác thực để khởi tạo session phía database. Toàn bộ quá trình này có thể tốn từ 10ms đến hơn 100ms tùy thuộc vào khoảng cách địa lý, đẩy latency của ứng dụng tăng vọt trước khi câu query thực tế được chạy.

Vấn đề còn lại: việc tạo và đóng kết nối liên tục làm lãng phí CPU, băng thông mạng và tài nguyên của database cho các tác vụ phi-chức-năng.

Layer 2 — Connection Pool: Giữ kết nối luôn mở và tái sử dụng

Thay vì mở và đóng kết nối cho mỗi query, Connection Pool hoạt động như một bộ quản lý (Pool Manager) duy trì sẵn một số lượng kết nối luôn mở ở trạng thái rảnh rỗi (idle).

sequenceDiagram
    participant App as 💻 App Code
    participant PM as 📦 Pool Manager
    participant DB as 🗄️ Database

    App->>PM: 1. Yêu cầu kết nối (Checkout)
    PM-->>App: 2. Trả về kết nối số #3 (Sẵn sàng)
    App->>DB: 3. SELECT * FROM users (Chạy query nhanh)
    DB-->>App: 4. Trả về kết quả
    App->>PM: 5. Trả lại kết nối (Checkin)
    Note over PM: Kết nối số #3 trở về trạng thái Idle

Khi ứng dụng cần truy xuất dữ liệu, nó sẽ "mượn" (checkout) một kết nối trống từ pool. Query được gửi đi ngay lập tức mà không cần qua các bước handshake. Sau khi nhận kết quả, ứng dụng "trả lại" (checkin) kết nối vào pool để luồng xử lý khác tiếp tục sử dụng.

Vấn đề còn lại: nếu lượng traffic tăng đột biến vượt quá số lượng kết nối sẵn có trong pool, chuyện gì sẽ xảy ra?

Layer 3 — Throttling & Queueing: Chốt chặn điều tiết tải

Khi toàn bộ kết nối trong pool đều bận rộn (active), Pool Manager sẽ kích hoạt cơ chế xếp hàng chờ (Queue) thay vì mở thêm kết nối vô hạn.

graph TD
    R["📥 Request mới"] --> PM{"📦 Pool Manager"}
    PM -->|"Có kết nối rảnh"| Alloc["Cấp kết nối cho Request"]
    PM -->|"Hết kết nối rảnh"| Q["⏳ Hàng đợi (Queue)"]
    Q -->|"Chờ quá Timeout"| Fail["❌ Trả về lỗi Timeout"]
    Q -->|"Có kết nối được trả lại"| Alloc
    style R fill:#1e293b,stroke:#475569,color:#cbd5e1
    style PM fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
    style Q fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd

Nếu một request phải chờ quá thời gian cấu hình tối đa (connection timeout) mà vẫn không có kết nối nào được giải phóng, pool sẽ từ chối request đó và trả về lỗi lập tức. Cơ chế này đóng vai trò như một bộ điều tiết tải (backpressure), bảo vệ database khỏi việc bị quá tải bộ nhớ và CPU do phải duy trì hàng ngàn session cùng một lúc.


Full picture

sequenceDiagram
    participant C as Client Request
    participant App as App Code
    participant PM as Pool Manager (Max: 10)
    participant DB as Database

    C->>App: 1. HTTP Request
    App->>PM: 2. Mượn kết nối
    alt Có kết nối rảnh (Idle)
        PM-->>App: Trả về kết nối số #5
        App->>DB: SELECT...
        DB-->>App: Dữ liệu
        App->>PM: Trả lại kết nối số #5
        App-->>C: 200 OK
    else Hết kết nối rảnh (Cả 10 đang bận)
        PM-->>PM: Đưa Request vào Hàng đợi
        alt Có kết nối trả về trước Timeout
            Note over PM: Kết nối số #1 được trả về
            PM-->>App: Trả về kết nối số #1
            App->>DB: SELECT...
            DB-->>App: Dữ liệu
            App->>PM: Trả lại kết nối số #1
            App-->>C: 200 OK
        else Quá Timeout (ví dụ: > 30s)
            PM-->>App: Lỗi Connection Timeout
            App-->>C: 500 Internal Server Error
        end
    end

Takeaway

Connection Pool không chỉ là công cụ để tối ưu hiệu năng và giảm độ trễ cho ứng dụng bằng cách bỏ qua chi phí bắt tay. Nó là một chốt chặn kiến trúc quan trọng để quản lý tài nguyên hữu hạn của hệ thống. Thiết lập kích thước pool (pool size) quá lớn sẽ làm nghẽn database, trong khi thiết lập quá nhỏ sẽ gây nghẽn ở hàng đợi ứng dụng.


Bài viết được hỗ trợ bởi Amy 🌸 - AI Assistant. Nội dung đã được kiểm duyệt bởi tác giả.

Related Posts