Какие способы ускорения обработки сообщений в очереди существуют?
Что проверяют
Умеете ли вы сначала найти узкое место и только потом выбирать инструмент. Следом обычно спрашивают, чем здесь отличаются потоки, процессы и asyncio и что станет с порядком сообщений.
Как отвечать
Начните с диагностики: растёт ли отставание (lag консьюмер-группы в Kafka, глубина очереди в RabbitMQ), сколько длится обработка одного сообщения и на что уходит время, на ожидание сети и базы или на процессор.
Дальше способы по ситуации:
- больше потребителей: конкурирующие консьюмеры на одну очередь. В Kafka параллелизм внутри группы ограничен числом партиций;
- prefetch:
basic_qos(prefetch_count=N)в RabbitMQ убирает сетевую паузу между сообщениями. В Celery этоworker_prefetch_multiplier, по умолчанию 4; для долгих задач ставят 1 вместе сtask_acks_late; - I/O-bound:
asyncioсaio-pikaилиaiokafkaи ограничение параллелизма черезasyncio.Semaphore, либо пул потоков: на ожидании ввода-вывода GIL отпускается; - CPU-bound:
ProcessPoolExecutorили prefork-пул Celery. Потоки упрутся в GIL, а free-threaded сборка CPython пока не стандарт для продакшена; - пачки: забирать сообщения пачкой (
getmany()в aiokafka) и писать в базу однимexecutemanyилиCOPYвместо тысячи мелкихINSERT; - сериализация:
orjsonилиmsgpackвместо стандартногоjsonна горячем пути; - раздельные очереди: тяжёлые задачи отдельно, чтобы не задерживали лёгкие;
- ошибки: упавшее сообщение уходит в dead letter queue или отложенный повтор, а не крутится в цикле, занимая воркер.
Доставка почти всегда «хотя бы один раз», поэтому обработчик должен быть идемпотентным.
Где спотыкаются
Добавляют потоки под тяжёлые вычисления. Из-за GIL скорость не растёт, а иногда падает.
Поднимают число консьюмеров в Kafka выше числа партиций. Лишние простаивают без назначенной партиции.
Ставят большой prefetch для долгих задач. Один воркер забирает десятки сообщений себе, а соседние стоят без работы.