Как работает long polling и чем он отличается от обычного polling?
Что проверяют
Понимаете ли вы, как сервер доставляет события клиенту поверх обычного HTTP и во что это обходится бэкенду. Следом почти всегда спрашивают, почему не WebSocket или SSE и как держать тысячи ожидающих запросов на Python.
Как отвечать
Сравните два режима:
- обычный polling: клиент раз в N секунд делает запрос, сервер сразу отвечает, даже если новых данных нет. Просто и надёжно, но задержка доставки до N секунд, а большинство запросов уходят впустую;
- long polling: клиент делает запрос, сервер держит его открытым, пока не появятся данные или не истечёт таймаут, обычно от 20 до 30 секунд. Получив ответ, клиент сразу отправляет следующий запрос. Задержка близка к нулю, пустых ответов мало.
Цена long polling в том, что каждое ожидание держит соединение. В синхронном приложении на Django или Flask под gunicorn с sync-воркерами один висящий запрос занимает целый воркер: при восьми воркерах девятый клиент уже ждёт в очереди. Поэтому такие ручки пишут асинхронно на FastAPI, Starlette или aiohttp: ожидание через asyncio.Event или подписку на Redis Pub/Sub, а по таймауту asyncio.wait_for отдают пустой ответ.
Обязательная деталь: курсор. Клиент передаёт offset или last_event_id, и сервер отдаёт всё, что появилось после него. Иначе события, пришедшие между двумя запросами, теряются. Живой пример: метод getUpdates в Telegram Bot API с параметрами timeout и offset.
Закончите выбором: long polling хорош, когда нужна совместимость с любыми прокси и простая инфраструктура. Для одностороннего потока с сервера удобнее SSE, для двустороннего обмена WebSocket.
Где спотыкаются
Говорят, что long polling снижает нагрузку в целом. Запросов меньше, но открытых соединений больше, и на синхронном стеке это упирается в число воркеров быстрее, чем обычный polling.
Забывают про таймауты по дороге. У nginx proxy_read_timeout по умолчанию 60 секунд, у балансировщиков свои лимиты, поэтому сервер должен ответить пустым результатом раньше, чем соединение оборвёт кто-то посередине.
Не думают о нескольких экземплярах приложения. Событие пришло в один процесс, а клиент ждёт в другом, поэтому нужен общий канал вроде Redis Pub/Sub.