Я новичок в Elixir и Phoenix Framework, так что может быть, мой вопрос немного тупой.
У меня есть приложение с Elixir + Phoenix Framework в качестве бэкэнд и Angular 2 в качестве интерфейса. Я использую Phoenix Channels в качестве канала для интерфейсного/межсетевого обмена. И я обнаружил странную ситуацию: если я отправлю большой блок данных из бэкэнда в интерфейс, то потребление канала в канале будет доходить до сотен МБ. И каждое соединение (каждый канал) потребляет такой объем памяти даже после завершения передачи.
Вот фрагмент кода из описания базового канала:
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 Мб. Поэтому я открываю несколько новых окон браузера, и каждое потребление памяти нового канала увеличивается до этой суммы после запроса "плейлисты: список".
Это нормальное поведение? Я ожидал бы большого потребления памяти во время запроса базы данных и разгрузки данных, но он все тот же, даже после завершения запроса.
ОБНОВЛЕНИЕ 1. Поэтому с большой помощью @Dogbert и @michalmuskala я обнаружил, что после ручной сбор памяти мусора собирается освободиться.
Я попытался немного выкопать библиотеку recon_ex и нашел следующие примеры:
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>
- это Elixir.Phoenix.Channel.Server и #PID<0.4405.6>
- cowboy_protocol.
Далее я пошел с:
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}}]}]
и
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}}]}]
И, наконец, состояние проблемы обрабатывается после recon.bin_leak (фактически после сбора мусора, конечно - если я запускаю: erlang.garbage_collection() с pids этих процессов, результат будет таким же):
{#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 часов) стала бесплатной.
Просто помню: у меня такое потребление памяти после отправки сообщения от бэкэнда к интерфейсу с 70 000 записей, полученных из Postgres. Модель довольно проста:
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)
Я бы очень признателен за любые советы здесь. Я считаю, что не собираюсь отправлять такой большой объем данных, но даже меньшие наборы данных могут привести к огромному потреблению памяти в случае многих клиентских подключений. И поскольку я не кодировал никаких сложных вещей, вероятно, эта ситуация скрывает некоторые более общие проблемы (но это просто предположение, конечно).