Phoenix (Elixir)MiddleTechnical

Как LiveView обрабатывает reconnects, latency, temporary assigns и memory usage?

LiveView reconnect вызывает повторный mount без восстановления state; для снижения memory используйте temporary_assigns и Streams; latency митигируется через phx-debounce и phx-disable-with.

LiveView: reconnects, latency, temporary assigns и memory

Reconnects

LiveView поддерживает автоматическое переподключение на стороне клиента. При разрыве соединения phoenix_live_view.js пытается переподключиться с exponential backoff. На сервере новый процесс LiveView монтируется заново: вызывается mount/3, затем handle_params/3. State предыдущей сессии не восстанавливается автоматически.

  • Используйте connected?(socket) в mount/3 для разделения статического рендера и подписок.
  • Для восстановления state после reconnect — персистируйте его в БД или передавайте через URL params.
  • LiveView отправляет клиенту phx-session токен; при reconnect клиент его повторно передаёт, что позволяет серверу верифицировать сессию без повторной аутентификации.
def mount(%{"page" => page}, _session, socket) do
  if connected?(socket), do: Phoenix.PubSub.subscribe(MyApp.PubSub, "updates")
  {:ok, assign(socket, page: page, items: load_items(page))}
end

Latency

LiveView работает по принципу: каждое событие — roundtrip до сервера и обратно. При высокой latency (> 100 мс) пользователь замечает задержку. Стратегии митигации:

  • phx-disable-with — блокирует кнопку и показывает placeholder во время ожидания ответа.
  • Optimistic UI через JS hooks: обновите DOM немедленно, отмените при ошибке сервера.
  • Debounce для форм: phx-debounce="300" снижает количество roundtrip при вводе.
  • Разместите ноды ближе к пользователям (multi-region deploy на Fly.io).
<button phx-click="submit" phx-disable-with="Saving...">
  Save
</button>
<input phx-change="search" phx-debounce="300" />

Temporary Assigns

По умолчанию все assigns хранятся в памяти процесса LiveView на протяжении всей сессии. Для больших списков (тысячи элементов, потоки событий) это нерационально. temporary_assigns указывает LiveView сбросить значение assign до значения по умолчанию после каждого рендера:

def mount(_params, _session, socket) do
  {:ok,
   assign(socket, messages: []),
   temporary_assigns: [messages: []]}
end

def handle_info({:new_message, msg}, socket) do
  # После рендера :messages снова станет []
  {:noreply, assign(socket, messages: [msg])}
end

На клиенте LiveView.js поддерживает phx-update="append" и phx-update="prepend" для накопления элементов в DOM без хранения всего списка в памяти сервера.

Memory Usage

Каждый LiveView — отдельный Elixir-процесс со своей кучей. При тысячах одновременных пользователей это существенная нагрузка. Основные техники снижения потребления памяти:

  • temporary_assigns — для списков, которые пополняются инкрементально.
  • LiveComponent — переносит логику в дочерний компонент с изолированным state; не создаёт отдельный процесс, но изолирует state внутри одного LiveView.
  • Streams (Phoenix 0.18+)stream/3 и stream_insert/3 для управления коллекциями без хранения данных в assigns.
def mount(_params, _session, socket) do
  {:ok, stream(socket, :items, MyApp.list_items())}
end

def handle_info({:new_item, item}, socket) do
  {:noreply, stream_insert(socket, :items, item, at: 0)}
end

Подводные камни

  • Подписка вне connected?. Если подписаться на PubSub в mount без проверки connected?, статический рендер (HTTP) тоже создаст подписку-призрак.
  • temporary_assigns и phx-update. Без phx-update="append" в шаблоне временные assigns корректно работают только при добавлении элементов; без директивы DOM будет перерисовываться полностью.
  • Большие бинарные данные в assigns. base64-encoded изображения или большие JSON-блобы в state приводят к высокому GC pressure. Используйте URL-ссылки вместо инлайн-данных.
  • Отсутствие idle timeout. LiveView не завершает процесс при неактивном tab'е. Добавьте таймер через Process.send_after для очистки неактивных сессий.
  • handle_info с тяжёлой логикой. Если handle_info блокирует процесс — все события от клиента становятся в очередь. Делегируйте тяжёлые операции в Task.

Common mistakes

  • Отвечать определением без production-сценария.
  • Не называть runtime boundary, security boundary или failure mode.
  • Игнорировать версию API, observability и тестовую проверку.

What the interviewer is testing

  • Объясняет механизм своими словами и без выдуманных API.
  • Называет реальные риски, диагностику и критерий корректности.
  • Связывает ответ с текущей документацией и миграционными ограничениями.

Sources

Related topics