01 — Масштабирование переноса данных · push
Когда данные приходят сами.
До сих пор скрипт сам просил следующую страницу — это pull. Бывает иначе: источник отправляет события, когда они происходят, — заказы, клики, изменения в базе (CDC). Это push. Не следующая ступень после pull, а другая модель работы со своими проблемами.
↓ Прокрутка меняет ситуацию. Управление позволяет её запустить.
Без посредника.
Продюсер отправляет 1 000 событий в секунду прямо потребителю, а тот пишет их в приёмник. Пока всё работает, задержки нет. На 10-й секунде приёмник становится недоступен. Продюсеру некуда девать события: у него буфер всего на 1 000 штук.
Запустите со сбоем на 15 секунд: буфер заполняется за секунду, остальные события теряются. Сбой приёмника стал проблемой продюсера.
УправлениеОчередь между ними.
Поставим между ними надёжную очередь — Kafka, RabbitMQ или другой брокер. Продюсер пишет в очередь и не ждёт приёмник. Потребитель читает со своей скоростью и запоминает, до какого места дочитал (offset). Очередь хранит до 20 000 событий, и их можно прочитать заново — например, чтобы восстановить приёмник.
Тот же сбой: события копятся, лаг растёт до 15 000, а потом медленно уходит — у потребителя всего 500 событий в секунду запаса. Ускорьте потребителя или удлините сбой до 30 секунд: очередь переполнится и удалит старые события. Брокер — ещё одна система, которую нужно эксплуатировать.
УправлениеОдно плохое сообщение.
Очередь разбита на 4 партиции, их читают два потребителя. На 10-й секунде в первую партицию попадает событие, которое невозможно записать: битые данные. Потребитель повторяет попытку снова и снова.
Без очереди недоставленных сообщений (DLQ) первая партиция стоит до конца, и лаг за ней растёт, хотя остальные работают. С DLQ после трёх попыток событие откладывается в сторону, и чтение продолжается. Но теперь кто-то должен разобрать DLQ.
УправлениеСообщение придёт ещё раз.
Потребитель сохраняет offset не после каждого события, а раз в несколько секунд. На 34-й секунде он падает и через 5 секунд перезапускается — с последнего сохранённого offset. Всё, что он записал после этого, придёт повторно: доставка «хотя бы один раз».
Без идемпотентной записи каждое повторное событие — дубль в приёмнике. Сохраняйте offset чаще: дублей меньше, обращений к брокеру больше. Идемпотентный потребитель — upsert по ключу события — убирает дубли совсем, и обычно это проще, чем добиваться доставки «ровно один раз».
УправлениеПотребители не успевают.
Продюсер ускорился до 6 000 событий в секунду, а один потребитель пишет 1 000. Очередь делится на 8 партиций, и каждую читает один потребитель группы — больше восьми потребителей не помогут. Очередь хранит до 100 000 событий.
Добавляйте потребителей и следите за лагом: пока их общая скорость ниже потока, он растёт, пока очередь не начнёт удалять старое. Включите горячую партицию — половина событий с одним ключом — и её потребитель не успевает, сколько бы ни было остальных. В push лаг — главная метрика: за ним следят, его ограничивают и с ним договариваются.
УправлениеБез очереди
- 0
- №1
Готово к запуску. Сбои происходят в одно и то же время при каждом запуске.
Все числа — результат модели. Анимация сжимает 60 секунд до нескольких.
Pull или push
Сначала выберите модель, потом развивайте надёжность и скорость внутри неё. Очередь — не обязательный последний шаг любого переноса.
| Pull | Push | |
|---|---|---|
| Кто задаёт темп | Потребитель: просит, когда готов | Продюсер: отправляет, когда случилось |
| Типичная задача | Миграция, импорт, выгрузка по расписанию | События, CDC, интеграции почти в реальном времени |
| Обратное давление | Само собой: не просим — не получаем | Буфер, ограничения или растущий лаг |
| Восстановление | С ключа, страницы или checkpoint | С offset в брокере, повторное чтение |
| Масштабирование | Диапазоны ключей и несколько читателей | Партиции и группы потребителей |
| Связанность | Читатель знает, как устроен источник | Продюсер и потребитель развязаны |
Что осталось за пределами модели
Шаг модели — 100 мс, длительность — 60 секунд. Продюсер производит события равномерно; при горячей партиции половина событий идёт в первую партицию. Потребитель пишет с постоянной скоростью; каждая партиция принадлежит одному потребителю (номер партиции по модулю числа потребителей). Приёмник недоступен с 10-й секунды; плохое событие появляется на 10-й секунде, одна попытка занимает 1 с, в DLQ — после трёх; потребитель падает на 33,7 с и перезапускается через 5 с. Очередь удаляет самые старые непрочитанные события сверх ёмкости, поровну поделённой между партициями. Не моделируются: перебалансировка группы, задержки сети и брокера, подтверждения продюсеру, повторное чтение истории (replay) и эволюция схемы событий.
Глубже в механизм
Как это устроено в реальных брокерах:
Apache Kafka — позиция потребителя и offset ↗Apache Kafka — гарантии доставки ↗RabbitMQ — dead letter exchanges ↗Заметки, на которых основано исследование ↗