← Pull: миллион записей

01 — Масштабирование переноса данных · push

Когда данные приходят сами.

До сих пор скрипт сам просил следующую страницу — это pull. Бывает иначе: источник отправляет события, когда они происходят, — заказы, клики, изменения в базе (CDC). Это push. Не следующая ступень после pull, а другая модель работы со своими проблемами.

↓ Прокрутка меняет ситуацию. Управление позволяет её запустить.

01 / 05

Без посредника.

Продюсер отправляет 1 000 событий в секунду прямо потребителю, а тот пишет их в приёмник. Пока всё работает, задержки нет. На 10-й секунде приёмник становится недоступен. Продюсеру некуда девать события: у него буфер всего на 1 000 штук.

Запустите со сбоем на 15 секунд: буфер заполняется за секунду, остальные события теряются. Сбой приёмника стал проблемой продюсера.

Управление
02 / 05

Очередь между ними.

Поставим между ними надёжную очередь — Kafka, RabbitMQ или другой брокер. Продюсер пишет в очередь и не ждёт приёмник. Потребитель читает со своей скоростью и запоминает, до какого места дочитал (offset). Очередь хранит до 20 000 событий, и их можно прочитать заново — например, чтобы восстановить приёмник.

Тот же сбой: события копятся, лаг растёт до 15 000, а потом медленно уходит — у потребителя всего 500 событий в секунду запаса. Ускорьте потребителя или удлините сбой до 30 секунд: очередь переполнится и удалит старые события. Брокер — ещё одна система, которую нужно эксплуатировать.

Управление
03 / 05

Одно плохое сообщение.

Очередь разбита на 4 партиции, их читают два потребителя. На 10-й секунде в первую партицию попадает событие, которое невозможно записать: битые данные. Потребитель повторяет попытку снова и снова.

Без очереди недоставленных сообщений (DLQ) первая партиция стоит до конца, и лаг за ней растёт, хотя остальные работают. С DLQ после трёх попыток событие откладывается в сторону, и чтение продолжается. Но теперь кто-то должен разобрать DLQ.

Управление
04 / 05

Сообщение придёт ещё раз.

Потребитель сохраняет offset не после каждого события, а раз в несколько секунд. На 34-й секунде он падает и через 5 секунд перезапускается — с последнего сохранённого offset. Всё, что он записал после этого, придёт повторно: доставка «хотя бы один раз».

Без идемпотентной записи каждое повторное событие — дубль в приёмнике. Сохраняйте offset чаще: дублей меньше, обращений к брокеру больше. Идемпотентный потребитель — upsert по ключу события — убирает дубли совсем, и обычно это проще, чем добиваться доставки «ровно один раз».

Управление
05 / 05

Потребители не успевают.

Продюсер ускорился до 6 000 событий в секунду, а один потребитель пишет 1 000. Очередь делится на 8 партиций, и каждую читает один потребитель группы — больше восьми потребителей не помогут. Очередь хранит до 100 000 событий.

Добавляйте потребителей и следите за лагом: пока их общая скорость ниже потока, он растёт, пока очередь не начнёт удалять старое. Включите горячую партицию — половина событий с одним ключом — и её потребитель не успевает, сколько бы ни было остальных. В push лаг — главная метрика: за ним следят, его ограничивают и с ним договариваются.

Управление

Без очереди

Продюсер1 000/с
Буфер продюсера
  1. 0
ёмкость 1 000
Потребители
  • №1
Приёмникдоступен
Лаг, событий в очередимакс. 0
Прошло0 мсЛаг0Записано0Потеряно0

Готово к запуску. Сбои происходят в одно и то же время при каждом запуске.

Все числа — результат модели. Анимация сжимает 60 секунд до нескольких.

Pull или push

Сначала выберите модель, потом развивайте надёжность и скорость внутри неё. Очередь — не обязательный последний шаг любого переноса.

PullPush
Кто задаёт темпПотребитель: просит, когда готовПродюсер: отправляет, когда случилось
Типичная задачаМиграция, импорт, выгрузка по расписаниюСобытия, CDC, интеграции почти в реальном времени
Обратное давлениеСамо собой: не просим — не получаемБуфер, ограничения или растущий лаг
ВосстановлениеС ключа, страницы или checkpointС offset в брокере, повторное чтение
МасштабированиеДиапазоны ключей и несколько читателейПартиции и группы потребителей
СвязанностьЧитатель знает, как устроен источникПродюсер и потребитель развязаны
Что осталось за пределами модели

Шаг модели — 100 мс, длительность — 60 секунд. Продюсер производит события равномерно; при горячей партиции половина событий идёт в первую партицию. Потребитель пишет с постоянной скоростью; каждая партиция принадлежит одному потребителю (номер партиции по модулю числа потребителей). Приёмник недоступен с 10-й секунды; плохое событие появляется на 10-й секунде, одна попытка занимает 1 с, в DLQ — после трёх; потребитель падает на 33,7 с и перезапускается через 5 с. Очередь удаляет самые старые непрочитанные события сверх ёмкости, поровну поделённой между партициями. Не моделируются: перебалансировка группы, задержки сети и брокера, подтверждения продюсеру, повторное чтение истории (replay) и эволюция схемы событий.

Глубже в механизм

Как это устроено в реальных брокерах:

Apache Kafka — позиция потребителя и offset ↗Apache Kafka — гарантии доставки ↗RabbitMQ — dead letter exchanges ↗Заметки, на которых основано исследование ↗