Đánh thức cơ sở dữ liệu mà không gặp kết nối đã chết
Các ứng dụng đã xuất bản trên Mythex, cùng với cơ sở dữ liệu của chúng, sẽ ngủ khi không có ai dùng. Khi một ứng dụng thức dậy, nó có thể giữ yêu cầu đầu tiên 10–12 giây, vì nó cố tái sử dụng các kết nối cơ sở dữ liệu đã chết trong lúc mọi thứ ngủ. Giờ chúng tôi cấu hình cơ sở dữ liệu cho các ứng dụng Spring Boot sao cho an toàn qua giấc ngủ, và yêu cầu agent xây các ứng dụng thuộc mọi stack theo cách tương tự. Trên nền tảng hosting, yêu cầu đầu tiên sau khi thức dậy đã giảm từ 12,7 giây xuống 2,3 giây.
Vì sao ứng dụng và cơ sở dữ liệu ngủ
Một ứng dụng Mythex đã xuất bản chạy trên một máy tạm ngưng sau vài phút rảnh, và cơ sở dữ liệu của nó là Postgres serverless ngủ sau năm phút rảnh và thức dậy ở truy vấn kế tiếp. Đó là điều giữ cho một ứng dụng yên tĩnh có chi phí thấp: bạn chỉ trả cho những gì cơ sở dữ liệu thực sự dùng, và một ứng dụng đang ngủ không tốn tiền tính toán. Chúng tôi đã viết về mặt thanh toán trong tính tiền cơ sở dữ liệu theo những gì nó thực sự dùng.
Cái bẫy là hai bên ngủ riêng rẽ, và chúng không đồng ý với nhau về việc một kết nối là gì.
Triệu chứng: lượt truy cập đầu tiên chậm
Hầu hết các ứng dụng máy chủ giữ một connection pool: một số ít kết nối cơ sở dữ liệu đang mở và sẵn sàng, để mỗi yêu cầu không phải trả chi phí mở kết nối mới. Đó là thiết kế đúng cho một ứng dụng luôn bật.
Nhưng khi có giấc ngủ, điều này xảy ra:
- Ứng dụng đang chạy, với vài kết nối rảnh trong pool.
- Không ai truy cập. Máy của ứng dụng tạm ngưng — đóng băng, với pool còn nguyên.
- Cơ sở dữ liệu ngủ và bỏ đầu kia của mọi kết nối.
- Một người truy cập đến. Ứng dụng thức dậy đúng như trước, vẫn giữ các kết nối mà cơ sở dữ liệu không còn biết đến.
- Pool trao cho yêu cầu một trong những kết nối đó. Nó phải phát hiện ra kết nối đã chết — bằng cách thử và chờ — trước khi từ bỏ nó.
Trong một ứng dụng Spring Boot, việc kiểm tra các kết nối chết đó chặn yêu cầu đầu tiên khoảng 10 giây trước khi nó thất bại với “Connection is not available”. Với người truy cập, trang chỉ đơn giản bị treo, rồi có thể hiện một lỗi.
Vì sao các ứng dụng Spring chịu thiệt nhiều nhất
Spring Boot là một framework Java phổ biến, và nó dùng một connection pool tên là HikariCP theo mặc định. Spring có thể cấu hình pool đó từ các thiết lập, bao gồm số kết nối rảnh cần giữ.
Nhưng Mythex cấp cho mỗi ứng dụng cơ sở dữ liệu dưới dạng một DATABASE_URL duy nhất theo dạng postgresql://user:pass@host/db. Spring không đọc trực tiếp được định dạng này. Vì vậy các ứng dụng Spring xây trên Mythex đã tự dựng pool trong mã bằng cách phân tích URL đó — và một pool dựng thủ công bỏ qua mọi thiết lập spring.datasource.* của Spring. Ngay cả khi ai đó biết các thiết lập pool đúng, cũng không có chỗ nào để chúng có hiệu lực.
Cách sửa, phần 1: thiết lập mà Spring dùng được
Khi một dự án có cơ sở dữ liệu, giờ chúng tôi cũng cấp cho nó các biến môi trường mà Spring đọc tự nhiên:
SPRING_DATASOURCE_URL,SPRING_DATASOURCE_USERNAMEvàSPRING_DATASOURCE_PASSWORD, được chuyển đổi từDATABASE_URL(với SSL bắt buộc trừ khi URL nói khác).- Các thiết lập pool giữ được qua giấc ngủ:
| Thiết lập | Giá trị | Vì sao |
|---|---|---|
| Số kết nối rảnh tối thiểu | 0 | Không giữ kết nối nào mở giữa các yêu cầu, nên không có gì bị cũ |
| Thời gian chờ rảnh | 30 giây | Đóng sớm các kết nối không dùng |
| Thời gian sống tối đa | 2 phút | Thu hồi mọi kết nối trước khi nó kịp cũ |
| Thời gian chờ kết nối | 5 giây | Không treo yêu cầu quá lâu nếu cơ sở dữ liệu phản hồi chậm |
Vì không giữ kết nối rảnh nào, một ứng dụng vừa thức dậy chỉ mở một kết nối mới thay vì lội qua các kết nối đã chết.
Khi các biến này có mặt, Spring tự cấu hình pool, và các thiết lập có hiệu lực. Bất cứ thứ gì dự án đã tự đặt sẽ luôn được ưu tiên; chúng tôi chỉ điền vào những gì còn thiếu. Ứng dụng không dùng Spring sẽ bỏ qua các biến này.
Chi tiết suýt khiến nó không làm gì cả
Spring ánh xạ biến môi trường vào thiết lập của nó theo một quy tắc dễ viết sai. Thiết lập minimum-idle phải được viết là SPRING_DATASOURCE_HIKARI_MINIMUMIDLE — bỏ dấu gạch ngang, các từ nối liền nhau. Viết thành SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE, trông tự nhiên hơn, lại ánh xạ sang một thiết lập khác, không tồn tại (minimum.idle), và bị bỏ qua một cách im lặng.
Đó là kiểu sai lầm vượt qua mọi lần rà soát và không làm gì trong production. Vì vậy một bài kiểm thử kiểm tra cả việc các giá trị đã được đặt lẫn việc không biến nào dùng dạng phân tách bằng gạch dưới.
Cách sửa, phần 2: mọi stack khác
Spring không phải là framework duy nhất có pool, và chúng tôi không thể cấu hình sẵn mọi thư viện trong mọi ngôn ngữ. Vì vậy agent Mythex — AI viết ứng dụng của bạn — giờ có một chỉ dẫn chung cho mọi stack: các ứng dụng đã xuất bản và cơ sở dữ liệu của chúng ngủ khi rảnh, nên một kết nối được giữ mở có thể đã chết ở yêu cầu kế tiếp. Nó nên:
- không giữ kết nối cơ sở dữ liệu rảnh nào mở, hoặc kiểm tra từng kết nối trước khi dùng,
- thử lại một lần khi có lỗi kết nối, và
- dùng các thiết lập cơ sở dữ liệu mà nền tảng cung cấp trong môi trường, thay vì tự dựng pool bằng cách phân tích
DATABASE_URL.
Điểm cuối cùng khép vòng lặp với phần 1: một ứng dụng xây trên các thiết lập được cung cấp sẽ tự nhận cấu hình an toàn qua giấc ngủ.
Kết quả
Đo trên nền tảng hosting mà chúng tôi chạy các ứng dụng đã xuất bản, yêu cầu đầu tiên sau khi thức dậy đã giảm từ 12,7 giây xuống 2,3 giây.
Chúng tôi rút ra gì
- Mọi tầng biết ngủ đều phải đồng ý về những gì còn lại. Một máy ứng dụng đóng băng và một cơ sở dữ liệu ngủ mỗi bên đưa ra một lựa chọn hợp lý. Cùng nhau, ứng dụng thức dậy với trạng thái không còn đúng nữa.
- Mặc định thiết kế cho máy chủ luôn bật không hợp với ứng dụng co về không. Giữ kết nối ấm là một ý hay cho đến khi mọi thứ xung quanh nó nguội đi.
- Đưa dữ liệu cho framework bằng định dạng riêng của nó. Một URL đơn giản với chúng tôi đã buộc mọi ứng dụng Spring phải tự dựng một pool bỏ qua chính cấu hình của nó.
- Kiểm thử rằng một thiết lập được viết đúng tên mà framework đọc. Một giá trị đúng dưới tên sai không làm gì cả, và không báo lỗi.
Nếu bạn mới làm quen với cách cơ sở dữ liệu hoạt động trên Mythex, xem tài liệu cơ sở dữ liệu và bài giải thích của chúng tôi về Postgres là gì.