Lỗi Node.js mà các công cụ giám sát không thể phát hiện được
Health check của bạn báo mọi thứ đều ổn. Nó chỉ mất một mili giây. Sau đó lưu lượng truy cập tăng lên. Đột nhiên, độ trễ p99 của bạn nhảy vọt lên 400ms. Bạn nhìn vào các dashboard. Mọi thứ đều hiển thị màu xanh.
Mức sử dụng CPU ở mức trung bình. Event loop lag vẫn ổn định. Bộ nhớ vẫn ở mức an toàn. APM của bạn cho thấy một yêu cầu bị chậm nhưng không cho bạn biết lý do tại sao. Không có các lệnh gọi cơ sở dữ liệu chậm. Cũng không có lỗi nào xảy ra.
Thời gian bị tiêu tốn trong libuv thread pool.
Khả năng quan sát (observability) tiêu chuẩn của Node tập trung vào event loop. Thread pool là một hàng đợi riêng biệt. Nó nằm ngoài tầm kiểm soát của bạn.
Node chạy JavaScript trên event loop. Nó đẩy các tác vụ nặng sang libuv thread pool. Các tác vụ này bao gồm:
- Các tác vụ hệ thống tệp (fs.readFile, fs.writeFile).
- Các tác vụ mã hóa (bcrypt, scrypt, pbkdf2).
- Nén dữ liệu (zlib gzip, deflate).
- Tra cứu DNS (dns.lookup).
Mặc định, pool này chỉ có bốn thread. Điều này đúng bất kể máy của bạn có bao nhiêu nhân CPU.
Bốn thread là không đủ. Dưới đây là ba cách mà pool này gây ra lỗi:
Bcrypt khi đăng nhập. Một lần băm bcrypt duy nhất có thể mất 250ms. Nếu bốn người đăng nhập cùng lúc, tất cả các slot đều bị lấp đầy. Người thứ năm phải chờ trong hàng đợi. Họ mất 250ms chỉ để bắt đầu. Độ trễ của bạn tăng gấp đôi.
Các thao tác gzip lớn. Việc nén một phản hồi lớn sẽ chiếm giữ một slot trong pool. Nếu bốn yêu cầu thực hiện việc này cùng lúc, mọi tác vụ khác đều phải chờ đợi. Các lệnh tra cứu DNS và đọc tệp sẽ bị kẹt trong hàng đợi.
Tra cứu DNS. Hầu hết các ứng dụng Node đều sử dụng dns.lookup. Phương thức này sử dụng một system call mang tính chặn (blocking). Nó đưa tác vụ vào pool. Nếu mạng của bạn gặp sự cố nhỏ, các lệnh tra cứu này sẽ làm đình trệ toàn bộ pool.
Một yêu cầu bị kẹt trong hàng đợi của pool là không thể nhìn thấy được. Nó không sử dụng CPU. Nó không chạy JavaScript. Nó chỉ đang nằm chờ.
Cách tìm ra nó:
Nếu độ trễ p99 của bạn tăng lên khi tải nặng nhưng event loop lag vẫn ổn định, hãy kiểm tra pool.
Cách kiểm tra nhanh nhất: Tăng UV_THREADPOOL_SIZE. Thiết lập nó thành 64 trong môi trường của bạn và khởi động lại. Nếu độ trễ giảm xuống, bạn đã tìm thấy vấn đề.
Cách khắc phục đúng cách:
- Sử dụng
worker_threadscho các tác vụ mã hóa nặng như bcrypt. Điều này giúp tách chúng khỏi libuv pool. - Sử dụng
dns.resolvethay vìdns.lookup. Đây là một bộ phân giải bất đồng bộ (async resolver) thực thụ. - Sử dụng streams cho các tác vụ zlib để giải phóng các slot nhanh hơn.
- Tránh các tác vụ hệ thống tệp nặng trên các đường dẫn yêu cầu chính (main request paths).
Đừng chỉ nhìn chằm chằm vào các dashboard màu xanh trong khi người dùng của bạn đang phải chờ đợi.
Source: https://dev.to/r9v/the-nodejs-bug-thats-invisible-to-your-monitoring-oo8
