сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · FastAPI, Django и API

WebSockets и стриминг

Реальное время: WebSocket, SSE, стриминг

Как только продукту нужен живой апдейт - чат, нотификации, дашборд - HTTP-модель «запрос-ответ» перестаёт хватать, и на собесе спрашивают, чем ты это закроешь. Ценят не знание слова WebSocket, а умение выбрать между ним, SSE и стримингом и понимание, что всё это ломается на масштабе.

Типовые формулировки: «WebSocket или SSE?», «как отдать большой файл, не съев память?», «как масштабировать вебсокеты на несколько воркеров».

WebSocket, SSE, StreamingResponse

WebSocket - постоянный двунаправленный канал: обе стороны шлют сообщения в любой момент, в отличие от «запрос-ответ». Это для настоящей двусторонности: чаты, живые дашборды. Но он требует ASGI (asynchronous server gateway interface) - WSGI (web server gateway interface) постоянный канал не держит.

SSE (server-sent events) - односторонний поток сервер→клиент поверх обычного HTTP (text/event-stream): проще WebSocket, есть авто-reconnect, спокойно проходит прокси. Если данные текут только в одну сторону (лента событий, прогресс) - бери SSE, не усложняй вебсокетом.

StreamingResponse отдаёт тело кусками из генератора, не держа весь ответ в памяти - так отдают большие файлы и данные на лету, не раздувая RAM.

// Правило выбора: двусторонность → WebSocket; поток в одну сторону → SSE; большой ответ телом → StreamingResponse.

WebSocket
постоянный двусторонний канал поверх одного соединения
SSE
односторонний поток событий сервер→клиент по HTTP

Масштаб: где всё ломается

Соединения живут в памяти конкретного воркера. Значит два клиента на разных воркерах друг друга не видят - рассылку между воркерами делают через общий backend, обычно Redis pub/sub: воркер публикует сообщение, все подписанные воркеры доставляют его своим соединениям.

Два продовых врага. Медленный клиент, который читает медленнее, чем сервер шлёт - буфер отправки растёт до OOM (out of memory), если нет backpressure (сдерживания). И мёртвые соединения, которые сервер не заметил - их ловят ping/pong-хартбитом, иначе копятся зомби.

// И база: не пересоздавай соединение или клиента на каждое сообщение - весь смысл в переиспользовании одного канала.

StreamingResponse
отдача тела частями без буферизации целиком
backpressure
сдерживание, когда клиент читает медленнее, чем сервер шлёт

Как отвечать: «WebSocket или SSE - что выбрать?»

Смотрю на направление данных. Если поток нужен только от сервера к клиенту - лента событий, прогресс задачи, живые метрики - беру SSE: он проще, работает поверх обычного HTTP, сам переподключается и спокойно проходит прокси. WebSocket достаю, когда нужна настоящая двусторонность в реальном времени: чат, коллаборативный редактор, где клиент тоже постоянно шлёт. За удобство WebSocket платит тем, что требует ASGI и сложнее масштабируется. Поэтому не усложняю: одностороннему кейсу - SSE, двустороннему - WebSocket.

Дан чёткий критерий выбора (направление данных), названы сильные стороны SSE (прокси, reconnect) и честная цена WebSocket это выбор по требованиям, а не по хайпу.

На чём валят

  • Ждать WebSocket на WSGI-сервере: постоянный двусторонний канал требует ASGI.
  • Пересоздавать соединение/клиент на каждое сообщение вместо переиспользования одного канала.
  • Игнорировать медленных клиентов: буфер отправки растёт до OOM без backpressure.
  • Держать вебсокеты на нескольких воркерах без общего backend (Redis pub/sub) - клиенты не видят друг друга.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 12, остальные разбираются в тренажёре.

  1. #websockets_streaming1 / 5
    Чем websocket-соединение отличается от обычного HTTP-запроса?
    A)Websocket работает исключительно в одну сторону: только сервер шлёт клиенту
    B)Это постоянный двунаправленный канал — сервер и клиент шлют данные в любой момент
    C)Websocket — тот же HTTP-запрос, только с более длинным допустимым таймаутом ответа
    D)Разница лишь в формате: websocket шлёт данные бинарными кадрами
    показать ответ и разбор
    +B)Это постоянный двунаправленный канал — сервер и клиент шлют данные в любой момент

    // разбор: HTTP — модель «запрос-ответ»: клиент спрашивает, сервер отвечает, соединение по смыслу завершено. Websocket после апгрейда держит постоянное TCP-соединение, по которому обе стороны шлют сообщения в произвольные моменты. Это нужно для реального времени: чаты, нотификации, живые дашборды, игры.

  2. #websockets_streaming2 / 5
    Нужны серверные обновления в реальном времени, но только в одну сторону (лента событий). Что выбрать?
    A)Обычный GET-запрос, который сервер будет держать открытым бесконечно долго
    B)Обязательно полноценный websocket, ведь другого способа слать данные с сервера нет
    C)SSE (Server-Sent Events): однонаправленный поток сервер→клиент поверх HTTP
    D)Частый опрос эндпоинта клиентом через setInterval — это и есть правильный realtime
    показать ответ и разбор
    +C)SSE (Server-Sent Events): однонаправленный поток сервер→клиент поверх HTTP

    // разбор: Если данные текут только с сервера (нотификации, прогресс, котировки), websocket избыточен. SSE — стандарт поверх обычного HTTP (Content-Type text/event-stream): сервер держит соединение и шлёт события, клиент слушает через EventSource. Проще websocket, легко проходит прокси, авто-reconnect из коробки. Для двусторонности всё же нужен websocket.

  3. #websockets_streaming3 / 5
    Эндпоинт формирует гигантский CSV. Почему лучше StreamingResponse, а не собрать строку и вернуть?
    A)Обычный ответ вообще не способен вернуть данные объёмом больше пары мегабайт
    B)Разницы по памяти нет: обычный ответ и стриминг расходуют её одинаково
    C)Стриминг отдаёт данные кусками, не держа весь ответ в памяти сразу
    D)StreamingResponse автоматически сжимает ответ, а обычный ответ отдаётся без сжатия
    показать ответ и разбор
    +C)Стриминг отдаёт данные кусками, не держа весь ответ в памяти сразу

    // разбор: Собрать весь CSV в строку — значит держать его целиком в памяти воркера, что при больших объёмах и многих клиентах опасно. StreamingResponse принимает генератор и отдаёт тело порциями по мере готовности: память под один чанк, а не под весь ответ. Так же стримят файлы и результаты, вычисляемые на лету.

  4. #websockets_streaming4 / 5
    Почему websocket-эндпоинт нельзя обслужить на WSGI-сервере (gunicorn sync)?
    A)Можно, если увеличить число sync-воркеров gunicorn под количество клиентов
    B)gunicorn запрещает websockets в целях безопасности передаваемого трафика
    C)Websocket требует отдельного порта, а gunicorn умеет слушать только один
    D)Постоянный двусторонний канал требует ASGI — WSGI знает лишь запрос-ответ
    показать ответ и разбор
    +D)Постоянный двусторонний канал требует ASGI — WSGI знает лишь запрос-ответ

    // разбор: Websocket живёт в событийной модели с постоянным соединением и обменом в обе стороны — это спецификация ASGI. WSGI устроен как единичный синхронный вызов на запрос и такой канал выразить не может. Поэтому websocket-эндпоинты FastAPI обслуживает ASGI-сервер (uvicorn), а не sync-gunicorn.

  5. #websockets_streaming5 / 5
    Как выглядит жизненный цикл websocket-обработчика в FastAPI?
    A)Клиент сам управляет соединением, а серверный обработчик не участвует в обмене
    B)accept подтверждает соединение, дальше цикл receive/send до отключения
    C)Один вызов функции: принял сообщение, вернул ответ, соединение сразу закрылось
    D)Соединение открывается и закрывается автоматически на каждое отдельное сообщение
    показать ответ и разбор
    +B)accept подтверждает соединение, дальше цикл receive/send до отключения

    // разбор: Websocket-обработчик FastAPI: сначала await websocket.accept() завершает рукопожатие, затем обычно бесконечный цикл, где await websocket.receive_text()/receive_json() читает входящее, а send_* отправляет исходящее. Выход из цикла (или WebSocketDisconnect) закрывает соединение. Всё это в рамках одного долгоживущего вызова обработчика.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.