Как 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.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.