Приложению, созданному с помощью Commanded 0.17.2 на Elixir 1.7.4, часто не хватает памяти. Расследование показало, что утечка памяти, по всей видимости, вызвана растущим числом агрегированных экземпляров, которые никогда не останавливаются.
Рассматриваемый агрегат получает команды, запускаемые внешней системой. В некоторых случаях функция execute
возвращает событие, а в некоторых других случаях команду следует игнорировать, и поэтому она возвращает nil
(как описано в документы).
def execute(%RemoteThing{}, %ImportRemoteThing{deleted: true}), do: nil
Кажется, что каждый раз, когда возвращается nil
, а не событие, агрегированный экземпляр остается в живых неопределенно долго. Это происходит, даже если прикреплены и тайм-аут, и срок жизни, что явно предполагает что-то еще:
defmodule RemoteThing.Lifespan do
@behaviour Commanded.Aggregates.AggregateLifespan
def after_event(_event), do: :stop
def after_command(_command), do: :stop
end
dispatch(
ImportRemoteThing,
to: RemoteThing,
lifespan: RemoteThing.Lifespan,
timeout: 15_000
)
Я подозреваю, что это ошибка в Командовал:
defp aggregate_lifespan_timeout(_context, []), do: :infinity
Одним из способов избежать утечки памяти может быть создание события, даже если оно никому не требуется. Это привело бы к загрязнению постоянного хранилища событий вместо энергозависимой памяти и, следовательно, могло бы вызвать еще более серьезные проблемы в долгосрочной перспективе.
Сейчас я ищу способ остановить агрегатный экземпляр, если функция execute
возвращает nil
. Мы будем очень признательны за каждую идею обходного пути.
Это уже было исправлено и будет доступно в следующий выпуск Commanded.
Запросы по вытягиванию # 210 увеличивают совокупное поведение продолжительности жизни, чтобы включить обратные вызовы after_error/1
и after_command/1
.
Раньше вам нужно было только определить функцию обратного вызова after_event/1
для реализации поведения Commanded.Aggregates.AggregateLifespan
:
defmodule BankAccountLifespan do
@behaviour Commanded.Aggregates.AggregateLifespan
def after_event(%BankAccountClosed{}), do: :stop
def after_event(_event), do: :infinity
end
Теперь вы также должны определить функции обратного вызова after_command/1
и after_error/1
:
defmodule BankAccountLifespan do
@behaviour Commanded.Aggregates.AggregateLifespan
def after_event(%BankAccountClosed{}), do: :stop
def after_event(_event), do: :infinity
def after_command(%CloseAccount{}), do: :stop
def after_command(_command), do: :infinity
def after_error(:invalid_initial_balance), do: :stop
def after_error(_error), do: :stop
end
Обратный вызов after_command/1
позволит вам остановить агрегат, когда никакие события не производятся.
PR объединен AFAICT, поэтому {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git"}
должен быть достаточно хорош.
В качестве обходного пути вы потенциально можете использовать локальный Registry
(с именем Commanded.Registration.LocalRegistry
), чтобы идентифицировать запущенный совокупный процесс и при необходимости корректно завершить его.
Спасибо, приятно знать. Есть идеи обходного пути, пока исправление не выпущено?