Programación de múltiples programas con un solo clic — Superando el límite de 15 minutos de AWS Lambda
Un operador de canales FAST programa un mes de contenido y pulsa Desplegar una vez. Detrás de ese único clic, cientos de programas se convierten en miles de acciones de programación de MediaLive que deben llegar a AWS en orden, todo dentro del límite de ejecución de quince minutos de Lambda — y cualquier fallo en el medio deja un canal en vivo con huecos. Aquí explicamos cómo hicimos que el despliegue con un solo clic de grandes listas de reproducción fuera fiable, y por qué el siguiente paso es un entorno de ejecución diferente, no una Lambda más grande.
Resumen rápido
| Aspecto | Detalle |
|---|---|
| Tiempo de ejecución | AWS Lambda (invocación única), tiempo de espera de backend de 615s |
| Objetivo de salida | MediaLive BatchUpdateScheduleCommand |
| Procesamiento por lotes | Hasta 200 acciones de MediaLive por solicitud — ~25 programas en la práctica |
| Acciones por programa | ~7–8 (cambio de entrada + 4 marcas de agua por renderización + 2 SCTE-35), más con pausas publicitarias |
| Recuperación | Reintento por programa en cualquier rechazo de lote |
| Observado | 195 programas desplegados en ~44 segundos (medición local) |
| Objetivo documentado | 360 programas en ≤90 segundos |
| Ruta de escalado horizontal | División de Step Functions para listas de reproducción de >1 mes — planificado, no implementado |
El Desafío
Desplegar una lista de reproducción no es una escritura — es una orquestación. Para cada programa programado por el operador, MediaLive necesita saber exactamente cuándo cambiar las entradas, cuándo activar la marca de agua para cada renderización, cuándo insertar puntos de referencia SCTE-35 y cuándo intercalar anuncios. Las presiones estructurales son:
- El recuento de acciones es multiplicativo, no aditivo. Cada programa emite ~7–8 acciones antes de las pausas publicitarias — un cambio de entrada, cuatro activaciones de marcas de agua (una por renderización porque StaticImageOutputActivate utiliza coordenadas de píxeles de salida), y dos marcadores SCTE-35. Las pausas publicitarias añaden tres acciones más cada una. Un mes de 360 programas son aproximadamente 2.800 acciones en la red.
- MediaLive impone un orden por canal. Las acciones de programación están ancladas en el tiempo y se referencian entre sí; no se pueden paralelizar las escrituras en el mismo canal sin que la API las rechace por conflicto.
- AWS Lambda tiene un límite estricto de 15 minutos. No es un límite flexible, no es una configuración. El orquestador debe terminar dentro de ese límite o el canal quedará desplegado a medias.
- El fallo parcial es operacionalmente inaceptable. Si el programa 174 de 360 falla y aborta la ejecución, el operador no tiene una vista de diferencias de lo que se desplegó y lo que no. El canal se emite con huecos; los espectadores ven una pantalla en blanco donde esperaban contenido.
- La primera versión enviaba un BatchUpdateSchedule por programa con una pausa de 200ms entre programas. Eso es ~2.5 segundos de tiempo real por programa. Con 360 programas, ya se supera el límite de Lambda antes de que MediaLive haya hecho cualquier trabajo real.
El objetivo, entonces, no es escribir más rápido. Es escribir menos veces, sobrevivir a fallos parciales y mantenerse dentro de una sola invocación — sin perder las garantías de ordenación que MediaLive exige.
Por qué fallan los enfoques existentes
Las soluciones obvias fallan por razones estructurales, no por problemas de ajuste.
- "Simplemente aumenta el tiempo de espera de Lambda." No se puede. Quince minutos es un límite estricto impuesto por AWS en la ejecución de Lambda; no es una opción configurable en la consola. Incluso si lo fuera, el costo por programa crece con el catálogo — comprar más tiempo real solo pospone el siguiente límite.
- "Muévete a ECS Fargate o EC2." Lo consideramos y lo rechazamos. Los contenedores de larga duración significan que somos dueños del entorno de ejecución: comprobaciones de salud, autoescalado, compensaciones entre arranque en frío y piscina caliente, alcance de IAM y rotación de guardias para un servicio que se ejecuta en ráfagas. Lambda nos da aislamiento por invocación y costo cero en inactividad para una carga de trabajo inherentemente intermitente. No estábamos listos para renunciar a eso para solucionar un cuello de botella.
- "Paraleliza las escrituras de MediaLive." MediaLive serializa las actualizaciones de programación por canal. Las llamadas concurrentes a BatchUpdateSchedule contra el mismo canal compiten en la línea de tiempo de acciones y son rechazadas. El único paralelismo legítimo es dentro de un lote, no entre lotes.
- "Deja que se caiga y que el operador lo reintente." Esta es la peor opción. Cuando un despliegue se aborta en el programa N, el canal está en un estado que nadie puede describir desde la UI. Los operadores no obtienen una diferencia; obtienen una caja negra y un canal en vivo con huecos. El sistema debe desplegar todo o desplegar resultados parciales con un estado por programa sobre el que el operador pueda actuar.
La palanca que nos quedaba era la forma del propio trabajo: menos escrituras, más grandes, ejecutadas en serie, con una recuperación que degrada a granularidad a nivel de fila solo cuando un lote es rechazado.
Nuestra Solución
Mover el costo fuera de Lambda antes de que comience el bucle, consolidar las escrituras de MediaLive dentro del bucle y degradar a la presentación por programa solo cuando un lote falla. Tres ideas, en ese orden — y una decisión deliberada de que el límite de 15 minutos está bien para los tamaños de catálogo que realmente desplegamos hoy. Cuando las listas de reproducción superen una sola invocación, la respuesta no es una Lambda más grande; es un entorno de ejecución diferente.

Arquitectura
- Backend NestJS (schedule.service.ts) — precalienta el despliegue: un viaje de ida y vuelta a Mongo para todos los videos únicos, uno para todos los marcadores de anuncios, luego ffprobe paralelo a través de URLs de video únicas con los resultados en caché para el bucle de enriquecimiento.
- Orquestador Lambda (fastChannel-lambda-fun/index.js) — posee el bucle de procesamiento por lotes, la limitación por lote, la recuperación por programa y el barrido de huérfanos.
- MediaLive BatchUpdateScheduleCommand — la única superficie de escritura. Cada acción — cambio de entrada, marca de agua, SCTE-35, inserción de anuncios — fluye a través de ella.
- MongoDB — la fuente de verdad para programas, videos y marcadores de anuncios; nunca consultada dentro del bucle interno.
- Mapa scheduleResults — estado scheduled / error por programa devuelto al backend para que el operador obtenga una diferencia, no un rastreo de pila.
Decisiones clave de ingeniería
1. Precalentar lo lento antes de que se ejecute el bucle. El código original realizaba una búsqueda en Mongo por cada programación y una llamada a ffprobe por cada programación dentro del bucle de enriquecimiento — un clásico N+1 pagado dos veces. El backend actual obtiene en lote cada video único y marcador de anuncio en un solo viaje de ida y vuelta cada uno, luego ejecuta ffprobe en paralelo a través de URLs únicas y guarda el resultado en una caché de resolución. El bucle interno se convierte en un acierto de caché. La latencia de ffprobe se paga una vez por URL única, no de forma serial en el bucle.
2. Consolidar las escrituras de MediaLive en lotes de ~25. Dentro del orquestador, las acciones se acumulan en un único BatchUpdateScheduleCommand hasta que se encolan 200 acciones o se alcanza el último programa. Dado que cada programa produce ~7–8 acciones, los lotes naturalmente se agrupan en alrededor de 25 programas cada uno. El límite de 200 acciones se eligió de forma conservadora para mantenerse muy por debajo de los límites de carga útil por solicitud de MediaLive y reducir la posibilidad de que un lote sea rechazado por tamaño — lo suficientemente grande para amortizar el costo de red, lo suficientemente pequeño para que la recuperación por programa (siguiente decisión) sea económica cuando tenga que ejecutarse. Un viaje de ida y vuelta de red reemplaza veinticinco. La limitación de 200ms que solía estar entre cada programa ahora está entre cada lote.
3. Procesamiento por lotes para velocidad, recuperación para corrección. La consolidación solo es segura si un solo programa erróneo no contamina a los otros veinticuatro de su lote. Cuando submitProgramBatch lanza una excepción, el bloque catch invoca retryBatchAsIndividuals, que reenvía cada programa del lote fallido como su propio BatchUpdateScheduleCommand, registra status: 'scheduled' o status: 'error' por programa, espera 200ms entre intentos y vuelve a anclar la línea de tiempo de acciones después de cada éxito parcial. La ruta rápida se procesa por lotes. La ruta de recuperación es granular. El operador obtiene una diferencia por programa de cualquier manera.
4. Barrer los huérfanos al final, no prevenirlos a mitad de vuelo. sweepIncompleteProgramGroups se ejecuta una vez al final del despliegue y elimina cualquier grupo de acciones que no haya alcanzado un estado terminal limpio. Deliberadamente no intentamos mantener el canal internamente consistente durante el bucle — eso significaría una ruta de retroceso que a su vez tendría que ajustarse al presupuesto de 15 minutos. La limpieza es un barrido único, no una transacción.
5. No expandir la Lambda; reemplazarla cuando el catálogo crezca. Para todo lo que desplegamos hoy, la ruta de invocación única termina bien dentro del presupuesto. La expansión horizontal honesta es la división de Step Functions — particionar la lista de reproducción, ejecutar los fragmentos como máquinas de estado paralelas, volver a ensamblar. Esa es la ruta para listas de reproducción de >1 mes y 6 meses. Está diseñada, no desplegada. Llamarlo el siguiente paso es más útil que pretender que ya está en funcionamiento.
Resultados
- En pruebas internas, un despliegue de 195 programas se completó en ~44 segundos — una reducción significativa respecto a la base de múltiples minutos, un programa a la vez.
- El objetivo documentado para una lista de reproducción de 360 programas (~1 mes) es de ≤90 segundos, cómodamente dentro del tiempo de espera de backend de 615 segundos y el límite de 15 minutos de Lambda.
- Un programa defectuoso en un lote ya no aborta los otros 24. El operador recibe un mapa de estado por programa de cada despliegue.
- Las partes lentas de la solicitud — búsquedas en Mongo y ffprobe — se pagan una vez por recurso único, no una vez por entrada de programación.
- El límite de 15 minutos dejó de ser el factor limitante para los tamaños de catálogo que realmente enviamos. Cuando vuelva a serlo, la división de Step Functions es la respuesta, no una Lambda más grande.
Pila Tecnológica: AWS Lambda · AWS MediaLive · NestJS · MongoDB · Node 18 · ffprobe · TypeScript · AWS SDK v3

