2 giờ sáng và cơn ác mộng mang tên “Connection Timeout”
Điện thoại rung liên hồi. Bạn bật dậy, mắt nhắm mắt mở check log và thấy hàng nghìn dòng lỗi: HikariPool-1 - Connection is not available. Hệ thống production chính thức tê liệt. Đây là kịch bản kinh hoàng mà mình từng nếm trải khi database chạm mốc 50GB và lượng user truy cập tăng vọt 300% chỉ sau một đêm.
Lỗi không nằm ở MySQL. Thủ phạm thực sự thường là cách chúng ta quản lý kết nối và transaction trong code Java. Dù Spring Boot đã cấu hình sẵn khá tốt, nhưng để gánh tải hàng triệu bản ghi, bạn buộc phải can thiệp sâu vào HikariCP. Bài viết này sẽ chia sẻ những tinh chỉnh thực tế giúp mình giải quyết triệt để vấn đề này.
Tại sao Connection Pool lại quan trọng đến thế?
Mỗi khi ứng dụng cần truy vấn, nó phải thực hiện bắt tay TCP và xác thực với MySQL. Quá trình này rất ngốn tài nguyên. Nếu cứ mỗi request lại tạo kết nối mới rồi đóng lại, CPU của database server sẽ sớm chạm ngưỡng 100%.
HikariCP đóng vai trò như một đội xe taxi chờ sẵn (idle). Thay vì gọi xe mới, app chỉ việc lấy một xe đang rảnh để dùng. Tuy nhiên, nếu bạn để đội xe quá ít, khách sẽ phải đợi lâu. Ngược lại, nếu quá nhiều xe, bãi đỗ (RAM) sẽ bị quá tải. Việc tìm ra con số “vừa đủ” chính là chìa khóa tối ưu.
Cài đặt môi trường chuẩn
Trước tiên, hãy kiểm tra file pom.xml. Mình luôn khuyến nghị dùng bản MySQL Connector mới nhất để tận dụng các bản vá hiệu năng và bảo mật.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
Cấu hình HikariCP: Đừng tin vào mặc định
Đừng bao giờ bê nguyên cấu hình mặc định lên production. Dưới đây là các thông số mình thường dùng cho các hệ thống có lượng truy cập trung bình cao trong file application.yml:
spring:
datasource:
url: jdbc:mysql://localhost:3306/my_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
hikari:
# Giới hạn số kết nối đồng thời. Với DB 2 core, con số 20 là khởi đầu tốt.
maximum-pool-size: 20
# Luôn giữ ít nhất 5 kết nối chờ sẵn.
minimum-idle: 5
# Quá 30 giây không lấy được connection sẽ báo lỗi thay vì treo app.
connection-timeout: 30000
# Thời gian sống của 1 connection.
max-lifetime: 1800000
pool-name: MyHikariPool
jpa:
hibernate:
ddl-auto: validate
show-sql: false # Tắt log SQL trên production để tiết kiệm IOPS
Lưu ý đắt giá: Có lần mình để max-lifetime là 30 phút, nhưng wait_timeout của MySQL server chỉ có 10 phút. App vẫn cố dùng kết nối đã bị MySQL ngắt, dẫn đến lỗi Broken pipe hàng loạt. Hãy luôn đảm bảo max-lifetime ngắn hơn timeout của MySQL ít nhất 2-3 phút.
Quản lý Transaction: Nhỏ gọn là sức mạnh
Lạm dụng @Transactional là con đường ngắn nhất dẫn đến tình trạng treo database (Locking). Quy tắc vàng là: Chỉ bao bọc những gì thực sự cần thiết.
Tận dụng Read-Only để tăng tốc SELECT
Với các tác vụ chỉ đọc, hãy dùng @Transactional(readOnly = true). Hibernate sẽ bỏ qua bước kiểm tra thay đổi (dirty checking), giúp giảm tải bộ nhớ và tăng tốc độ phản hồi khoảng 10-15%.
@Transactional(readOnly = true)
public List<Product> getAllProducts() {
return productRepository.findAll();
}
Tuyệt đối không gọi API bên ngoài trong Transaction
Đây là lỗi kinh điển. Nếu bạn gọi một API thanh toán mất 5 giây bên trong @Transactional, connection đó sẽ bị khóa cứng suốt 5 giây. Chỉ cần 20 khách thanh toán cùng lúc, toàn bộ pool sẽ cạn kiệt. Hãy thực hiện gọi API trước, sau đó mới mở transaction để lưu kết quả vào DB.
Giám sát hệ thống thực tế
Để biết pool đang “thở” ra sao, bạn nên bật log debug cho HikariCP. Ngoài ra, hãy thường xuyên kiểm tra trạng thái kết nối trực tiếp trên MySQL bằng lệnh:
SHOW PROCESSLIST;
Nếu thấy danh sách dài dằng dặc các kết nối ở trạng thái “Sleep”, đó là dấu hiệu bạn đang giữ transaction quá lâu hoặc quên đóng chúng trong code.
Sau khi áp dụng bộ khung này, hệ thống 50GB của mình đã chạy cực kỳ ổn định. CPU database duy trì ở mức 20% ngay cả trong giờ cao điểm. Việc hiểu rõ từng thông số cấu hình không chỉ giúp app chạy nhanh hơn, mà quan trọng nhất là giúp bạn có những giấc ngủ ngon không bị gián đoạn.

