Tôi có một ứng dụng elixir/OTP bị treo trong quá trình sản xuất do sự cố hết bộ nhớ. Các chức năng gây ra vụ tai nạn được gọi là mỗi 6 giờ, trong một quá trình chuyên dụng. Phải mất vài phút (~ 30) để chạy và trông như thế này:Giải quyết các tệp nhị phân lớn bị rò rỉ
def entry_point do
get_jobs_to_scrape()
|> Task.async_stream(&scrape/1)
|> Stream.map(&persist/1)
|> Stream.run()
end
Trên máy tính địa phương của tôi, tôi thấy một sự tăng trưởng liên tục trong tiêu thụ bộ nhớ mã nhị phân lớn khi các chức năng chạy:
Lưu ý rằng khi tôi kích hoạt bộ sưu tập rác theo cách thủ công trong quá trình chạy chức năng, mức tiêu thụ bộ nhớ giảm đáng kể, vì vậy chắc chắn không phải là vấn đề với một số quy trình khác nhau không thể GC, nhưng chỉ có một quy trình không đúng GC. Ngoài ra, điều quan trọng là phải nói rằng cứ sau vài phút, quy trình thực hiện quản lý GC, nhưng đôi khi không đủ. Máy chủ sản xuất chỉ có RAM 1 GB và nó bị lỗi trước khi GC khởi động.
Cố gắng giải quyết vấn đề tôi gặp phải Erlang in Anger (xem trang 66-67). Một gợi ý là đặt tất cả các thao tác nhị phân lớn trong các quy trình một lần. Giá trị trả về của hàm scrape
là một bản đồ chứa các tệp nhị phân lớn. Do đó, chúng được chia sẻ giữa Task.async_stream
"công nhân" và quá trình chạy chức năng. Vì vậy, về lý thuyết, tôi có thể đặt persist
cùng với scrape
bên trong Task.async_stream
. Tôi không muốn làm như vậy, và giữ cho các cuộc gọi đến persist
được đồng bộ hóa thông qua quy trình.
Một đề xuất khác là gọi :erlang.garbage_collect
theo định kỳ. Dường như nó giải quyết vấn đề nhưng cảm thấy quá khó khăn. Tác giả cũng không khuyến cáo điều đó. Đây là giải pháp của tôi hiện tại:
def entry_point do
my_pid = self()
Task.async(fn -> periodically_gc(my_pid) end)
# The rest of the function as before...
end
defp periodically_gc(pid) do
Process.sleep(30_000)
if Process.alive?(pid) do
:erlang.garbage_collect(pid)
periodically_gc(pid)
end
end
Và tải bộ nhớ kết quả:
Tôi hoàn toàn không hiểu làm thế nào những gợi ý khác trong cuốn sách phù hợp với vấn đề.
Bạn sẽ đề xuất điều gì trong trường hợp đó? Giữ các giải pháp hacky hoặc có những lựa chọn tốt hơn.
Bạn có cân nhắc việc giữ các tệp nhị phân trong ETS không? Nếu bạn có thể giải phóng chúng một cách đáng tin cậy, bạn có thể chuyển sang phân bổ thủ công một cách hiệu quả và phá vỡ bất kỳ sự nastiness nào trong GC của BEAM. OTOH, nếu đây là một bông tuyết thích hợp, có thể giải pháp GC-cuộc gọi thủ công là đủ tốt? – cdegroot
Ý tưởng thú vị! Hãy xem nếu tôi hiểu: Có chức năng scraper đặt dữ liệu trong ETS và sau đó ánh xạ các 'persist' chức năng trên dữ liệu trong bảng, là chính xác? Tuy nhiên, công nhân 'Task.async_stream' sẽ có tham chiếu đến các tệp nhị phân lớn và quy trình chính sẽ có cùng tham chiếu để tìm nạp từ ETS bên trong hàm' persist' và vấn đề sẽ vẫn còn. – Nagasaki45
Vâng, "Mỗi mục có thể đếm được được chuyển làm đối số cho hàm và được xử lý bởi nhiệm vụ của chính nó" theo tài liệu 'Task.async_stream', vì vậy nếu mỗi lệnh' scrape' của bạn chỉ xây dựng nhị phân, hãy chặn nó trong ETS và trả lại một tham chiếu, sau đó bạn có thể đang kinh doanh. – cdegroot