我对 Elixir 和 Phoenix 框架还很陌生,所以我的问题可能有点愚蠢。
我有一个应用程序,以 Elixir + Phoenix 框架作为后端,Angular 2 作为前端。我使用 Phoenix Channels 作为前端/后端交换的渠道。我发现了一个奇怪的情况:如果我从后端向前端发送大量数据,那么特定通道进程的内存消耗会高达数百 MB。并且每个连接(每个通道进程)都会占用如此多的内存,即使在传输结束之后也是如此。
这是后端频道描述的代码片段:
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
我创建了一个包含 60000 条记录的集合,只是为了测试前端处理如此大量数据的能力,但有一个副作用——我发现特定的通道进程内存消耗为 167 Mb。所以我打开了几个新的浏览器窗口,每个新频道进程的内存消耗在“播放列表:列表”请求之后增长到这个数量。
这是正常行为吗?我预计在数据库查询和数据卸载期间会消耗大量内存,但即使在请求完成后它仍然相同。
更新 1。因此,在@Dogbert 和@michalmuskala 的大力帮助下,我发现手动垃圾收集后内存将被释放。
我尝试使用 recon_ex 库进行一些挖掘,并找到了以下示例:
iex(n1@192.168.10.111)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>
是 Elixir.Phoenix.Channel.Server 并且#PID<0.4405.6>
是 cowboy_protocol。
接下来我去了:
iex(n1@192.168.10.111)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}}]}]
和:
iex(n1@192.168.10.111)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}}]}]
最后是 recon.bin_leak 之后问题进程的状态(当然,实际上是在垃圾收集之后 - 如果我使用这些进程的 pid 运行 :erlang.garbage_collection() ,结果是相同的):
{#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}]},
如果我不手动运行垃圾收集 - 内存“从不”(至少,我已经等了 16 个小时)变得空闲。
请记住:在从后端向前端发送一条消息并从 Postgres 获取 70 000 条记录后,我有这样的内存消耗。该模型非常简单:
schema "playlists" do
field :title, :string
field :description, :string
belongs_to :user, MyApp.User
timestamps()
end
记录是自动生成的,如下所示:
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)
我真的很感激这里的任何建议。我相信我不会发送如此大量的数据,但即使是更小的数据集也可能在客户端连接很多的情况下导致巨大的内存消耗。而且由于我没有编写任何棘手的东西,因此这种情况可能隐藏了一些更普遍的问题(但这只是一个假设,当然)。