Polling (периодический опрос)
Polling (периодический опрос)
Polling — способ следить за изменениями, при котором клиент сам через равные промежутки времени спрашивает сервер: «ну что, поменялось?». Сервер ничего не сообщает по своей инициативе, он только отвечает на вопросы. Вся нагрузка и вся задержка обнаружения лежат на стороне спрашивающего.
История
Опрос — ровно настолько же старая идея, насколько стара сама связь между двумя машинами. Как только появились два компьютера, соединённых каналом, возник вопрос: как один узнаёт, что у другого что-то произошло? Самый простой ответ — спрашивать.
- 1975, Unix Version 7, cron. По воспоминаниям Брайана Кернигана (одного из авторов Unix и языка C), первую версию
cronдля седьмой редакции Unix написал именно он. Программа просыпалась раз в минуту, читала таблицу расписаний и запускала то, чему пора. Это чистый опрос своей же таблицы: никто не «будит» cron, он сам заглядывает внутрь по таймеру. Родословнаяcronжива до сих пор — в systemd, в Kubernetes CronJob, в launchd на macOS. - Конец 1980-х, SNMP. Протокол управления сетями SNMP (Simple Network Management Protocol) построен на паре «менеджер — агент». Менеджер раз в N секунд обходит устройства и спрашивает у каждого счётчики: сколько пакетов прошло, сколько ошибок, сколько занято памяти. Агент сам никогда не звонит. Опрос — не побочная деталь SNMP, а его основная модель работы, и именно она определяет, как выглядит график нагрузки на сети: одинаковые всплески через равные интервалы.
- 1999–2005, веб научился разговаривать. Появился XMLHttpRequest, за ним — AJAX. Страница перестала быть документом и стала программой, которая может сама ходить на сервер, не перезагружаясь. Первое, что начали делать:
setIntervalраз в пять секунд дёргать эндпоинт. С этого момента опрос стал массовым явлением, а не приёмом сисадминов. - 2006, Comet и long polling. Термин Comet (в честь кометы — «хвост» из висящих запросов) предложил Алекс Рассел из Dojo Foundation. Идея: если сервер не умеет пушить, пусть клиент отправит запрос, а сервер не отвечает, пока не появится новость. Так родился long polling (длинный опрос) — компромисс, который жив до сих пор.
- 2011, WebSocket (RFC 6455). Двусторонний постоянный канал поверх одного TCP-соединения. Впервые у веба появился настоящий push без костылей. Примерно тогда же оформился Server-Sent Events — односторонний поток событий от сервера к браузеру; он до сих пор недооценён, потому что работает на обычном HTTP и обычно переживает корпоративные прокси.
- 2012–2018, Prometheus и pull-модель. Система мониторинга Prometheus, начатая в SoundCloud (Матти Пихлая, Юлиус Волз), принципиально работает на вытягивании: сервер мониторинга сам ходит к экспортёрам за метриками. В 2016-м проект передали в CNCF, в 2018-м он получил статус graduated. До Prometheus так же работали Nagios (1999, изначально NetSaint) и Zabbix (конец 1990-х, Алексей Владышев). То есть в мире мониторинга опрос не «устаревший вариант», а осознанный выбор с историей в четверть века.
Что это такое
Формально опрос — это модель взаимодействия, в которой инициатива всегда на клиенте. Клиент хранит у себя расписание («спросить в 12:00:00, 12:00:10, 12:00:20…»), а сервер хранит только текущее состояние. Сервер не знает, кто его слушает, и не обязан никого уведомлять.
Из этого единственного свойства вытекает почти всё остальное.
Задержка обнаружения равна интервалу опроса. Если спрашивать раз в десять минут, то в среднем вы узнаете об изменении через пять минут, а в худшем случае — через десять. Хочешь узнавать быстрее — спрашивай чаще. Другого рычага нет: частота опроса и есть точность обнаружения.
Нагрузка пропорциональна числу клиентов, а не числу событий. Один клиент раз в десять минут — это 144 запроса в сутки, ерунда. Тысяча клиентов с тем же интервалом — 144 тысячи запросов в сутки, и почти все они отвечают «ничего не изменилось». Сервер платит за каждый.
Сервер ничего не помнит. Ему не нужны открытые соединения, списки подписчиков, буферы недоставленного. Он живёт по принципу «спросили — ответил». Это делает опрос невероятно устойчивым: упал клиент, упал сервер, между ними порвалась сеть — при следующем тике всё просто продолжается. Нет состояния — нет проблемы с его восстановлением.
Пропуск события не фатален. При push-модели потерянное сообщение — это дыра, которую надо догонять. При опросе клиент каждый раз получает абсолютное состояние, а не дельту (приращение). Сравнил с прошлым — увидел расхождение. Догонять нечего: последний ответ и есть истина.
Отсюда и главные пары для сравнения.
- Polling vs webhook (push). Webhook — это когда сервер сам стучится к вам по заранее заданному URL, как только что-то произошло. Задержка — секунды, нагрузка пропорциональна числу событий, а не времени. Но вебхук требует, чтобы вы умели принимать входящие запросы из интернета, отвечать быстро, защищаться от подделок и разбираться с повторами. Опрос требует только умения делать исходящий запрос — а это умеет любая железка в любой закрытой сети.
- Polling vs WebSocket. WebSocket — постоянное соединение, писать могут обе стороны в любой момент, задержка минимальна. Но это stateful-соединение (с сохраняемым состоянием): сервер должен держать в памяти тысячи открытых коннектов, переживать их переподключение, а любая проксирующая прослойка может его прибить по таймауту.
- Polling vs long polling. Это не альтернативы, а варианты одного и того же. При long polling клиент тоже спрашивает, но сервер задерживает ответ, пока не появится новость или не истечёт таймаут (обычно 20–60 секунд). Задержка после события падает до миллисекунд, а число «пустых» запросов резко уменьшается. Ценой становится висящее соединение на каждого клиента — то есть сервер снова становится stateful, только наполовину.
- Polling vs подписка на поток. Kafka, NATS, RabbitMQ, MQTT — модели, где клиент подписывается на тему и получает события по мере появления. Это push, но не по HTTP, а внутри инфраструктуры сообщений. Требует брокера и его эксплуатации.
Аналогии из жизни
Чайник на кухне. Ты поставил чайник и каждые полминуты подходишь проверить, закипел ли. Работает: чайник не обязан быть умным — ни свистка, ни датчика, ни связи с тобой. Ты знаешь только его текущее состояние, забыл посмотреть — посмотришь в следующий раз, ничего не потеряется. Где ломается: если ты ушёл в другую комнату и вернулся через двадцать минут, все эти двадцать минут вода кипела и выкипала, а ты об этом не знал. Ровно так же система, опрашивающая раз в час, узнаёт о ночном сбое только утром. И второе: если к одному чайнику по очереди подбегают десять человек, они мешают друг другу, хотя воды в чайнике от этого не прибавляется.
Диспетчер и водители. Диспетчер обзванивает машины: «где ты?» — и записывает ответ. Работает: в машинах не нужна телеметрия, диспетчер сам задаёт ритм, а если связь пропала — при следующем звонке картина восстановится целиком, не нужно догонять пропущенные координаты. Где ломается: при тысяче машин и звонке раз в десять секунд диспетчер занят только звонками — он не может делать ничего другого, и именно он, а не дорога, становится узким местом. Причём 999 из 1000 ответов будут «еду по тому же адресу», то есть почти вся работа — впустую. Это и есть главный аргумент против частого опроса: цена платится за каждый тик, а не за каждое изменение.
Тесто под полотенцем. Повар каждые пять минут приподнимает полотенце и смотрит, поднялось ли тесто. Работает: не нужен никакой прибор, всё видно глазами, и в любой момент известно точное состояние — «поднялось на треть». Где ломается: каждый подъём полотенца выпускает тепло, и слишком частое любопытство замедляет то самое, за чем ты наблюдаешь. У этого есть прямой аналог в технике: опрос тяжёлого эндпоинта, который сам ходит в базу или пересчитывает данные, заметно нагружает систему. Есть даже отдельное правило в мониторинге: проверка здоровья должна быть дешёвой и не трогать тяжёлые зависимости.
Как это работает
Разберём на примере, который легко представить: скрипт следит за настройками внешнего сервиса и должен сообщить, когда там что-то поменялось в интересном поле.
Шаг 1. Задать расписание. Клиент вычисляет момент следующего опроса: сейчас плюс интервал. Интервал выбирается из компромисса «скорость обнаружения против нагрузки». Десять минут, минута, тридцать секунд — универсального ответа нет, есть только требования задачи.
Шаг 2. Сделать запрос. Обычный запрос за текущим состоянием: GET /api/object?key=.... Ничего специального в нём нет — именно поэтому опрос работает везде, где работает HTTP.
Шаг 3. Снять слепок. Из ответа вырезаются только те поля, за которыми следим. Не весь JSON, а конкретные значения: адрес, статус, счётчик, дата изменения. Это важная деталь, о ней ниже.
Шаг 4. Сравнить с предыдущим слепком.
слепок_новый = {поле: значение из ответа}
если слепок_новый != слепок_старый:
сообщить о различии
слепок_старый = слепок_новый
# если равно — молча ждём следующего тика
Шаг 5. Заснуть до следующего тика. Не «подождать ровно десять минут от начала работы», а «десять минут от конца предыдущего запроса». Разница принципиальна: если запрос шёл две секунды, то при первом варианте расписание постепенно съезжает, а при медленном ответе тики могут наложиться друг на друга.
Шаг 6. Пережить сбой. Запрос не ответил, вернул 500, порвалось соединение. Правильное поведение: не считать это изменением, не сбрасывать слепок, не заканчивать работу. Записать в лог и продолжить по расписанию.
Три ошибки, которые встречаются чаще всего
Первая: считать ошибку сети изменением состояния и разослать тревогу «всё сломалось». Второе: включить в слепок поля, которые меняются сами по себе (время последнего обращения, счётчик запросов, случайный идентификатор запроса) — тогда сравнение срабатывает на каждом тике, и опрос превращается в генератор ложных срабатываний. Третье: опрашивать быстрее, чем разрешает провайдер, и получить блокировку по лимиту запросов.
Разберём тик подробнее, потому что именно здесь ломается большинство самодельных мониторов.
Сравнивать надо нормализованное значение, а не сырой ответ. Ответ сервера почти всегда содержит что-то изменчивое: идентификатор запроса, время генерации, порядок ключей в JSON. Если сравнивать целиком, монитор будет «срабатывать» каждый раз. Поэтому слепок делается вручную и узко: перечислите поля, которые действительно означают изменение. Правило простое — если поле меняется между двумя запросами подряд, когда ничего не происходило, оно в слепок не попадает.
Слепок должен жить в файле, а не в памяти. Монитор, который перезапустился и потерял предыдущее состояние, на первом же тике решит, что «всё изменилось». Поэтому слепок пишется на диск рядом с логами, и при старте читается обратно. Тот же файл полезен как история: видно, когда значение стало другим.
Интервал лучше выбирать с разбросом. Если сто клиентов запускаются одновременно по расписанию ровно в 12:00:00, они ударят по серверу одной волной. Добавка случайных секунд (jitter, «дрожание») размазывает волну и заодно делает поведение менее похожим на бота. Ровный ритм метронома — характерная сигнатура автоматизации.
Считайте стоимость тика. Один запрос раз в минуту кажется бесплатным, пока не посчитаешь: 1440 запросов в сутки на один монитор, 43 тысячи в месяц. Умножьте на число мониторов и на то, что каждый запрос может тянуть данные из базы. Отсюда практика: чем чаще опрос, тем дешевле должен быть эндпоинт. Идеальный вариант — отдельная «облегчённая» ручка, которая отдаёт только искомое поле.
Опрос не бесплатен даже когда «ничего не произошло»
В push-модели цена пропорциональна числу событий: нет событий — нет трафика. В опросе цена пропорциональна времени: платить приходится за тишину. Поэтому главный вопрос при выборе интервала звучит не «как часто мне нужно узнавать», а «сколько я готов платить за то, что ничего не происходит». Ответ на него обычно и задаёт интервал.
Где встречается в обычной жизни
- Обновление почты в приложении. Телефон сам ходит за новой почтой раз в 15 минут (в стандартной настройке IMAP-ящика), и вы об этом не думаете. Настоящий push бывает, но он требует постоянного канала до сервера почты и потому доступен не всем и не всегда.
- Отслеживание посылки. Сайт доставки не звонит вам, когда курьер выехал. Он просто отдаёт текущий статус, а вы обновляете страницу. Курьерская система обновляет статус у себя, вы его вытягиваете.
- Табло на остановке и в аэропорту. Электронное табло раз в несколько секунд запрашивает у центра данные о рейсах. Именно поэтому время «обновлено 12 секунд назад», а при обрыве связи табло показывает устаревшие данные, но продолжает работать.
- Обновление приложения на телефоне. Никто не «толкает» вам новую версию — приложение само спрашивает магазин, вышло ли обновление. Проверка идёт по расписанию и с разбросом, чтобы миллионы телефонов не ударили в магазин одновременно.
- Обновление страницы банка в браузере. Некоторые банковские интерфейсы до сих пор перезапрашивают баланс раз в 30–60 секунд. Это чистый опрос, просто спрятанный за интерфейсом.
- Биржевой стакан в терминале. Часть терминалов подтягивает котировки опросом с частотой раз в секунду — со стороны это выглядит как живой поток, но внутри это просто очень частый опрос.
Где встречается в IT и бизнесе
- Мониторинг. Главный дом опроса. Сервер мониторинга сам ходит к сервисам за метриками и за ответом «жив ли». Pull-модель выбрана осознанно: если сервис замолчал, это сразу видно (запрос не отвечает), а при push-модели «молчит» и «сломался» выглядят одинаково.
- Асинхронные задачи через внешний API. «Отправили запрос — ждём результат»: сервис вернул идентификатор задачи, а вы раз в несколько секунд спрашиваете, готова ли она. Так работают очереди в API генерации изображений, отчёты в аналитических системах, проверки в системах проверки контрагентов. Вариант с опросом здесь используется потому, что принимать входящий вебхук со стороны небольшого проекта часто просто негде — нет публичного адреса.
- Интеграция двух систем, которые нельзя соединить напрямую. Корпоративные сети, закрытые контуры, чужие SaaS без вебхуков. Единственный доступный инструмент — исходящий запрос. Опрос становится не «плохим выбором», а единственным работающим.
- Сверка данных (сверка реестров). Раз в сутки система забирает полный список из внешнего источника и сравнивает со своим. Это опрос с интервалом в сутки, и он не «костыль», а нормальный режим для сверки: задача не в скорости, а в полноте.
- Обновление кэша и справочников. Курсы валют, тарифы, списки регионов, стоп-листы. Меняются редко, критичны к актуальности не секундно. Классический случай, когда опрос раз в час — идеальное решение.
- Проверка состояния долгих процессов. «Отрендерилось ли видео», «завершилась ли миграция», «выгрузился ли отчёт». Клиент периодически спрашивает статус и показывает прогресс.
Кто пользуется
- Prometheus — построен на опросе (scrape) с интервалом обычно 15–60 секунд, и это индустриальная норма для метрик. Именно из-за модели «сервер сам ходит к цели» Prometheus так хорошо обнаруживает падения: недоступная цель — это отдельная, явно видимая ситуация.
- Nagios — с 1999 года и до сих пор опрашивает хосты и сервисы по расписанию. На этой архитектуре выросла целая эпоха эксплуатации.
- Zabbix — тоже pull: сервер и прокси сами собирают данные с агентов на хостах.
- IMAP-клиенты (почта в телефоне и настольных программах) — опрос ящика по таймеру; стандартное значение 15 минут живёт десятилетиями.
- Kubernetes — сам контроллер опрашивает состояние объектов через механизм watch, но ручки проверки живости (liveness/readiness probe) — это честный периодический опрос, который kubelet делает по расписанию.
- Магазины приложений (App Store, Google Play) — устройство само проверяет обновления; при таком количестве устройств это единственный реально работающий вариант.
- Трекеры посылок и сервисы доставки — вытягивание статуса из систем перевозчика, потому что перевозчики не раздают вебхуки всем желающим.
Альтернативы и конкуренты
Webhook (обратный вызов).
Плюсы: задержка — секунды, нагрузка пропорциональна событиям, а не времени; экономит запросы в тишине.
Минусы: нужен публично доступный адрес, обработчик должен отвечать быстро, придётся проверять подпись и разбираться с повторными доставками; если получатель лежит — событие надо где-то хранить и досылать.
Long polling (длинный опрос).
Плюсы: задержка после события — миллисекунды, число пустых запросов падает в разы, работает на обычном HTTP и обычно проходит через прокси.
Минусы: каждое ожидание держит соединение и в браузере, и на сервере; таймауты прокси надо подгонять, а сервер вынужден помнить ожидающих — состояние вернулось.
WebSocket.
Плюсы: настоящий двусторонний обмен, минимальная задержка, одно соединение вместо тысяч запросов.
Минусы: соединение с состоянием, нужны переподключение и контроль живости; часть прокси и корпоративных сетей его режет; тестировать и отлаживать сложнее.
Server-Sent Events (SSE).
Плюсы: односторонний поток от сервера, работающий поверх обычного HTTP, автоматическое переподключение из коробки, простой текстовый формат.
Минусы: только от сервера к клиенту, исторические ограничения на число соединений в браузере (в HTTP/1.1), а серверу всё равно нужно удерживать открытые соединения.
Брокер сообщений (Kafka, RabbitMQ, NATS, MQTT).
Плюсы: доставка с гарантиями, буферизация, развязка производителя и потребителя, горизонтальное масштабирование читателей.
Минусы: отдельная инфраструктура, которую надо эксплуатировать; избыточно для «проверить одно поле в чужом API».
Когда НЕ стоит использовать
- Когда нужна реакция за секунды. Опрос раз в секунду не даёт гарантии «секунда»: он даёт среднюю задержку в полсекунды и худший случай в секунду, плюс постоянный поток запросов. Для торговых систем, чатов и событий безопасности это не подходит — нужен push.
- Когда клиентов много, а события редкие. Тысяча подписчиков и одно изменение в час — это 999 бесполезных запросов за тот же час. Суммарная нагрузка на сервер растёт линейно по числу клиентов, а полезная работа — нет. Это первый сценарий, где стоит переходить на вебхуки или поток.
- Когда провайдер ограничивает частоту запросов. Если у API лимит, скажем, десять запросов в секунду на весь аккаунт, то частый опрос съест лимит сам и оставит без него полезные вызовы. Приходится либо реже опрашивать, либо просить у провайдера потоковую ручку.
- Когда опрашиваемый эндпоинт дорогой. Если один запрос за статусом тянет join по пяти таблицам, то частый опрос превращается в способ положить собственную базу. Лечится отдельной дешёвой ручкой, но это дополнительная работа — иногда дешевле сразу сделать событие.
- Когда важна гарантия доставки каждого события. Опрос видит только текущее состояние. Если между двумя тиками значение изменилось дважды и вернулось к прежнему, для опрашивающего это выглядит как «ничего не было». Для аудита и событийной аналитики такой потери недопустимы.
Связанные понятия
- Webhook — обратный вызов: сервер сам сообщает вашему адресу о событии, противоположность опросу по направлению инициативы.
- Long polling — опрос с задержанным ответом: клиент спрашивает, сервер отвечает только когда появится новость или истечёт таймаут.
- Heartbeat — короткий сигнал «я жив», который участники посылают по расписанию; по сути это опрос самого себя, вывернутый наизнанку.
- Идемпотентность — свойство операции давать тот же результат при повторе; для опроса критична, потому что одинаковые запросы повторяются тысячи раз.
- Rate limiting — ограничение частоты запросов; главный внешний ограничитель при выборе интервала опроса.
- Jitter (дрожание) — случайный разброс расписания, размазывающий одновременные запросы множества клиентов во времени.
- Кэширование — сохранение ответа, чтобы не ходить за ним слишком часто; часто применяется вместе с опросом, чтобы дорогой эндпоинт не страдал.
Литература и источники
- RFC 6455 «The WebSocket Protocol» (2011) — как выглядит настоящий двусторонний канал, к которому обычно переходят с опроса.
- Документация Prometheus, раздел про сбор метрик (scrape) — канонический пример pull-модели с объяснением, почему выбрано вытягивание, а не отправка.
- Документация Zabbix и Nagios — как опрос устроен в классическом мониторинге: интервалы, таймауты, повторные проверки.
- Спецификация Server-Sent Events (входит в стандарты WHATWG по EventSource) — компромиссный вариант между опросом и полным двусторонним каналом.
- Книга «Release It!» Майкла Нигарда (второе издание, 2018, en) — про устойчивость интеграций: таймауты, повторы с нарастающей паузой, изоляция отказов. Прямо относится к тому, как правильно переживать неудачные тики.
- Статья «Comet: Low Latency Data for the Browser» (Алекс Рассел, 2006) — исторический текст, с которого начался long polling; искать в веб-архиве по названию.
Где встретилось у меня
На этой неделе я разбирался с одной интеграцией: нужно было понять, поменялось ли конкретное поле в настройках внешнего сервиса — там, где я инициировал изменение через поддержку. Прямого вебхука у сервиса нет, API отдаёт только текущий конфиг, поэтому вопрос «поменялось или нет» сводился ровно к опросу: запрашивать конфиг по расписанию, вырезать из него интересующие поля, сравнивать со слепком.
Попутно всплыли все три типовые грабли. Первая: подозрение на кэширование — пришлось проверять, не отдаёт ли сервер устаревший ответ (смотрел заголовки кэширования, запрашивал с обходным параметром и без cookies). Вторая: соблазн сравнивать ответ целиком, хотя в нём есть изменчивые поля. Третья: выбор интервала — при опросе раз в десять минут и получасовом окне ожидания выяснилось, что проверять чаще смысла нет, а тишина в логах выглядит ровно как «всё хорошо», хотя означает лишь «ничего не поменялось».
Практическая привычка
Если делаете мониторинг сами, заведите два файла: слепок состояния и журнал тиков. Первый даёт корректное сравнение после перезапуска, второй — ответ на вопрос «а он вообще работал?». Без журнала молчащий монитор неотличим от сломанного.
Краткое резюме
- Опрос — модель, где клиент сам спрашивает сервер по расписанию. Сервер никогда не сообщает первым и не хранит состояние о слушателях.
- Задержка обнаружения равна интервалу опроса; нагрузка растёт с числом клиентов, а не с числом событий. Платить приходится и за тишину.
- Главные плюсы: работает где угодно, не требует публичного адреса и состояния на сервере, устойчив к сбоям, а последний ответ всегда содержит полную картину.
- Главные минусы: медленное обнаружение, пустые запросы, потеря событий между тиками, упирается в лимиты частоты запросов.
- Сравнивать нужно узкий слепок из устойчивых полей, хранить его на диске, переживать ошибки без сброса состояния и добавлять случайный разброс к расписанию.