Construyendo para sistemas grandes y trabajos en segundo plano de larga duración.
Crédito: Ilias Chebbi en UnsplashHace meses, asumí el rol que requería construir infraestructura para streaming de medios (audio). Pero más allá de servir audio como fragmentos transmisibles, había trabajos de procesamiento de medios de larga duración y una extensa canalización RAG que atendía la transcripción, transcodificación, incrustación y actualizaciones secuenciales de medios. Construir un MVP con mentalidad de producción nos hizo reiterar hasta lograr un sistema fluido. Nuestro enfoque ha sido uno donde integramos características y la pila subyacente de prioridades.
Durante el proceso de construcción, cada iteración surgió como respuesta a una necesidad inmediata y a menudo "abarcadora". La preocupación inicial era la cola de trabajos, que se satisfizo fácilmente con Redis; simplemente disparamos y olvidamos. Bull MQ en el framework NEST JS nos dio un control aún mejor sobre los reintentos, atrasos y la cola de cartas muertas. Localmente y con algunas cargas útiles en producción, conseguimos el flujo de medios correcto. Pronto nos vimos agobiados por el peso de la Observabilidad:
Logs → Registro de trabajos (solicitudes, respuestas, errores).
Métricas → Cuánto / con qué frecuencia estos trabajos se ejecutan, fallan, completan, etc.
Trazas → La ruta que tomó un trabajo a través de servicios (funciones/métodos llamados dentro de la ruta de flujo).
Puedes resolver algunos de estos diseñando APIs y construyendo un panel personalizado para conectarlos, pero el problema de escalabilidad será suficiente. Y de hecho, diseñamos las APIs.
El desafío de gestionar flujos de trabajo backend complejos y de larga duración, donde las fallas deben ser recuperables y el estado debe ser duradero, Inngest se convirtió en nuestra salvación arquitectónica. Reformuló fundamentalmente nuestro enfoque: cada trabajo en segundo plano de larga duración se convierte en una función en segundo plano, activada por un evento específico.
Por ejemplo, un evento Transcription.request activará una función TranscribeAudio. Esta función podría contener ejecuciones de pasos para: fetch_audio_metadata, deepgram_transcribe, parse_save_trasncription y notify_user.
La primitiva de durabilidad central son las ejecuciones de pasos. Una función en segundo plano se descompone internamente en estas ejecuciones de pasos, cada una conteniendo un bloque mínimo y atómico de lógica.
Abstracto de función 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 };
},
);
};
El modelo basado en eventos de Inngest proporciona una visión granular de cada ejecución de flujo de trabajo:
La advertencia de confiar en el procesamiento puro de eventos es que, mientras Inngest pone eficientemente en cola las ejecuciones de funciones, los eventos en sí mismos no están internamente en cola en el sentido tradicional de un broker de mensajería. Esta ausencia de una cola de eventos explícita puede ser problemática en escenarios de alto tráfico debido a posibles condiciones de carrera o eventos perdidos si el punto final de ingestión está sobrecargado.
Para abordar esto y hacer cumplir la durabilidad estricta de eventos, implementamos un sistema de colas dedicado como buffer.
AWS Simple Queue System (SQS) fue el sistema elegido (aunque cualquier sistema de colas robusto es factible), dado nuestra infraestructura existente en AWS. Diseñamos un sistema de dos colas: una Cola Principal y una Cola de Cartas Muertas (DLQ).
Establecimos un Entorno de Trabajador Elastic Beanstalk (EB) específicamente configurado para consumir mensajes directamente de la Cola Principal. Si un mensaje en la Cola Principal no puede ser procesado por el Trabajador EB un número determinado de veces, la Cola Principal mueve automáticamente el mensaje fallido a la DLQ dedicada. Esto asegura que ningún evento se pierda permanentemente si falla al activarse o ser recogido por Inngest. Este entorno de trabajador difiere de un entorno de servidor web EB estándar, ya que su única responsabilidad es el consumo y procesamiento de mensajes (en este caso, reenviar el mensaje consumido al punto final de la API de Inngest).
Una parte subestimada y bastante pertinente de construir infraestructura a escala empresarial es que consume recursos, y son de larga duración. La arquitectura de microservicios proporciona escalabilidad por servicio. Almacenamiento, RAM y tiempos de espera de recursos entrarán en juego. Nuestra especificación para el tipo de instancia AWS, por ejemplo, pasó rápidamente de t3.micro a t3.small, y ahora está fijada en t3.medium. Para trabajos en segundo plano de larga duración e intensivos en CPU, el escalado horizontal con instancias pequeñas falla porque el cuello de botella es el tiempo que toma procesar un solo trabajo, no el volumen de nuevos trabajos que entran en la cola.
Trabajos o funciones como transcodificación, incrustación son típicamente limitados por CPU y limitados por Memoria. Limitados por CPU porque requieren un uso sostenido e intenso de CPU, y Limitados por Memoria porque a menudo requieren RAM sustancial para cargar modelos grandes o manejar archivos grandes o cargas útiles de manera eficiente.
En última instancia, esta arquitectura aumentada, colocando la durabilidad de SQS y la ejecución controlada de un entorno de Trabajador EB directamente aguas arriba de la API de Inngest, proporcionó resiliencia esencial. Logramos una propiedad estricta de eventos, eliminamos condiciones de carrera durante picos de tráfico y obtuvimos un mecanismo de carta muerta no volátil. Aprovechamos Inngest por sus capacidades de orquestación de flujo de trabajo y depuración, mientras confiamos en primitivas AWS para máximo rendimiento de mensajes y durabilidad. El sistema resultante no solo es escalable sino altamente auditable, traduciendo con éxito trabajos backend complejos y de larga duración en micro-pasos seguros, observables y tolerantes a fallos.
Building Spotify for Sermons. fue publicado originalmente en Coinmonks en Medium, donde las personas están continuando la conversación destacando y respondiendo a esta historia.
