Создание систем для крупномасштабных и длительных фоновых задач.
Автор: Ilias Chebbi на UnsplashНесколько месяцев назад я взял на себя роль, требующую создания инфраструктуры для потоковой передачи медиа (аудио). Но помимо предоставления аудио в виде потоковых фрагментов, существовали длительные задачи обработки медиа и обширный конвейер RAG, обеспечивающий транскрипцию, перекодирование, встраивание и последовательные обновления медиа. Создание MVP с производственным мышлением заставило нас повторять процесс, пока мы не достигли безупречной системы. Наш подход заключался в интеграции функций и базового стека приоритетов.
В процессе создания каждая итерация была ответом на немедленную и часто "всеобъемлющую" потребность. Первоначальной проблемой была очередь задач, с которой легко справлялся Redis; мы просто запускали и забывали. Bull MQ в фреймворке NEST JS дал нам еще лучший контроль над повторными попытками, отставаниями и очередью недоставленных писем. Локально и с несколькими полезными нагрузками в производстве мы правильно настроили поток медиа. Вскоре мы столкнулись с проблемой наблюдаемости:
Логи → Запись задач (запросы, ответы, ошибки).
Метрики → Сколько / как часто эти задачи выполняются, не выполняются, завершаются и т.д.
Трассировки → Путь, который задача прошла через сервисы (функции/методы, вызываемые в пути потока).
Некоторые из этих проблем можно решить, разрабатывая API и создавая пользовательскую панель управления для их подключения, но проблема масштабируемости останется. И на самом деле, мы разработали API.
Проблема управления сложными, длительными рабочими процессами бэкенда, где сбои должны быть восстанавливаемыми, а состояние должно быть устойчивым, Inngest стал нашим архитектурным спасением. Он фундаментально изменил наш подход: каждая длительная фоновая задача становится фоновой функцией, запускаемой определенным событием.
Например, событие Transcription.request запустит функцию TranscribeAudio. Эта функция может содержать пошаговые выполнения для: fetch_audio_metadata, deepgram_transcribe, parse_save_trasncription и notify_user.
Основным примитивом долговечности являются пошаговые выполнения. Фоновая функция внутренне разбивается на эти пошаговые выполнения, каждое из которых содержит минимальный, атомарный блок логики.
Абстракция функции Inngest:
import { inngest } from 'inngest-client';
export const createMyFunction = (dependencies) => {
return inngest.createFunction(
{
id: 'my-function',
name: 'My Example Function',
retries: 3, // retry the entire run on failure
concurrency: { limit: 5 },
onFailure: async ({ event, error, step }) => {
// handle errors here
await step.run('handle-error', async () => {
console.error('Error processing event:', error);
});
},
},
{ event: 'my/event.triggered' },
async ({ event, step }) => {
const { payload } = event.data;
// Step 1: Define first step
const step1Result = await step.run('step-1', async () => {
// logic for step 1
return `Processed ${payload}`;
});
// Step 2: Define second step
const step2Result = await step.run('step-2', async () => {
// logic for step 2
return step1Result + ' -> step 2';
});
// Step N: Continue as needed
await step.run('final-step', async () => {
// finalization logic
console.log('Finished processing:', step2Result);
});
return { success: true };
},
);
};
Событийно-ориентированная модель Inngest обеспечивает детальное представление о каждом выполнении рабочего процесса:
Недостаток полагания на чистую обработку событий заключается в том, что, хотя Inngest эффективно ставит в очередь выполнение функций, сами события не ставятся в очередь внутренне в традиционном смысле брокера сообщений. Это отсутствие явной очереди событий может быть проблематичным в сценариях с высоким трафиком из-за потенциальных условий гонки или потерянных событий, если конечная точка приема перегружена.
Чтобы решить эту проблему и обеспечить строгую долговечность событий, мы внедрили специальную систему очередей в качестве буфера.
AWS Simple Queue System (SQS) была выбранной системой (хотя любая надежная система очередей подойдет), учитывая нашу существующую инфраструктуру на AWS. Мы спроектировали систему с двумя очередями: Основная очередь и Очередь недоставленных писем (DLQ).
Мы создали рабочую среду Elastic Beanstalk (EB), специально настроенную для потребления сообщений непосредственно из Основной очереди. Если сообщение в Основной очереди не удается обработать рабочим процессом EB определенное количество раз, Основная очередь автоматически перемещает неудачное сообщение в специальную DLQ. Это гарантирует, что ни одно событие не будет потеряно навсегда, если оно не сможет запуститься или быть подхваченным Inngest. Эта рабочая среда отличается от стандартной среды веб-сервера EB, поскольку ее единственная ответственность - потребление и обработка сообщений (в данном случае, пересылка потребляемого сообщения в конечную точку API Inngest).
Недооцененной и довольно актуальной частью создания инфраструктуры корпоративного масштаба является то, что она потребляет ресурсы, и они долгосрочны. Архитектура микросервисов обеспечивает масштабируемость для каждого сервиса. В игру вступают хранилище, оперативная память и тайм-ауты ресурсов. Наша спецификация для типа экземпляра AWS, например, быстро перешла от t3.micro к t3.small, а сейчас зафиксирована на t3.medium. Для длительных, интенсивных по CPU фоновых задач горизонтальное масштабирование с крошечными экземплярами не работает, потому что узким местом является время, необходимое для обработки одной задачи, а не объем новых задач, поступающих в очередь.
Задачи или функции, такие как перекодирование, встраивание, обычно ограничены CPU и ограничены памятью. Ограничены CPU, потому что они требуют устойчивого, интенсивного использования CPU, и ограничены памятью, потому что они часто требуют значительного объема оперативной памяти для загрузки больших моделей или эффективной обработки больших файлов или полезных нагрузок.
В конечном итоге, эта расширенная архитектура, размещающая долговечность SQS и контролируемое выполнение рабочей среды EB непосредственно выше API Inngest, обеспечила необходимую устойчивость. Мы достигли строгого владения событиями, устранили условия гонки во время всплесков трафика и получили энергонезависимый механизм недоставленных писем. Мы использовали Inngest для его возможностей оркестрации рабочих процессов и отладки, полагаясь на примитивы AWS для максимальной пропускной способности сообщений и долговечности. Полученная система не только масштабируема, но и высоко проверяема, успешно преобразуя сложные, длительные задачи бэкенда в безопасные, наблюдаемые и отказоустойчивые микро-шаги.
Building Spotify for Sermons. была первоначально опубликована в Coinmonks на Medium, где люди продолжают обсуждение, выделяя и отвечая на эту историю.

