logo

Lộ trình

Khóa học

Tài liệu

Mock Interview

Liên hệ

Quay lại
  • Trang chủ

    /

  • Tài liệu

    /

  • Connection Pool: Tối ưu hiệu suất kết nối Database trong hệ thống Backend
Tài liệu

Connection Pool: Tối ưu hiệu suất kết nối Database trong hệ thống Backend

Ronin Engineer

19 Tháng 7 2025

<p>By <a href="https://www.linkedin.com/in/cndvn/?ref=roninhub.com" rel="noreferrer">Đức Hiếu</a></p><h1 id="1-connection-pool-l%C3%A0-g%C3%AC"><strong>1. Connection Pool là gì?</strong></h1><p>Connection Pool là một cơ chế quản lý kết nối giữa các ứng dụng đến cơ sở dữ liệu (Database Server), trong đó một tập hợp các kết nối được khởi tạo trước và duy trì sẵn sàng để sử dụng. Thay vì phải thiết lập kết nối mới mỗi khi cần truy vấn dữ liệu. Database Client (thường là Backend App) có thể "mượn" một kết nối có sẵn từ pool, sử dụng xong và trả lại pool để tái sử dụng.</p><h2 id="11-v%E1%BA%A5n-%C4%91%E1%BB%81-v%E1%BB%9Bi-ph%C6%B0%C6%A1ng-ph%C3%A1p-k%E1%BA%BFt-n%E1%BB%91i-truy%E1%BB%81n-th%E1%BB%91ng"><strong>1.1. Vấn đề với phương pháp kết nối truyền thống</strong></h2><p>Trong mô hình kết nối truyền thống, mỗi query xuống Database. Backend App tạo một kết nối mới tới Database Server, điều này dẫn đến một số vấn đề về hiệu suất và tốn tài nguyên (CPU, Memory) của DB server. Cụ thể, ở các bước sau:</p><ul><li><strong>Mỗi lần kết nối đều yêu cầu thiết lập TCP với quy trình bắt tay 3 bước (3-Way Handshake)</strong></li><li><strong>Quá trình xác thực và thiết lập session với Database tốn thời gian</strong></li><li><strong>Khởi tạo và giải phóng kết nối liên tục tạo áp lực lên cả ứng dụng và Database</strong></li></ul><h2 id="12-minh-h%E1%BB%8Da-th%C3%B4ng-qua-v%C3%AD-d%E1%BB%A5"><strong>1.2. Minh họa thông qua ví dụ</strong></h2><p>Giả sử chúng ta có một API để lấy thông tin profile người dùng:</p> <!--kg-card-begin: html--> <table style="border:none;border-collapse:collapse;"><colgroup><col width="334"><col width="134"></colgroup><tbody><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Thao tác</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Thời gian (ms)</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Kết nối từ Client (Browser) đến Server</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">10 ms</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Mở kết nối từ Server đến Database</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">5 ms</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Thực thi truy vấn lấy thông tin profile</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">100 ms</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Tổng thời gian (không tính xử lý và trả về)</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">115+ ms</span></p></td></tr></tbody></table> <!--kg-card-end: html--> <p>Khi sử dụng Connection Pool, <strong>chi phí 5ms cho việc thiết lập kết nối Database có thể được loại bỏ trong hầu hết các trường hợp, giảm tổng thời gian xuống còn khoảng 110ms.</strong></p><p>Lý thuyết đã xong, giờ chúng ta cùng thực hành để hiểu rõ hơn</p><h1 id="2-quy-tr%C3%ACnh-c%C6%A1-b%E1%BA%A3n-c%E1%BB%A7a-connection-pool"><strong>2. Quy trình cơ bản của Connection pool</strong></h1><p><strong><em>Lưu ý</em></strong>: Các ví dụ và tư liệu dưới đây mình dựa trên package database/sql của Golang và MySQL. Cơ bản các thư viện và các ngôn ngữ khác cũng gần tương tự. Để chắc chắn các bạn có thể tự thực hành trên ngôn ngữ hoặc thư viện mà bạn đang sử dụng để hiểu rõ hơn</p><ul><li>Khi ứng dụng khởi động, Connection Pool được khởi tạo.</li><li>Khi cần truy vấn Database, ứng dụng yêu cầu một kết nối từ pool.</li><li>Nếu có kết nối available (free) trong pool thì pool sẽ cung cấp kết nối đó ngay lập tức.</li><li>Nếu không có kết nối available và chưa đạt giới hạn tối đa, pool sẽ tạo kết nối mới.</li><li>Nếu đã đạt giới hạn tối đa, yêu cầu sẽ phải chờ hoặc bị từ chối tùy theo cấu hình.</li><li>Sau khi sử dụng xong và bạn gọi hàm đóng kết nối.<em> Tùy một số ngôn ngữ lập trình hoặc thư viện mà có hàm để nhận biết đóng kết nối. </em>ví dụ <em>ở Go là lúc mà bạn gọi hàm Close()</em>. Lúc này kết nối được trả lại pool thay vì đóng hoàn toàn.</li></ul><p>Xem diagram dưới để thấy chi tiết hơn</p><figure class="kg-card kg-image-card"><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfM6flx4QPilQELFtcP5TKPdSuUqPy5B3fV10CjYz-1Qtt23KQZ9kr4sGgrWeVy0rqWbtRS1JdfwsYFIlV9QfiOh3ubb_DDD8Au5U98Vv1XE06GlsXfsS7MiDkT3IB_BejmXPFzAw?key=CmMXf4v7mA6e8cTIZIo24g" class="kg-image" alt="" loading="lazy" width="801" height="1041"></figure><h2 id="21-c%C3%A1c-h%C3%A0m-quan-tr%E1%BB%8Dng-trong-connection-pool"><strong>2.1 Các hàm quan trọng trong Connection Pool</strong></h2> <!--kg-card-begin: html--> <table style="border:none;border-collapse:collapse;"><colgroup><col width="155"><col width="466"></colgroup><tbody><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Hàm</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Mô tả</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">SetMaxOpenConns</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Số lượng kết nối tối đa được mở cùng lúc</span></p></td></tr><tr style="height:38.5pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">SetMaxIdleConns</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Số lượng kết nối tối đa được giữ trong trạng thái idle (nhàn rỗi) trong pool</span></p></td></tr><tr style="height:38.5pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">SetMaxIdleTime</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Thời gian tối đa một kết nối được phép ở trạng thái idle trước khi bị đóng. tính từ lúc nó available(nhàn rỗi)</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">SetConnMaxLifetime</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Thời gian tối đa một kết nối được phép tồn tại kể từ khi được tạo</span></p></td></tr></tbody></table> <!--kg-card-end: html--> <h2 id="22-c%C3%A1c-ch%E1%BB%89-s%E1%BB%91-theo-d%C3%B5i-hi%E1%BB%87u-su%E1%BA%A5t-connection-pool"><strong>2.2 Các chỉ số theo dõi hiệu suất Connection Pool</strong></h2> <!--kg-card-begin: html--> <table style="border:none;border-collapse:collapse;"><colgroup><col width="183"><col width="441"></colgroup><tbody><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Chỉ số</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Ý nghĩa</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">MaxOpenConnections</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Số lượng kết nối tối đa được phép mở cùng lúc</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">OpenConnections</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Số lượng kết nối đang mở (bao gồm cả đang sử dụng và idle)</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">InUse</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Số lượng kết nối đang được sử dụng (đang thực thi truy vấn)</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Idle</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Số lượng kết nối đang ở trạng thái nhàn rỗi trong pool</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">WaitCount</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Tổng số yêu cầu đã từng phải chờ kết nối</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">WaitDuration</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Tổng thời gian các yêu cầu đã phải chờ</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">MaxIdleClosed</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Số lượng kết nối đã bị đóng do vượt quá MaxIdleConns</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">MaxIdleTimeClosed</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Số lượng kết nối đã bị đóng do vượt quá ConnMaxIdleTime</span></p></td></tr><tr style="height:25pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">MaxLifetimeClosed</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Số lượng kết nối đã bị đóng do vượt quá ConnMaxLifetime</span></p></td></tr></tbody></table> <!--kg-card-end: html--> <h1 id="3-demo"><strong>3. Demo</strong></h1><h2 id="31-video-demo">3.1 Video Demo</h2><figure class="kg-card kg-embed-card"><iframe width="200" height="150" src="https://www.youtube.com/embed/qbUe39V9qw0?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" title="Connection Pool 101 | Backend Engineering"></iframe></figure><h2 id="32-code-demo">3.2 Code Demo</h2><h3 id="321-so-s%C3%A1nh-m%C3%B4-h%C3%ACnh-k%E1%BA%BFt-n%E1%BB%91i-truy%E1%BB%81n-th%E1%BB%91ng-v%E1%BB%9Bi-d%C3%B9ng-connection-pool-%C4%91%E1%BB%83-k%E1%BA%BFt-n%E1%BB%91i">3.2.1 So sánh mô hình kết nối truyền thống với dùng connection pool để kết nối</h3><pre><code class="language-Go">package main import ( "database/sql" "fmt" "log" "net/http" "os" "time" "github.com/gin-gonic/gin" _ "github.com/go-sql-driver/mysql" ) func main() { mode := os.Args[1:] if mode[0] == "pool" { pool() } else { normal() } } func normal() { dns := "root:123456@tcp(127.0.0.1:3306)/test" query := "select name from customer limit 1000,1000;" dbNormal, err := sql.Open("mysql", dns) dbNormal.SetMaxIdleConns(0) // hầu hết thư viện khi bạn mở open là đã tạo pool nên mình set 0 ở đây để 0 có kết nỗi nhàn rỗi nào. tức là khi làm xong nó sẽ release connection luôn if err != nil { log.Fatal("open DB Normal fail", err.Error()) } router := gin.Default() router.GET("normal", func(ctx *gin.Context) { row, err := dbNormal.Query(query) if err != nil { fmt.Println(err.Error()) ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } defer row.Close() ctx.JSON(http.StatusOK, gin.H{"message": "ok"}) }) if err = router.Run(":8081"); err != nil { log.Fatal("Start server fail", err.Error()) } } func pool() { dns := "root:123456@tcp(127.0.0.1:3306)/test" query := "select name from customer limit 1000,1000;" maxOpenConns := 140 maxIdleConns := 70 connMaxIdleTime := 10 * time.Minute dbPool, err := sql.Open("mysql", dns) if err != nil { log.Fatal("open DB Poll fail", err.Error()) } dbPool.SetMaxOpenConns(maxOpenConns) // Số lượng tối đa được mở cùng lúc dbPool.SetMaxIdleConns(maxIdleConns) // Số lượng tối đa nằm trong pool dbPool.SetConnMaxIdleTime(connMaxIdleTime) // Thời gian tồn tại tối đa của 1 connection khi nhàn rỗi router := gin.Default() router.GET("pool", func(ctx *gin.Context) { row, err := dbPool.Query(query) if err != nil { fmt.Println(err.Error()) ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } defer row.Close() ctx.JSON(http.StatusOK, gin.H{"message": "ok"}) }) if err = router.Run(":8082"); err != nil { log.Fatal("Start server fail", err.Error()) } } </code></pre><h3 id="322-t%C3%ACm-hi%E1%BB%83u-c%C3%A1c-h%C3%A0m-v%C3%A0-ch%E1%BB%89-s%E1%BB%91-c%E1%BA%A7n-thi%E1%BA%BFt">3.2.2 Tìm hiểu các hàm và chỉ số cần thiết</h3><pre><code class="language-Go">package main import ( "context" "database/sql" "encoding/json" "fmt" "log" "net/http" "text/template" "time" "github.com/gin-gonic/gin" _ "github.com/go-sql-driver/mysql" ) func main() { connectionPoolPractice() } const viewerHTML = ` &lt;!DOCTYPE html&gt; &lt;html lang="en"&gt; &lt;head&gt; &lt;meta charset="UTF-8"/&gt; &lt;title&gt;Realtime DB Pool Stats&lt;/title&gt; &lt;link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css"&gt; &lt;style&gt; body { background: #23272f; color: #e6edf3; font-family: sans-serif; padding: 24px } pre { background: #161b22; padding: 12px; border-radius: 8px; } &lt;/style&gt; &lt;/head&gt; &lt;body&gt; &lt;h2&gt;Realtime DB pool stats&lt;/h2&gt; &lt;pre id="json"&gt;&lt;/pre&gt; &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"&gt;&lt;/script&gt; &lt;script&gt; const el = document.getElementById("json"); const sse = new EventSource("/info-pool/sse"); sse.onmessage = (e) =&gt; { let json = JSON.parse(e.data); el.textContent = JSON.stringify(json, null, 2); hljs.highlightElement(el); }; &lt;/script&gt; &lt;/body&gt; &lt;/html&gt; ` func connectionPoolPractice() { dns := "root:123456@tcp(127.0.0.1:3306)/test" db, err := sql.Open("mysql", dns) if err != nil { panic(err) } db.SetMaxOpenConns(3) // số lượng tối đa được mở cùng lúc. Cái này nên &gt; maxIdleConns để tối ưu db.SetMaxIdleConns(2) // số lượng tối đa nằm trong pool db.SetConnMaxIdleTime(time.Second * 10) // thời gian tồn tại tối đa của 1 connection tính từ lúc nó available(nhàn rỗi) db.SetConnMaxLifetime(time.Second * 30) router := gin.Default() router.GET("/info-pool/sse", func(ctx *gin.Context) { ctx.Writer.Header().Set("Content-Type", "text/event-stream") ctx.Writer.Header().Set("Cache-Control", "no-cache") ctx.Writer.Header().Set("Connection", "keep-alive") ctx.Writer.Header().Set("Connection", "keep-alive") ctx.Writer.Flush() clientGone := ctx.Writer.CloseNotify() ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() for { select { case &lt;-clientGone: return case &lt;-ticker.C: stats := db.Stats() // Serialize stats thành JSON jsonStats, err := json.Marshal(stats) if err != nil { continue } fmt.Fprintf(ctx.Writer, "data: %s\n\n", jsonStats) ctx.Writer.Flush() } } }) router.GET("/view", func(ctx *gin.Context) { tmpl, err := template.New("viewer").Parse(viewerHTML) if err != nil { ctx.String(http.StatusInternalServerError, "Template error") return } tmpl.Execute(ctx.Writer, nil) }) router.GET("/pool", func(ctx *gin.Context) { conn, err := db.Conn(context.Background()) if err != nil { fmt.Println(err.Error()) } defer conn.Close() row, err := conn.QueryContext(context.Background(), "select sleep(10)") if err != nil { fmt.Println(err.Error()) } defer row.Close() ctx.JSON(200, "ok") }) router.Run() } </code></pre><p></p><h1 id="4-c%C3%A1ch-m%C3%ACnh-t%E1%BB%91i-%C6%B0u-connection-pool">4. <strong>Cách mình tối ưu Connection Pool</strong></h1><p>Mọi thứ dưới đây là kinh nghiệm bản thân mình thông qua dự án thực tế và kiến thức học được ở khóa <strong>Advanced Backend của Ronin.</strong></p><p>Việc tối ưu này theo mình biết hiện tại chưa có một công thức chuẩn cụ thể nào cho chúng ta follow theo. Cho nên <strong>chỉ có thể tuning dần </strong>để tìm ra các chỉ số phù với hệ thống mà các bạn đang làm.</p><ul><li><strong>Tham số chỉ ra số lượng connection tối đa được mở đồng thời (MaxOpenConns):</strong> Đây là tham số mà mình thấy <strong>quan trọng nhất</strong> vì nó quyết định tới performance của Database. Nếu quá thấp sẽ gây ra hiện tượng nghẽn cổ chai, quá cao sẽ gây áp lực lên Database. Có nhiều yếu tố ảnh hưởng tới tham số này:<ul><li>Hardware capacity của DB server và Client</li><li>Độ dài trung bình của các transaction</li><li>Kiến trúc của DB (standalone, cluster)</li><li>…</li></ul></li><li><strong>Tham số chỉ ra số lượng connection tối đa ở trong pool (MaxIdleConns):</strong> Nên thiết lập MaxIdleConns thấp hơn hoặc bằng MaxOpenConns. Sau một vài dự án thực chiến thì <em>mình đúc kết ra 2 cách tính cho riêng mình</em> là MaxIdleConns = MaxOpenConns * 0.5 hoặc MaxIdleConns = MaxOpenConns * 0.75.&nbsp;</li><li><strong>Tham số chỉ ra tuổi thọ kết nối (ConnMaxIdleTime, ConnMaxLifetime):</strong> Sử dụng ConnMaxIdleTime và ConnMaxLifetime để đảm bảo kết nối không bị quá cũ, tránh các vấn đề như dư thừa connection không dùng tới gây chiếm connection ở ứng dụng khác</li></ul><p>Thường xuyên theo dõi các chỉ số như WaitCount, WaitDuration để phát hiện và xử lý sớm các vấn đề về hiệu suất.</p><p><strong>Bài này chỉ đang nói về Internal Connection Pool tức là Connection Pool bên trong App (Backend App)</strong>. Với những hệ thống nhỏ một Backend với một Database chúng ta có thể tuning dần để đưa ra các tham số phù hợp cho hệ thống. Nhưng với các hệ thống bắt đầu scale bằng cách thêm instance hay Database sử dụng Replication Master-Slave thì lúc này chúng ta cần dùng tới các<strong> External Connection Pool như ProxySQL</strong> mà mình sẽ giới thiệu ở phần sau</p><h1 id="5-k%E1%BA%BFt-lu%E1%BA%ADn"><strong>5. Kết luận</strong></h1><p><strong>Connection Pool là một kỹ thuật quan trọng trong việc tối ưu hiệu suất Database</strong>. Thông qua việc tái <strong>sử dụng kết nối</strong>, Connection Pool giúp giảm đáng kể thời gian thiết lập kết nối, tăng tốc độ phản hồi của ứng dụng và giảm tải cho Database.</p><p>Hiểu rõ cách hoạt động và các tham số cấu hình của Connection Pool sẽ giúp các Backend Engineer xây dựng hệ thống với hiệu suất tối ưu, đáp ứng được các yêu cầu về tốc độ và khả năng mở rộng trong môi trường thực tế.</p><p>Tham khảo thêm:</p><p><a href="https://go.dev/doc/database/manage-connections?ref=roninhub.com#connection_pool_properties"><u>https://go.dev/doc/database/manage-connections#connection_pool_properties</u></a></p><p><a href="https://dev.mysql.com/doc/connector-j/en/connector-j-usagenotes-j2ee-concepts-connection-pooling.html?ref=roninhub.com"><u>https://dev.mysql.com/doc/connector-j/en/connector-j-usagenotes-j2ee-concepts-connection-pooling.html</u></a></p><p><a href="https://www.academia.edu/3272493/Database_Performance_Optimization_by_Connection_Pooling?ref=roninhub.com"><u>https://www.academia.edu/3272493/Database_Performance_Optimization_by_Connection_Pooling</u></a></p>

By Đức Hiếu

1. Connection Pool là gì?

Connection Pool là một cơ chế quản lý kết nối giữa các ứng dụng đến cơ sở dữ liệu (Database Server), trong đó một tập hợp các kết nối được khởi tạo trước và duy trì sẵn sàng để sử dụng. Thay vì phải thiết lập kết nối mới mỗi khi cần truy vấn dữ liệu. Database Client (thường là Backend App) có thể "mượn" một kết nối có sẵn từ pool, sử dụng xong và trả lại pool để tái sử dụng.

1.1. Vấn đề với phương pháp kết nối truyền thống

Trong mô hình kết nối truyền thống, mỗi query xuống Database. Backend App tạo một kết nối mới tới Database Server, điều này dẫn đến một số vấn đề về hiệu suất và tốn tài nguyên (CPU, Memory) của DB server. Cụ thể, ở các bước sau:

  • Mỗi lần kết nối đều yêu cầu thiết lập TCP với quy trình bắt tay 3 bước (3-Way Handshake)
  • Quá trình xác thực và thiết lập session với Database tốn thời gian
  • Khởi tạo và giải phóng kết nối liên tục tạo áp lực lên cả ứng dụng và Database

1.2. Minh họa thông qua ví dụ

Giả sử chúng ta có một API để lấy thông tin profile người dùng:

Thao tác

Thời gian (ms)

Kết nối từ Client (Browser) đến Server

10 ms

Mở kết nối từ Server đến Database

5 ms

Thực thi truy vấn lấy thông tin profile

100 ms

Tổng thời gian (không tính xử lý và trả về)

115+ ms

Khi sử dụng Connection Pool, chi phí 5ms cho việc thiết lập kết nối Database có thể được loại bỏ trong hầu hết các trường hợp, giảm tổng thời gian xuống còn khoảng 110ms.

Lý thuyết đã xong, giờ chúng ta cùng thực hành để hiểu rõ hơn

2. Quy trình cơ bản của Connection pool

Lưu ý: Các ví dụ và tư liệu dưới đây mình dựa trên package database/sql của Golang và MySQL. Cơ bản các thư viện và các ngôn ngữ khác cũng gần tương tự. Để chắc chắn các bạn có thể tự thực hành trên ngôn ngữ hoặc thư viện mà bạn đang sử dụng để hiểu rõ hơn

  • Khi ứng dụng khởi động, Connection Pool được khởi tạo.
  • Khi cần truy vấn Database, ứng dụng yêu cầu một kết nối từ pool.
  • Nếu có kết nối available (free) trong pool thì pool sẽ cung cấp kết nối đó ngay lập tức.
  • Nếu không có kết nối available và chưa đạt giới hạn tối đa, pool sẽ tạo kết nối mới.
  • Nếu đã đạt giới hạn tối đa, yêu cầu sẽ phải chờ hoặc bị từ chối tùy theo cấu hình.
  • Sau khi sử dụng xong và bạn gọi hàm đóng kết nối. Tùy một số ngôn ngữ lập trình hoặc thư viện mà có hàm để nhận biết đóng kết nối. ví dụ ở Go là lúc mà bạn gọi hàm Close(). Lúc này kết nối được trả lại pool thay vì đóng hoàn toàn.

Xem diagram dưới để thấy chi tiết hơn

2.1 Các hàm quan trọng trong Connection Pool

Hàm

Mô tả

SetMaxOpenConns

Số lượng kết nối tối đa được mở cùng lúc

SetMaxIdleConns

Số lượng kết nối tối đa được giữ trong trạng thái idle (nhàn rỗi) trong pool

SetMaxIdleTime

Thời gian tối đa một kết nối được phép ở trạng thái idle trước khi bị đóng. tính từ lúc nó available(nhàn rỗi)

SetConnMaxLifetime

Thời gian tối đa một kết nối được phép tồn tại kể từ khi được tạo

2.2 Các chỉ số theo dõi hiệu suất Connection Pool

Chỉ số

Ý nghĩa

MaxOpenConnections

Số lượng kết nối tối đa được phép mở cùng lúc

OpenConnections

Số lượng kết nối đang mở (bao gồm cả đang sử dụng và idle)

InUse

Số lượng kết nối đang được sử dụng (đang thực thi truy vấn)

Idle

Số lượng kết nối đang ở trạng thái nhàn rỗi trong pool

WaitCount

Tổng số yêu cầu đã từng phải chờ kết nối

WaitDuration

Tổng thời gian các yêu cầu đã phải chờ

MaxIdleClosed

Số lượng kết nối đã bị đóng do vượt quá MaxIdleConns

MaxIdleTimeClosed

Số lượng kết nối đã bị đóng do vượt quá ConnMaxIdleTime

MaxLifetimeClosed

Số lượng kết nối đã bị đóng do vượt quá ConnMaxLifetime

3. Demo

3.1 Video Demo

3.2 Code Demo

3.2.1 So sánh mô hình kết nối truyền thống với dùng connection pool để kết nối

package main


import (
    "database/sql"
    "fmt"
    "log"
    "net/http"
    "os"
    "time"


    "github.com/gin-gonic/gin"
    _ "github.com/go-sql-driver/mysql"
)


func main() {
    mode := os.Args[1:]


    if mode[0] == "pool" {
        pool()
    } else {
        normal()
    }
}


func normal() {
    dns := "root:123456@tcp(127.0.0.1:3306)/test"
    query := "select name from customer limit 1000,1000;"


    dbNormal, err := sql.Open("mysql", dns)
    dbNormal.SetMaxIdleConns(0) // hầu hết thư viện khi bạn mở open là đã tạo pool nên mình set 0 ở đây để 0 có kết nỗi nhàn rỗi nào. tức là khi làm xong nó sẽ release connection luôn
    if err != nil {
        log.Fatal("open DB Normal fail", err.Error())
    }


    router := gin.Default()


    router.GET("normal", func(ctx *gin.Context) {
        row, err := dbNormal.Query(query)
        if err != nil {
            fmt.Println(err.Error())
            ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }


        defer row.Close()


        ctx.JSON(http.StatusOK, gin.H{"message": "ok"})
    })


    if err = router.Run(":8081"); err != nil {
        log.Fatal("Start server fail", err.Error())
    }
}


func pool() {
    dns := "root:123456@tcp(127.0.0.1:3306)/test"
    query := "select name from customer limit 1000,1000;"
    maxOpenConns := 140
    maxIdleConns := 70
    connMaxIdleTime := 10 * time.Minute


    dbPool, err := sql.Open("mysql", dns)
    if err != nil {
        log.Fatal("open DB Poll fail", err.Error())
    }
    dbPool.SetMaxOpenConns(maxOpenConns)       // Số lượng tối đa được mở cùng lúc
    dbPool.SetMaxIdleConns(maxIdleConns)       // Số lượng tối đa nằm trong pool
    dbPool.SetConnMaxIdleTime(connMaxIdleTime) // Thời gian tồn tại tối đa của 1 connection khi nhàn rỗi


    router := gin.Default()


    router.GET("pool", func(ctx *gin.Context) {
        row, err := dbPool.Query(query)
        if err != nil {
            fmt.Println(err.Error())
            ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }


        defer row.Close()


        ctx.JSON(http.StatusOK, gin.H{"message": "ok"})


    })


    if err = router.Run(":8082"); err != nil {
        log.Fatal("Start server fail", err.Error())
    }
}

3.2.2 Tìm hiểu các hàm và chỉ số cần thiết

package main


import (
    "context"
    "database/sql"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "text/template"
    "time"


    "github.com/gin-gonic/gin"
    _ "github.com/go-sql-driver/mysql"
)


func main() {
    connectionPoolPractice()
}


const viewerHTML = `
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8"/>
  <title>Realtime DB Pool Stats</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
  <style>
    body { background: #23272f; color: #e6edf3; font-family: sans-serif; padding: 24px }
    pre { background: #161b22; padding: 12px; border-radius: 8px; }
  </style>
</head>
<body>
  <h2>Realtime DB pool stats</h2>
  <pre id="json"></pre>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script>
const el = document.getElementById("json");
const sse = new EventSource("/info-pool/sse");
sse.onmessage = (e) => {
  let json = JSON.parse(e.data);
  el.textContent = JSON.stringify(json, null, 2);
  hljs.highlightElement(el);
};
</script>
</body>
</html>
`


func connectionPoolPractice() {
    dns := "root:123456@tcp(127.0.0.1:3306)/test"
    db, err := sql.Open("mysql", dns)
    if err != nil {
        panic(err)
    }
    db.SetMaxOpenConns(3)                   // số lượng tối đa được mở cùng lúc. Cái này nên > maxIdleConns để tối ưu
    db.SetMaxIdleConns(2)                   // số lượng tối đa nằm trong pool
    db.SetConnMaxIdleTime(time.Second * 10) // thời gian tồn tại tối đa của 1 connection tính từ lúc nó available(nhàn rỗi)
    db.SetConnMaxLifetime(time.Second * 30)
    router := gin.Default()


    router.GET("/info-pool/sse", func(ctx *gin.Context) {
        ctx.Writer.Header().Set("Content-Type", "text/event-stream")
        ctx.Writer.Header().Set("Cache-Control", "no-cache")
        ctx.Writer.Header().Set("Connection", "keep-alive")
        ctx.Writer.Header().Set("Connection", "keep-alive")
        ctx.Writer.Flush()


        clientGone := ctx.Writer.CloseNotify()
        ticker := time.NewTicker(1 * time.Second)
        defer ticker.Stop()


        for {
            select {
            case <-clientGone:
                return
            case <-ticker.C:
                stats := db.Stats()
                // Serialize stats thành JSON
                jsonStats, err := json.Marshal(stats)
                if err != nil {
                    continue
                }
                fmt.Fprintf(ctx.Writer, "data: %s\n\n", jsonStats)
                ctx.Writer.Flush()
            }
        }
    })


    router.GET("/view", func(ctx *gin.Context) {
        tmpl, err := template.New("viewer").Parse(viewerHTML)
        if err != nil {
            ctx.String(http.StatusInternalServerError, "Template error")
            return
        }
        tmpl.Execute(ctx.Writer, nil)
    })


    router.GET("/pool", func(ctx *gin.Context) {
        conn, err := db.Conn(context.Background())
        if err != nil {
            fmt.Println(err.Error())
        }
        defer conn.Close()
        row, err := conn.QueryContext(context.Background(), "select sleep(10)")
        if err != nil {
            fmt.Println(err.Error())
        }
        defer row.Close()
        ctx.JSON(200, "ok")
    })


    router.Run()
}

4. Cách mình tối ưu Connection Pool

Mọi thứ dưới đây là kinh nghiệm bản thân mình thông qua dự án thực tế và kiến thức học được ở khóa Advanced Backend của Ronin.

Việc tối ưu này theo mình biết hiện tại chưa có một công thức chuẩn cụ thể nào cho chúng ta follow theo. Cho nên chỉ có thể tuning dần để tìm ra các chỉ số phù với hệ thống mà các bạn đang làm.

  • Tham số chỉ ra số lượng connection tối đa được mở đồng thời (MaxOpenConns): Đây là tham số mà mình thấy quan trọng nhất vì nó quyết định tới performance của Database. Nếu quá thấp sẽ gây ra hiện tượng nghẽn cổ chai, quá cao sẽ gây áp lực lên Database. Có nhiều yếu tố ảnh hưởng tới tham số này:
    • Hardware capacity của DB server và Client
    • Độ dài trung bình của các transaction
    • Kiến trúc của DB (standalone, cluster)
    • …
  • Tham số chỉ ra số lượng connection tối đa ở trong pool (MaxIdleConns): Nên thiết lập MaxIdleConns thấp hơn hoặc bằng MaxOpenConns. Sau một vài dự án thực chiến thì mình đúc kết ra 2 cách tính cho riêng mình là MaxIdleConns = MaxOpenConns * 0.5 hoặc MaxIdleConns = MaxOpenConns * 0.75. 
  • Tham số chỉ ra tuổi thọ kết nối (ConnMaxIdleTime, ConnMaxLifetime): Sử dụng ConnMaxIdleTime và ConnMaxLifetime để đảm bảo kết nối không bị quá cũ, tránh các vấn đề như dư thừa connection không dùng tới gây chiếm connection ở ứng dụng khác

Thường xuyên theo dõi các chỉ số như WaitCount, WaitDuration để phát hiện và xử lý sớm các vấn đề về hiệu suất.

Bài này chỉ đang nói về Internal Connection Pool tức là Connection Pool bên trong App (Backend App). Với những hệ thống nhỏ một Backend với một Database chúng ta có thể tuning dần để đưa ra các tham số phù hợp cho hệ thống. Nhưng với các hệ thống bắt đầu scale bằng cách thêm instance hay Database sử dụng Replication Master-Slave thì lúc này chúng ta cần dùng tới các External Connection Pool như ProxySQL mà mình sẽ giới thiệu ở phần sau

5. Kết luận

Connection Pool là một kỹ thuật quan trọng trong việc tối ưu hiệu suất Database. Thông qua việc tái sử dụng kết nối, Connection Pool giúp giảm đáng kể thời gian thiết lập kết nối, tăng tốc độ phản hồi của ứng dụng và giảm tải cho Database.

Hiểu rõ cách hoạt động và các tham số cấu hình của Connection Pool sẽ giúp các Backend Engineer xây dựng hệ thống với hiệu suất tối ưu, đáp ứng được các yêu cầu về tốc độ và khả năng mở rộng trong môi trường thực tế.

Tham khảo thêm:

https://go.dev/doc/database/manage-connections#connection_pool_properties

https://dev.mysql.com/doc/connector-j/en/connector-j-usagenotes-j2ee-concepts-connection-pooling.html

https://www.academia.edu/3272493/Database_Performance_Optimization_by_Connection_Pooling

database
duchieu
beginner

Bài viết liên quan

Materialized view với PostgreSQL

Khái niệm view trong relational database chắc hẳn đã quá quen thuộc với anh em dev. View chỉ lưu trữ câu lệnh truy vấn, không lưu kết quả truy vấn và thực hiện truy vấn trên (các) bảng gốc mỗi khi view được truy cập. Vậy nếu truy vấn của view là một truy vấn phức tạp cho thống kế, cần JOIN nhiều bảng, nhiều điều kiện FILTER, tập dữ liệu ở bảng gốc lớn, có nhiều phép tổng hợp như SUM, AVG… Thì các bạn có đoán được vấn đề gì sẽ xuất hiện không? Nếu câu trả lời của các bạn là “hiệu suất của truy

DFS & BFS - Khi dữ liệu không được lưu trữ một cách tuyến tính

By Bùi Đức Toàn “Muốn tìm trân châu nơi đáy biển, ắt phải lặn sâu. Muốn nhìn toàn cảnh rừng xanh, phải leo lên đỉnh núi.” - Cổ nhân. Từ thuở khai hoang, con người khi thì đào sâu xuống lòng đất để tìm vàng, khi thì lại lần theo dòng sông để biết nó chảy về đâu. Trong thế giới khoa học máy tính, tinh thần khai hoang đó được tái hiện qua hai chiến lược kinh điển trong việc duyệt đồ thị là: 1. Tìm kiếm theo chiều sâu (Depth-First Search – DFS). 2. Tìm kiếm theo chiều rộng (Breadth-First Search

Stack & Queue - Khi array không chỉ để lưu trữ

Stack & Queue là hai cấu trúc dữ liệu cơ bản và phổ biến trong con đường của một lập trình viên, hẳn rằng những lập trình viên có kinh nghiệm đều đã nghe, đã biết đến cấu trúc dữ liệu này, vậy cuối cùng thì, Stack & Queue là gì và nó có tác dụng thế nào? QUEUE - HÀNG ĐỢI Để bắt đầu nhẹ nhàng, chúng ta bắt đầu với Queue trước. Trước tiên thì Queue là một cấu trúc dữ liệu có dạng FIFO (First In Frist Out) tức là vào trước thì ra trước. Một ví dụ dễ thấy trong cuộc sống hàng ngày là việc xếp

HTTP/1.1 vs HTTP/2 : Tại sao HTTP/2 lại nhanh hơn?

By @VietDuc HTTP (Hypertext Transfer Protocol) là giao thức truyền tải tài nguyên cho Web theo mô hình Client-Server (hoặc Request-Response). Ví dụ khi bạn truy cập https://roninhub.com/ . Trình duyệt sẽ tạo các HTTP request đến Server lấy các file HTML, Ảnh, Text… hiển thị lên như ảnh bên dưới. HTTP hoạt động thế nào? Khi một Client (thường là trình duyệt) gửi một HTTP request, quá trình giao tiếp giữa Client và Server thường diễn ra qua các bước sau: * Thiết lập kết nối TCP: Client

Transaction trong Store Procedure: Vấn đề gì xảy ra khi quên ROLLBACK?

By Đức Hiếu Store Procedure: Vũ khí hai lưỡi trong tối ưu hóa performance Trong quá trình phát triển phần mềm, chúng ta thường tin rằng Store Procedure là công cụ mạnh mẽ để tối ưu hiệu suất hệ thống. Tuy nhiên, qua trải nghiệm thực tế, mình nhận ra rằng nếu không sử dụng đúng cách, chúng có thể gây ra những vấn đề nghiêm trọng về hiệu năng. Bài viết này chia sẻ một bài học quý giá về trường hợp Store Procedure có thể trở thành nguyên nhân làm chậm hệ thống nếu không được xử lý lỗi đúng cách.

Tất cả bài viết
logo

HỘ KINH DOANH LẬP VƯƠNG

Giấy chứng nhận đăng ký doanh nghiệp số: 8656162915-001. Cấp ngày 21/02/2024. Nơi cấp: Sở Kế hoạch và Đầu tư TP. Hà Nội

PHƯƠNG THỨC THANH TOÁN

vnpay

LIÊN HỆ

roninengineer88@gmail.com

0362228388

26 ngõ 156 Hồng Mai, Hai Bà Trưng, Hà Nội

THEO DÕI CHÚNG TÔI

Facebook

Youtube

Tiktok

CHÍNH SÁCH

Chính sách bảo mật

Chính sách thanh toán

Đổi trả/Hoàn tiền

Hướng dẫn thanh toán VNPAY

PHƯƠNG THỨC THANH TOÁN

vnpay

Ronin Engineer 2024