Vấn đề rò rỉ bộ nhớ của Copilot
(stevenharman.net)Tổng hợp quá trình khắc phục memory leak liên quan đến ActiveSupport::Notifications
-
Tình huống phát sinh memory leak
- Từ một thời điểm nhất định, mức sử dụng bộ nhớ của
webDyno bắt đầu tăng bất thường - Pager bắt đầu báo động, xuất hiện tình huống có vẻ là memory leak
- Từ một thời điểm nhất định, mức sử dụng bộ nhớ của
-
Ứng phó ngay lập tức
- Trên Heroku, khi nghi ngờ có memory leak thì có thể tạm thời xử lý bằng cách khởi động lại Dyno
- Khởi động lại theo chu kỳ deploy thông thường hoặc thủ công khởi động lại các Dyno đang gần chạm giới hạn bộ nhớ
-
Rà soát mã đáng nghi để xác định nguyên nhân
- Xem lại các thay đổi mã được triển khai ngay trước thời điểm memory spike
- Triển khai lại từng đoạn mã bị nghi ngờ để kiểm tra có phát sinh memory leak hay không
- Không thấy đoạn mã nào có vẻ là nguyên nhân nên tiếp tục rollback cả các thay đổi về tooling để kiểm tra. Nhưng memory leak vẫn tiếp diễn
-
Phân tích mẫu tăng bộ nhớ
- Chỉ
webDyno bị leak. Sidekiq, Delayed::Job Dyno vẫn bình thường - Không phải mọi
webDyno đều luôn bị leak. Có lúc chạy bình thường vài giờ rồi một, hai hoặc tất cả Dyno mới bắt đầu leak - Nghi ngờ nguyên nhân đến từ một loại traffic cụ thể hơn là do lưu lượng tổng thể
- Không phải tất cả Puma worker trong Dyno đều bị leak, mà chỉ một số ít worker đang dùng phần lớn tổng bộ nhớ
- Chỉ
-
Thu thập và phân tích heap dump
- Dùng
rbtraceđể thu thập heap dump của Ruby process đang bị leak- Dùng
heroku ps:execđể SSH vào dyno đang bị leak - Dùng lệnh
psđể chọn Ruby worker process đang dùng nhiều bộ nhớ nhất - Attach
rbtracevào pid đó rồi bắt đầu theo dõi phân bổ bộ nhớ (ObjectSpace.trace_object_allocations_start) - Thu thập heap dump bằng
ObjectSpace.dump_all. Nếu dung lượng lớn thì nén bằng gzip - Dùng
heroku ps:copyđể tải file dump về máy local
- Dùng
- Dùng
reapđể trực quan hóa heap dump thành flamegraph- Phát hiện một Thread đang tham chiếu tới 1.9GB bộ nhớ, và bên dưới là một Array tham chiếu tới 32.067 object
- Dùng
sheapđể truy vết các object khả nghi- Xác định Thread đó là worker thread của Puma
- Object
ActiveSupport::SubscriberQueueRegistryđang tham chiếu tớiHash, và bên dưới có các objectStringvàArray - Trong
Arraycó hơn 32.000 objectActiveSupport::Notifications::Eventbị tích tụ
- Dùng
-
Suy luận về nguyên nhân
- Phỏng đoán rằng object
EventcủaActiveSupport::Notificationsđang bị tích sai trong mảng#children - Nếu xảy ra lỗi bên trong block
ActiveSupport::Notifications.instrument, có thểEventđó không bị xóa khỏi#childrenvà gây ra rò rỉ bộ nhớ
- Phỏng đoán rằng object
-
Tái hiện trên local
- Gửi request trên local với request path và parameter đáng nghi đã phát hiện trong production
- Xác nhận xuất hiện
500 Internal Server Errorcùng vớiURI::InvalidURIError - Xác nhận mức sử dụng bộ nhớ của production dyno đã xử lý request đó tăng vọt
-
Phân tích nguyên nhân cụ thể
- Có một bug liên quan đến
Event#childrencủaActiveSupport::Notificationsđã được sửa trong Rails 7.1 - Đồng thời có bug trong gem Bugsnag khiến quá trình làm sạch request url gọi
URI.parsevà phát sinhURI::InvalidURIError, từ đó dẫn đến memory leak - Lỗi được raise bên trong block
ActiveSupport::Notifications.subscribekhông được bắt, khiếnEventtương ứng không bị xóa khỏi mảng#childrenvà tiếp tục tích tụ, gây memory leak
- Có một bug liên quan đến
-
Cách khắc phục
- Ngắn hạn: nâng cấp phiên bản gem Bugsnag để khi phát sinh
URI::InvalidURIErrorcũng không raise lỗi - Dài hạn: nâng cấp lên Rails 7.x nơi bug của
ActiveSupport::Notificationsđã được sửa
- Ngắn hạn: nâng cấp phiên bản gem Bugsnag để khi phát sinh
Ý kiến của GN⁺
- Quá trình phát hiện vấn đề rồi lần lượt xác định nguyên nhân một cách có hệ thống rất ấn tượng. Bài viết cũng tổng hợp khá tốt quy trình phân tích cơ bản khi nghi ngờ có memory leak
- Có vẻ các công cụ mã nguồn mở để thu thập, trực quan hóa và phân tích heap dump của Ruby như
rbtrace,reap,sheapđang được phát triển rất tích cực. Không chỉ riêng Ruby, điều quan trọng là cần nắm được các công cụ phân tích bộ nhớ hữu ích theo từng ngôn ngữ và biết cách áp dụng vào vấn đề thực tế - Thực tế, nguyên nhân memory leak khá thường đến từ bug trong thư viện hoặc framework đang dùng, nhưng không phải lúc nào cũng có điều kiện tự phân tích, sửa và deploy bản vá, nên điều quan trọng là áp dụng cách né tránh hoặc giảm thiểu càng sớm càng tốt. Việc cung cấp bug report kèm theo phương án thay thế khả thi cũng là một cách hay
- Điểm hay là bài viết không chỉ dừng ở việc xử lý memory leak mà còn đào sâu đến root cause của vấn đề. Tinh thần phân tích cẩn thận mã nguồn bên trong framework để lần ra nguyên nhân gốc là điều các developer rất cần
- Cuối cùng, nguyên nhân của memory leak hóa ra lại nằm ở một lần nâng cấp phiên bản thư viện tưởng như chẳng liên quan. Đây là một ví dụ cho thấy tầm quan trọng của quản lý dependency và theo dõi thay đổi. Dù là thay đổi nhỏ cũng cần phân tích kỹ tác động và tiếp tục giám sát sau khi triển khai
1 bình luận
Ý kiến trên Hacker News
Có thể giải quyết bằng rèn luyện kỹ năng kỹ sư mà không cần sợ quản lý bộ nhớ thủ công
Trường hợp thiệt hại 5 triệu USD do rò rỉ bộ nhớ
Công cụ gỡ lỗi rò rỉ bộ nhớ và hướng khắc phục
Lời khen về phong cách viết