Планування кількох програм одним кліком — Подолання 15-хвилинного ліміту AWS Lambda
Оператор FAST каналу планує місяць програм і один раз натискає Deploy. За цим одним кліком сотні програм перетворюються на тисячі дій розкладу MediaLive, які мають послідовно потрапити в AWS, всередині п'ятнадцятихвилинного ліміту виконання Lambda — і будь-яка помилка посередині залишає «живий» канал з прогалинами. Ось як ми зробили розгортання великих плейлистів одним кліком надійним, і чому наступним кроком є інше середовище виконання, а не більша Lambda.
Швидкий огляд
| Аспект | Деталі |
|---|---|
| Середовище виконання | AWS Lambda (одноразовий виклик), тайм-аут бекенда 615 с |
| Ціль виводу | MediaLive BatchUpdateScheduleCommand |
| Пакетна обробка | До 200 дій MediaLive за запит — ~25 програм на практиці |
| Дії на програму | ~7–8 (перемикання входу + 4 водяні знаки для кожного відтворення + 2 SCTE-35), більше з рекламними паузами |
| Відновлення | Повторна спроба для кожної програми у разі відхилення пакета |
| Спостережуване | 195 програм розгорнуто приблизно за 44 секунди (локальне вимірювання) |
| Задокументована ціль | 360 програм за ≤90 секунд |
| Шлях масштабування | Розбиття на частини за допомогою Step Functions для плейлистів тривалістю >1 місяця — заплановано, не випущено |
Виклик
Розгортання плейлиста — це не запис, це оркестровка. Для кожної програми, запланованої оператором, MediaLive потрібно точно знати, коли перемикати входи, коли вмикати водяний знак для кожного відтворення, коли вставляти контрольні точки SCTE-35 і коли врізати рекламу. Структурні обмеження:
- Кількість дій є множинною, а не адитивною. Кожна програма генерує ~7–8 дій до рекламних пауз — одне перемикання входу, чотири активації водяних знаків (по одному на відтворення, оскільки StaticImageOutputActivate використовує координати вихідного пікселя) і два маркери SCTE-35. Рекламні паузи додають по три додаткові дії кожна. Місяць на 360 програм — це приблизно 2800 дій у мережі.
- MediaLive забезпечує послідовність для кожного каналу. Дії розкладу прив'язані до часу та посилаються одна на одну; ви не можете паралелізувати записи до одного каналу без того, щоб API відхилив їх як конфліктні.
- AWS Lambda має жорсткий 15-хвилинний ліміт. Це не м'яке обмеження, не конфігурація. Оркестратор повинен завершити роботу в межах цього ліміту, інакше канал буде розгорнуто лише частково.
- Часткова відмова є операційно неприпустимою. Якщо програма 174 з 360 зазнає невдачі та перериває виконання, оператор не має змоги побачити різницю між тим, що було розгорнуто, а що ні. Канал запускається з прогалинами; глядачі бачать заставку замість очікуваного контенту.
- Перша версія надсилала один BatchUpdateSchedule на програму з затримкою 200 мс між програмами. Це приблизно 2,5 секунди реального часу на програму. При 360 програмах ви вже перевищуєте ліміт Lambda до того, як MediaLive виконає будь-яку реальну роботу.
Отже, завдання полягає не в тому, щоб писати швидше. А в тому, щоб писати менше разів, витримувати часткові збої і залишатися в межах одного виклику — не втрачаючи гарантій послідовності, які вимагає MediaLive.
Чому існуючі підходи зазнають невдачі
Очевидні виходи з положення всі зазнають невдачі з структурних причин, а не через проблеми з налаштуванням.
- "Просто збільште тайм-аут Lambda." Ви не можете. П'ятнадцять хвилин — це жорстке обмеження виконання Lambda, встановлене AWS; це не ручка в консолі. Навіть якби це було так, вартість на програму зростає з каталогом — придбання додаткового «стінного» часу лише відкладає наступну перешкоду.
- "Перейти на ECS Fargate або EC2." Ми розглянули це і відхилили. Довготривалі контейнери означають, що ми самі відповідаємо за середовище виконання: перевірки стану, автомасштабування, компроміси між холодним стартом і теплим пулом, IAM-обмеження та чергування для сервісу, який працює з періодичними сплесками. Lambda забезпечує нам ізоляцію для кожного виклику та нульову вартість простою для навантаження, яке за своєю природою є періодичним. Ми не були готові відмовитися від цього, щоб виправити одне вузьке місце.
- "Паралелізувати записи MediaLive." MediaLive серіалізує оновлення розкладу для кожного каналу. Паралельні BatchUpdateSchedule виклики до одного й того ж каналу конкурують на шкалі часу дій і відхиляються. Єдина легітимна паралельність можлива в межах пакета, а не між пакетами.
- "Просто дозвольте йому впасти, а оператор нехай спробує знову." Це найгірший варіант. Коли розгортання переривається на програмі N, канал знаходиться в стані, який ніхто не може описати з UI. Оператори не отримують різницю; вони отримують чорний ящик і «живий» канал з прогалинами. Система повинна або розгорнути все, або розгорнути часткові результати зі статусом для кожної програми, на який оператор може реагувати.
Важелем, який у нас залишився, була сама форма роботи: менше, більших записів, що виконуються послідовно, з механізмом відновлення, який знижує гранулярність до рівня рядків лише тоді, коли пакет відхиляється.
Наше рішення
Винести витрати з Lambda до початку циклу, об'єднати записи MediaLive всередині циклу та перейти до подання на програму лише у разі збою пакета. Три ідеї, у такому порядку — і свідоме рішення, що 15-хвилинний ліміт є прийнятним для розмірів каталогів, які ми фактично розгортаємо сьогодні. Коли плейлисти перевищують один виклик, відповідь полягає не в більшій Lambda; це інше середовище виконання.

Архітектура
- Бекенд NestJS (schedule.service.ts) — попередньо «розігріває» розгортання: один обхід Mongo для всіх унікальних відео, один для всіх рекламних маркерів, потім паралельне виконання ffprobe для унікальних URL-адрес відео з кешованими результатами для циклу збагачення.
- Оркестратор Lambda (fastChannel-lambda-fun/index.js) — відповідає за цикл пакетної обробки, дроселювання для кожного пакета, резервний варіант для кожної програми та очищення «сирітських» записів.
- MediaLive BatchUpdateScheduleCommand — єдина поверхня для запису. Кожна дія — перемикання входу, водяний знак, SCTE-35, врізка реклами — проходить через неї.
- MongoDB — джерело істини для програм, відео та рекламних маркерів; ніколи не запитується всередині внутрішнього циклу.
- scheduleResults map — статус scheduled / error для кожної програми, що повертається бекенду, щоб оператор отримував різницю, а не трасування стека.
Ключові інженерні рішення
1. Попередньо «розігріти» повільні елементи до початку циклу. Оригінальний код виконував пошук в Mongo для кожного розкладу та виклик ffprobe для кожного розкладу всередині циклу збагачення — класичний N+1, оплачений двічі. Поточний бекенд масово вибирає кожне унікальне відео та рекламний маркер за один «раунд тріп» кожен, потім паралельно запускає ffprobe для унікальних URL-адрес і зберігає результат у кеші роздільної здатності. Внутрішній цикл стає кеш-хітом. Затримка ffprobe оплачується один раз за унікальну URL-адресу, а не послідовно в циклі.
2. Об'єднати записи MediaLive у пакети по ~25. Всередині оркестратора дії накопичуються в одному BatchUpdateScheduleCommand до тих пір, поки не буде 200 дій у черзі або не буде досягнуто останньої програми. Оскільки кожна програма генерує ~7–8 дій, пакети природно містять близько 25 програм кожен. Обмеження в 200 дій було обрано консервативно, щоб залишатися значно нижче лімітів корисного навантаження MediaLive на запит і зменшити ймовірність відхилення пакета через розмір — досить велике, щоб амортизувати мережеві витрати, досить мале, щоб зробити резервний варіант для кожної програми (наступне рішення) дешевим, коли його потрібно запустити. Одна мережева операція замінює двадцять п'ять. Дроселювання в 200 мс, яке раніше було між кожною програмою, тепер знаходиться між кожним пакетом.
3. Пакетна обробка для швидкості, резервний варіант для коректності. Об'єднання безпечне лише в тому випадку, якщо одна «погана» програма не «отруює» інші двадцять чотири в своєму пакеті. Коли submitProgramBatch видає помилку, блок `catch` викликає retryBatchAsIndividuals, який повторно надсилає кожну програму з невдалого пакета як окремий BatchUpdateScheduleCommand, записує status: 'scheduled' або status: 'error' для кожної програми, робить паузу 200 мс між спробами та перезакріплює часову шкалу дій після кожного часткового успіху. Швидкий шлях є пакетним. Шлях відновлення є гранулярним. Оператор отримує різницю для кожної програми в будь-якому випадку.
4. Видаляти «сирітські» записи наприкінці, а не запобігати їх появі під час виконання. sweepIncompleteProgramGroups запускається один раз наприкінці розгортання та видаляє будь-яку групу дій, яка не досягла чистого кінцевого стану. Ми свідомо не намагаємося підтримувати внутрішню послідовність каналу під час циклу — це означало б шлях відкату, який сам мав би вписатися в 15-хвилинний бюджет. Очищення — це єдине «підмітання», а не транзакція.
5. Не збільшувати Lambda; замінити її, коли зросте каталог. Для всього, що ми розгортаємо сьогодні, шлях з одним викликом завершується значно раніше бюджету. Справжнє масштабування — це розбиття на частини за допомогою Step Functions — розділити плейлист, запустити частини як паралельні скінченні автомати, зібрати знову. Це шлях для плейлистів тривалістю понад 1 місяць та 6 місяців. Це спроектовано, але не розгорнуто. Називати це наступним кроком корисніше, ніж вдавати, що воно вже працює.
Результати
- Під час внутрішнього тестування розгортання 195 програм завершилося приблизно за 44 секунди — порівняно з базовим показником у кілька хвилин для однієї програми за раз.
- Задокументована ціль для плейлиста на 360 програм (~1 місяць) становить ≤90 секунд, що зручно вкладається в 615-секундний тайм-аут бекенда та 15-хвилинний ліміт Lambda.
- Одна «погана» програма в пакеті більше не перериває інші 24. Оператор отримує карту статусу для кожної програми після кожного розгортання.
- Повільні частини запиту — пошук у Mongo та ffprobe — оплачуються один раз за унікальний ресурс, а не один раз за кожний запис у розкладі.
- 15-хвилинний ліміт перестав бути обмежувальним фактором для розмірів каталогів, які ми фактично відправляємо. Коли він знову ним стане, рішенням буде розбиття на частини за допомогою Step Functions, а не більша Lambda.
Технологічний стек: AWS Lambda · AWS MediaLive · NestJS · MongoDB · Node 18 · ffprobe · TypeScript · AWS SDK v3

