Асинхронная обработка: что отправить в фон, а что оставить в чате

Синхронный вызов модели прост: отправили запрос, дождались ответа, показали пользователю. Пока задачи короткие, этого достаточно. Проблемы начинаются, когда обработка занимает минуту или когда таких задач сотня.

ОЧЕРЕДЬТяжёлое уходит в фонПользователь не ждёт, а получает уведомлениеЗадачипринимаются мгновенноОчередьхранит порядокОбработчик 1свой лимит скорости, свои повторыОбработчик 2свой лимит скорости, свои повторыОбработчик 3свой лимит скорости, свои повторы
Задачи принимаются мгновенно, а обрабатываются отдельными процессами со своими лимитами

Признак, по которому выбирают режим

ВЫБОРКогда синхронно, а когда в фонОтвет в чатесинхроннопользователь ждёт здесь и сейчасРазбор загруженного файлав фонсекунды или минуты работыНочной пересчёт каталогав фонрезультат нужен к утруПодсказка при вводесинхронноиначе теряет смыслПризнак простой: если пользователь смотрит на экран и ждёт — синхронно. Во всех остальных случаях в фон.
Если пользователь смотрит на экран и ждёт — синхронно. Во всех остальных случаях в фон

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

Что даёт очередь

**Устойчивость к всплескам.** Пришло сразу двести задач — они встали в очередь, а не уронили сервис.

**Соблюдение лимитов.** Скорость обработки задаётся вами, а не тем, как быстро приходят задачи.

**Повторы без потерь.** Упавшая задача возвращается в очередь и обрабатывается снова, а не исчезает вместе с запросом пользователя.

**Приоритеты.** Пользовательские задачи проходят вперёд фоновых.

Что предусмотреть

  • **Идемпотентность.** Задача может выполниться дважды: обработчик упал после вызова модели, но до записи результата. Проверяйте, не сделана ли работа уже.
  • **Ограничение числа попыток.** Иначе задача с неисправимой ошибкой будет повторяться вечно и жечь бюджет.
  • **Отдельная очередь для неудачных задач.** Туда уходит то, что не получилось после всех попыток, — оттуда это разбирают руками.
  • **Срок жизни задачи.** Результат, который стал неактуален, лучше не считать вовсе.
  • **Отмена.** Пользователь передумал — задача должна сниматься до вызова модели.

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

Как сообщать о результате

Три способа, от простого к сложному: письмо, уведомление в интерфейсе, обновление страницы в реальном времени.

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

Если задача идёт долго, показывайте прогресс по этапам, а не проценты. «Расшифровка завершена, готовим сводку» понятнее, чем полоса, застрявшая на сорока процентах.

Разделение потоков

Пользовательские и фоновые задачи должны идти по разным очередям с разными лимитами. Иначе ночной пересчёт каталога способен заблокировать чат на сайте.

Простейшая схема: две очереди, у фоновой жёстко ограниченная скорость, у пользовательской приоритет. При нехватке лимита замедляется фоновая, а не та, где кто-то ждёт ответа.

Пакетная обработка

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

Подходит для ночных пересчётов, разметки данных и массовой генерации описаний. Не подходит ни для чего, что видит пользователь в момент работы.

Как устроен обработчик

Обработчик — обычный процесс, который в цикле берёт задачу из очереди, выполняет её и записывает результат. Несколько деталей отличают рабочую реализацию от учебной.

**Задача помечается взятой в работу.** Иначе второй обработчик возьмёт её же.

**У задачи есть срок выполнения.** Если обработчик упал, задача через заданное время возвращается в очередь, а не зависает навсегда.

**Результат пишется одной операцией вместе со снятием задачи.** Иначе возможно состояние, когда результат записан, а задача осталась в очереди.

**Количество обработчиков настраивается.** Это ваш основной рычаг: им регулируется и скорость, и расход.

Что мерить

Четыре величины показывают состояние системы почти полностью.

МетрикаО чём говорит
Длина очередиУспеваете ли за потоком
Время ожидания в очередиЧто чувствует пользователь
Доля задач в очереди неудачныхЕсть ли системная поломка
Число повторов на задачуУпор в лимиты или сбои провайдера

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

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

С чего начать

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

Одна такая правка убирает главную жалобу на скорость и заодно даёт готовый каркас: следующие фоновые задачи ложатся в него без дополнительной работы.

Стоит заранее решить и что показывать в интерфейсе, когда очередь длинная. Честная оценка вроде «перед вами двенадцать задач, обычно это около трёх минут» воспринимается заметно лучше, чем бесконечный индикатор. Оценка считается по средней скорости обработки за последний час — цифра неточная, но полезнее молчания.

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

Написать