Tôi khá mới với Elixir và Phoenix Framework, vì vậy có thể là câu hỏi của tôi là một chút câm.Elixir + Phoenix Kênh tiêu thụ bộ nhớ
Tôi có một ứng dụng với Elixir + Phoenix Framework làm phụ trợ và Angular 2 làm giao diện người dùng. Tôi đang sử dụng Kênh Phoenix làm kênh cho giao diện đầu cuối/đầu cuối. Và tôi đã tìm thấy một tình huống lạ lùng: nếu tôi gửi một khối dữ liệu lớn từ backend đến frontend thì tiêu thụ bộ nhớ kênh cụ thể sẽ tăng lên tới hàng trăm MB. Và mỗi kết nối (mỗi quá trình kênh) ăn một lượng bộ nhớ như vậy, ngay cả sau khi kết thúc truyền.
Dưới đây là một đoạn mã từ mô tả kênh phụ trợ:
defmodule MyApp.PlaylistsUserChannel do
use MyApp.Web, :channel
import Ecto.Query
alias MyApp.Repo
alias MyApp.Playlist
# skipped ... #
# Content list request handler
def handle_in("playlists:list", _payload, socket) do
opid = socket.assigns.opid + 1
socket = assign(socket, :opid, opid)
send(self, :list)
{:reply, :ok, socket}
end
# skipped ... #
def handle_info(:list, socket) do
payload = %{opid: socket.assigns.opid}
result =
try do
user = socket.assigns.current_user
playlists = user
|> Playlist.get_by_user
|> order_by([desc: :updated_at])
|> Repo.all
%{data: playlists}
catch
_ ->
%{error: "No playlists"}
end
payload = payload |> Map.merge(result)
push socket, "playlists:list", payload
{:noreply, socket}
end
tôi đã tạo ra một bộ với 60000 hồ sơ chỉ để kiểm tra khả năng frontend để đối phó với số tiền đó dữ liệu, nhưng có một tác dụng phụ - Tôi thấy rằng tiêu thụ bộ nhớ kênh cụ thể là 167 Mb. Vì vậy, tôi mở một vài cửa sổ trình duyệt mới và mỗi tiêu thụ bộ nhớ kênh mới tăng lên đến số tiền này sau yêu cầu "danh sách phát: danh sách phát".
Hành vi bình thường có phải không? Tôi sẽ mong đợi tiêu thụ bộ nhớ cao trong quá trình truy vấn cơ sở dữ liệu và tải dữ liệu, nhưng nó vẫn giống nhau ngay cả sau khi yêu cầu hoàn tất.
CẬP NHẬT 1. Vì vậy, với sự trợ giúp lớn của @Dogbert và @michalmuskala tôi thấy rằng sau khi bộ nhớ thu gom rác thủ công sẽ miễn phí.
Tôi đã cố gắng để đào một chút với thư viện recon_ex và tìm thấy các ví dụ sau:
iex([email protected])19> :recon.proc_count(:memory, 3)
[{#PID<0.4410.6>, 212908688,
[current_function: {:gen_server, :loop, 6},
initial_call: {:proc_lib, :init_p, 5}]},
{#PID<0.4405.6>, 123211576,
[current_function: {:cowboy_websocket, :handler_loop, 4},
initial_call: {:cowboy_protocol, :init, 4}]},
{#PID<0.12.0>, 689512,
[:code_server, {:current_function, {:code_server, :loop, 1}},
{:initial_call, {:erlang, :apply, 2}}]}]
#PID<0.4410.6>
là Elixir.Phoenix.Channel.Server và #PID<0.4405.6>
là cowboy_protocol.
Tiếp theo, tôi đã đi với:
iex([email protected])20> :recon.proc_count(:binary_memory, 3)
[{#PID<0.4410.6>, 31539642,
[current_function: {:gen_server, :loop, 6},
initial_call: {:proc_lib, :init_p, 5}]},
{#PID<0.4405.6>, 19178914,
[current_function: {:cowboy_websocket, :handler_loop, 4},
initial_call: {:cowboy_protocol, :init, 4}]},
{#PID<0.75.0>, 24180,
[Mix.ProjectStack, {:current_function, {:gen_server, :loop, 6}},
{:initial_call, {:proc_lib, :init_p, 5}}]}]
và:
iex([email protected])22> :recon.bin_leak(3)
[{#PID<0.4410.6>, -368766,
[current_function: {:gen_server, :loop, 6},
initial_call: {:proc_lib, :init_p, 5}]},
{#PID<0.4405.6>, -210112,
[current_function: {:cowboy_websocket, :handler_loop, 4},
initial_call: {:cowboy_protocol, :init, 4}]},
{#PID<0.775.0>, -133,
[MyApp.Endpoint.CodeReloader,
{:current_function, {:gen_server, :loop, 6}},
{:initial_call, {:proc_lib, :init_p, 5}}]}]
Và cuối cùng tình trạng của vấn đề xử lý sau recon.bin_leak (trên thực tế sau khi thu gom rác thải, tất nhiên - nếu tôi chạy: erlang.garbage_collection() với pids của các quá trình này kết quả là như nhau):
{#PID<0.4405.6>, 34608,
[current_function: {:cowboy_websocket, :handler_loop, 4},
initial_call: {:cowboy_protocol, :init, 4}]},
...
{#PID<0.4410.6>, 5936,
[current_function: {:gen_server, :loop, 6},
initial_call: {:proc_lib, :init_p, 5}]},
Nếu tôi không chạy bộ sưu tập rác thủ công - bộ nhớ "không bao giờ" (ít nhất, tôi đã đợi 16 giờ) trở nên miễn phí.
Chỉ cần nhớ: Tôi có mức tiêu thụ bộ nhớ như vậy sau khi gửi tin nhắn từ phần phụ trợ đến giao diện người dùng với 70 000 bản ghi được tải xuống từ Postgres. Mô hình này là khá đơn giản:
schema "playlists" do
field :title, :string
field :description, :string
belongs_to :user, MyApp.User
timestamps()
end
ghi là autogenerated và trông như thế này:
description: null
id: "da9a8cae-57f6-11e6-a1ff-bf911db31539"
inserted_at: Mon Aug 01 2016 19:47:22 GMT+0500 (YEKT)
title: "Playlist at 2016-08-01 14:47:22"
updated_at: Mon Aug 01 2016 19:47:22 GMT+0500 (YEKT)
Tôi thực sự sẽ đánh giá cao bất kỳ lời khuyên ở đây. Tôi tin rằng tôi sẽ không gửi một số lượng lớn dữ liệu nhưng ngay cả các tập dữ liệu nhỏ hơn cũng có thể dẫn đến mức tiêu thụ bộ nhớ lớn trong trường hợp có nhiều kết nối máy khách. Và kể từ khi tôi đã không mã hóa bất kỳ điều khó khăn có lẽ tình hình này ẩn một số vấn đề chung chung hơn (nhưng nó chỉ là một giả định, tất nhiên).
Chắc chắn không bình thường. Việc sử dụng bộ nhớ giảm xuống sau khi bạn ngắt kết nối và quá trình chết? Ngoài ra, hãy thử ': observer.start' để xem quy trình nào đang sử dụng bộ nhớ và cho cái gì. – Dogbert
Dữ liệu thô bạn đang gửi trong json mất bao nhiêu bộ nhớ? 60_000 bản ghi? Bộ nhớ quá trình sẽ cần phải phát triển đến ít nhất là kích thước đó (thậm chí có thể nhiều hơn vì rác trên đường đi) để xử lý nó. – michalmuskala
@ Dogbert Tôi tìm thấy mức tiêu thụ bộ nhớ này với: người quan sát. Số tiền này được sử dụng bởi Elixir.Phoenix.Channel.Server: init/1 process, đó là lý do tại sao tôi hỏi về mức tiêu thụ bộ nhớ kênh Phoenix. Và có, sau khi ngắt kết nối quá trình chết và bộ nhớ miễn phí. Nhân tiện, tôi thấy rằng cowboy_protocol: init/4 cũng ăn 100Mb cho mỗi kết nối. – heathen