Черги завдань і балансування
1) Навіщо черги завдань
Черга завдань (job queue/work queue) роз'єднує виробників і виконавців за часом і швидкістю:- Згладжує піки: буфер між фронтом і важкими підсистемами.
- Стабілізує SLA: пріоритети та ізоляція класів навантаження.
- Спрощує відмовостійкість: ретраї, DLQ, повторна постановка.
- Масштабується горизонтально: додавайте воркери без зміни API.
Типові домени: обробка платежів, нотифікації, генерація звітів/медіа, ETL/ML-постпроцесинг, інтеграції із зовнішніми API.
2) Модель та основні поняття
Продюсер: публікує завдання (payload + метадані: idempotency key, пріоритет, дедлайн).
Черга/топік: буфер/лог завдань.
Воркер: бере завдання, обробляє, підтверджує (ack) або повертає з помилкою.
Visibility Timeout/Lease: «оренда» завдання на час обробки, після - авто-редоставка.
DLQ (Dead Letter Queue): «поховання» завдань після ліміту спроб/фатальних помилок.
Rate Limit/Concurrency: обмеження на споживання per-воркер/пер-черга/пер-тенант.
- Pull: воркер сам запитує завдання (дозує навантаження).
- Push: брокер пушит; потрібен захист від «заливки» слабких воркерів.
3) Семантики доставки і підтверджень
At-most-once: без ретраїв; швидше, але можлива втрата.
At-least-once (дефолт для більшості черг): можливі дублікати → потрібна ідемпотентність обробника.
Effectively exactly-once: досягається на рівні додатку (ідемпотентність, дедуп-стор, транзакції/аутбокс). Брокер може допомогти, але не «чарівна куля».
- Ack/Nack: явний результат.
- Requeue/Retry: с backoff + jitter.
- Poison message: відправка в DLQ.
4) Балансування і планування
4. 1 Черговість і алгоритми
FIFO: просто і прогнозовано.
Priority Queue: пріоритетні класи (P0... P3).
WRR/WSR (Weighted Round-Robin/Random): частки CPU/черезput між класами.
WFQ/DRR (аналог «справедливих» черг у мережах): частки per-тенант/клієнт.
Deadline/EDF: для завдань з дедлайнами.
Fair Share: обмеження «галасливих сусідів» (per-tenant quotas).
4. 2 Потоки обробки
Single-flight/Coalescing: об'єднуйте дублікати завдання за ключем.
Concurrency caps: жорсткі ліміти на паралелізм за типами задач/інтеграцій (зовнішні API).
4. 3 Гео і шардування
Шарди по ключу (tenant/id) → локальність даних, стабільний порядок в межах шардів.
Sticky кешам/ресурсам: хеш-роутинг на воркери з «прикріпленим» станом.
5) Ретраї, backoff і DLQ
Експоненціальний backoff + jitter: `base 2^attempt ± random`.
Максимум спроб і загальний дедлайн (time-to-die) на завдання.
Класифікація помилок: «retryable» (мережа/ліміт), «non-retryable» (валідація/бізнес-заборона).
Parking/Delay Queue: відкладені завдання (наприклад, повторити через 15 хв).
DLQ-політика: обов'язково вкажіть, куди і на яких умовах потрапляє «отруйне» повідомлення; передбачте reprocessor.
6) Ідемпотентність і дедуплікація
Idempotency-Key в задачі; стор (Redis/DB) з TTL для останніх N ключів:- seen → skip/merge/result-cache.
- Natural keys: використовуйте'order _ id/ payment_id' замість випадкових UUID.
- Outbox: запис факту задачі та її статусу в одній БД-транзакції з бізнес-операцією.
- Exactly-once в синці: 'UPSERT'по ключу, перевірка версій, «at-least-once» в черзі + ідемпотентність в БД.
7) Мульти-тенантність і класи SLA
Поділяйте черги/стріми за класами: `critical`, `standard`, `bulk`.
Квоти та пріоритети per-тенант (Gold/Silver/Bronze).
Ізоляція: dedicate-пули воркерів під P0; фонові - в окремому кластері/ноди.
Admission control: не приймати більше, ніж можете обробити в дедлайни.
8) Автоскейлінг воркерів
Метрики для скейлінгу: queue depth, arrival rate, processing time, SLA-дедлайни.
KEDA/Horizontal Pod Autoscaler: тригери по глибині SQS/Rabbit/Kafka lag.
Стримуючі фактори: зовнішні API rate limits, база даних (не зруйнувати бекенд).
9) Технологічні варіанти і патерни
9. 1 RabbitMQ/AMQP
Exchanges: direct/topic/fanout; Queues с ack/ttl/DLQ (dead-letter exchange).
Prefetch (QoS) регулює «скільки завдань на воркері».
ini x-dead-letter-exchange=dlx x-dead-letter-routing-key=jobs.failed x-message-ttl=60000
9. 2 SQS (і аналоги)
Visibility Timeout, DelaySeconds, RedrivePolicy (DLQ).
Ідемпотентність - на додатку (дедуп-таблиця).
Ліміти: батчі 1-10 повідомлень; орієнтуйтеся на ідемпотентні синці.
9. 3 Kafka/NATS JetStream
Для масштабних пайплайнів: висока пропускна, ретеншн/реплей.
Task-черга поверх логів: одне завдання = одне повідомлення; контроль «один воркер на ключ» через партіонування/subject.
Ретраї: окремі топіки/subject-суфікси з backoff.
9. 4 Redis-черги (Sidekiq/Resque/Bull/Celery-Redis)
Дуже низька латентність; слідкуйте за стійкістю (RDB/AOF), ключами retry і lock-keys для single-flight.
Підходить для «легких» завдань, не для довготривалого ретеншну.
9. 5 Фреймворки
Celery (Python), Sidekiq (Ruby), RQ/BullMQ (Node), Huey/Resque - готові ретраї, розклади, middleware, метрики.
10) Схеми маршрутизації та балансування
Round-Robin: рівномірно, але не враховує «тяжкість» завдань.
Weighted RR: розподіл за потужностями воркерів/пулу.
Fair/Backpressure-aware: воркер забирає нове завдання тільки при готовності.
Priority lanes: окремі черги на клас; воркери читають в порядку [P0→... →Pn] при наявності.
Hash-routing: 'hash (key)% shards'- для stateful/кешованої обробки.
11) Таймаути, дедлайни і SLA
Per-task timeout: внутрішня «оренда» роботи (в коді воркера) ≤ Visibility Timeout брокера.
Global deadline: завдання не має сенсу після T часу - NACK→DLQ.
Budget-aware: скорочуйте роботу (brownout) при наближенні дедлайну (часткові результати).
12) Спостережуваність і управління
12. 1 Метрики
`queue_depth`, `arrival_rate`, `service_rate`, `lag` (Kafka), `invisible_messages` (SQS).
`success/failed/retired_total`, `retry_attempts`, `dlq_in_total`, `processing_time_ms{p50,p95,p99}`.
`idempotency_hit_rate`, `dedup_drops_total`, `poison_total`.
12. 2 Логи/трейсинг
Кореляція: 'job _ id','correlation _ id', ключ дедуплікації.
Позначайте'retry/backoff/dlq'як події; лінковка зі span вихідного запиту.
12. 3 Дашборди/алерти
Тригери: глибина> X, p99> SLO, зростання DLQ, «залиплі» завдання (visibility закінчився> N), «гарячі» ключі.
13) Безпека та відповідність
Ізоляція орендарів: окремі черги/ключ-простору, ACL, квоти.
Шифрування на транспорті та/або «в спокої».
PII-мінімізація в payload; хеш/ID замість сирої PII.
Секрети: не класти токени в тіло завдань, використовувати vault/refs.
14) Анти-патерни
Ретраї без ідемпотентності → дублі операцій/гроші «двічі».
Одна гігантська черга «на все» → немає ізоляції, непередбачувані затримки.
Нескінченні ретраї без DLQ → вічні «отруйні» завдання.
Visibility Timeout <час обробки → каскадні дублікати.
Великі payload в черзі → тисне мережу/пам'ять; краще зберігати в об'єктному сторі і передавати посилання.
Пуш-модель без backpressure → воркери захлинаються.
Змішування критичних і bulk-завдань в одному пулі воркерів.
15) Чек-лист впровадження
- Класифікуйте задачі за SLA (P0/P1/P2) та обсягом.
- Виберіть брокер/фреймворк з потрібною семантикою і ретеншном.
- Спроектуйте ключі, пріоритети та маршрутизацію (hash/shards/priority lanes).
- Увімкніть ретраї з backoff + jitter і DLQ-політику.
- Реалізуйте ідемпотентність (ключі, upsert, дедуп-стор з TTL).
- Налаштуйте таймаути: per-task, visibility, загальний дедлайн.
- Обмежте concurrency і rate по інтеграціях/тенантам.
- Автоскейлінг по глибині/лагу з запобіжниками.
- Метрики/трейсинг/алерти; runbooks на «шторм» і переповнення DLQ.
- Тести на фейли: падіння воркера, «отруйне» повідомлення, перевантаження, довгі завдання.
16) Приклади конфігурацій і коду
16. 1 Celery (Redis/Rabbit) - базовий флоу
python app = Celery("jobs", broker="amqp://...", backend="redis://...")
app.conf.task_acks_late = True # ack после выполнения app.conf.broker_transport_options = {"visibility_timeout": 3600}
app.conf.task_default_retry_delay = 5 app.conf.task_time_limit = 300 # hard timeout
@app.task(bind=True, autoretry_for=(Exception,), retry_backoff=True, retry_jitter=True, max_retries=6)
def process_order(self, order_id):
if seen(order_id): return "ok" # идемпотентность do_work(order_id)
mark_seen(order_id)
return "ok"
16. 2 RabbitMQ — DLQ/TTL
ini x-dead-letter-exchange=dlx x-dead-letter-routing-key=jobs.dlq x-message-ttl=600000 # 10 минут x-max-priority=10
16. 3 Kafka - ретраї за рівнями
orders -> orders.retry.5s -> orders.retry.1m -> orders.dlq
(Перекладайте з відкладеною доставкою через scheduler/cron-consumer.)
16. 4 NATS JetStream — consumer с backoff
bash nats consumer add JOBS WORKERS --filter "jobs.email" \
--deliver pull --ack explicit --max-deliver 6 \
--backoff "1s,5s,30s,2m,5m"
17) FAQ
Q: Коли вибирати push проти pull?
A: Pull дає природний backpressure і «чесне» балансування; push простіше на низьких швидкостях і коли потрібен мінімальний TTFB, але вимагає обмежувачів.
Q: Як уникнути «гарячого» ключа?
A: Шардуйте за складеним ключем ('order _ id% N'), буферизуйте і робіть batch-обробку, вводьте ліміти per-ключ.
Q: Чи можна «exactly-once»?
A: Практично - через ідемпотентність і транзакційний аутбокс. Повністю «математичне» exactly-once на всьому шляху рідко досяжне і дорого.
Q: Де зберігати великі вкладення завдання?
A: В об'єктному сховищі (S3/GCS), а в задачі - посилання/ID; знижує тиск на брокер і мережу.
Q: Як вибрати TTL/visibility?
A: Visibility ≥ p99 часу обробки × запас 2-3 ×. TTL завдання - менше бізнес-дедлайну.
18) Підсумки
Сильна система черг - це баланс між семантикою доставки, пріоритетами та обмежувачами. Проектуйте ключі і маршрутизацію, забезпечте ідемпотентність, ретраї з backoff і DLQ, розподіляйте ресурси по класах SLA і стежте за метриками. Тоді ваші фонові процеси будуть передбачуваними, стійкими і масштабованими - без сюрпризів під піками.