惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tailwind CSS Blog
博客园 - Franky
博客园 - 司徒正美
Stack Overflow Blog
Stack Overflow Blog
GbyAI
GbyAI
Y
Y Combinator Blog
Microsoft Azure Blog
Microsoft Azure Blog
Blog — PlanetScale
Blog — PlanetScale
博客园 - 叶小钗
罗磊的独立博客
The GitHub Blog
The GitHub Blog
H
Help Net Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Engineering at Meta
Engineering at Meta
Martin Fowler
Martin Fowler
Spread Privacy
Spread Privacy
Google DeepMind News
Google DeepMind News
AWS News Blog
AWS News Blog
N
Netflix TechBlog - Medium
T
The Exploit Database - CXSecurity.com
云风的 BLOG
云风的 BLOG
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
Cyberwarzone
Cyberwarzone
T
Threatpost
T
Threat Research - Cisco Blogs
Recent Announcements
Recent Announcements
T
Tor Project blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Last Week in AI
Last Week in AI
L
Lohrmann on Cybersecurity
量子位
V
V2EX
V
Vulnerabilities – Threatpost
L
LINUX DO - 热门话题
www.infosecurity-magazine.com
www.infosecurity-magazine.com
P
Proofpoint News Feed
I
Intezer
Schneier on Security
Schneier on Security
雷峰网
雷峰网
B
Blog RSS Feed
Jina AI
Jina AI
爱范儿
爱范儿
Attack and Defense Labs
Attack and Defense Labs
Google DeepMind News
Google DeepMind News
M
MIT News - Artificial intelligence
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Palo Alto Networks Blog
美团技术团队
博客园 - 【当耐特】

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Реально большая стейт-машина: как мы строили облачную запись и ИИ-конспектирование в Телемосте
ilyagrig2000 · 2026-05-07 · via Все публикации подряд на Хабре

Реально большая стейт-машина: как мы строили облачную запись и ИИ-конспектирование в Телемосте

Время на прочтение11 мин

Охват и читатели12K

Всем привет! Меня зовут Илья Григорьев, я старший бэкенд-разработчик в команде Телемоста. В этой статье я разберу наш опыт разработки двух фич последнего года — ИИ-конспект с Алисой Про и облачной записи на Диск. Покажу, как мы проектировали их архитектуру, почему не всё получилось с первого раза, с какими системными и техническими ограничениями столкнулись при работе с медиаданными и как в итоге выстроили пайплайн их обработки и анализа.

Идея этих фич возникла из практической потребности: иногда встречи накладываются друг на друга или не получается подключиться вовремя, и встречу приходится пропускать. Чтобы не упускать важное, мы создали возможность быстро восстановить контекст. При этом Телемостом ежемесячно пользуются более 8,3 млн человек, поэтому любые решения нужно было сразу проектировать с учётом высокой нагрузки и стабильности. 

Подход к реализации конспектирования и облачной записи схожий, поэтому мы разрабатывали их параллельно. Сначала во время активной фазы конференции аудио- и видеопотоки участников сохраняются в облачное хранилище. Затем запускаются «подкапотные» механизмы агрегации и анализа медиаданных. 

В финале пользователи получают готовый артефакт: 

  • для конспектирования это текстовый отчёт, который приходит на почту всем участникам встречи. 

  • для облачной записи — письмо создателю встречи со ссылками на видео- и аудиофайлы. Запись полностью дублирует происходившее на конференции, хранится на Яндекс Диске и воспроизводится прямо в браузере, без необходимости что-то скачивать.

Эволюция архитектуры: от «спагетти» к стейт-машинам

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

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

Именно поэтому мы остановились на стейт-машинах — подходе, в котором у нашей команды накоплена большая экспертиза (советую посмотреть доклады ребят на Joker). Стейт-машина даёт наглядную модель вместо запутанных условий и, что важнее всего, позволяет гарантированно возобновлять работу с последнего зафиксированного состояния. После каждого успешного перехода мы сохраняем прогресс в базу — и весь конвейер обработки становится надёжным.

Схема стейт-машины конспектирования
Схема стейт-машины облачной записи

Проблема ожидания внешних событий

Стейт-машина решает проблему надёжности, но сразу ставит новый вопрос: что делать, когда нужно дождаться длительной внешней операции? Типичный пример — распознавание речи. Мы отправляем аудиодорожку в сервис ASR (Automatic Speech Recognition), получаем в ответ task_id и вынуждены периодически опрашивать сервис, готов ли результат. Этот процесс может занимать от одной до десяти минут, и всё это время стейт-машина фактически простаивает.

Чтобы не изобретать велосипед в каждом стейте, мы выделили два подхода: 

  1. Активное ожидание (polling): стейт-машина остаётся в текущем состоянии и через определённые интервалы «просыпается», чтобы проверить статус задачи по API. Просто в реализации, но при большом количестве параллельных процессов создаёт лишнюю нагрузку на базу данных и планировщик.

  2. Регистрация внешних событий: система переходит в состояние ожидания и «засыпает» до момента, пока внешний сервис сам не пришлёт уведомление (webhook) или пока не наступит нужное событие в нашей инфраструктуре.

Ситуацию осложняло то, что таких внешних зависимостей у нас много: нужно дождаться не только распознавания речи, но и обработки видео, генерации саммари нейросетью и успешной загрузки тяжёлых файлов на Яндекс Диск. Чтобы управлять этим хаосом мы внедрили событийную модель — механизм, который позволяет стейт-машине элегантно вставать на паузу и возобновляться ровно в тот момент, когда внешняя система подтверждает готовность данных.

Проектирование механизма ожидания

Как реализовать ожидание внешнего события? Самый очевидный путь — создать в текущем стейте отдельный поток (alarm thread), который через wait/notify будет периодически просыпаться, проверять статус задачи и снова засыпать. 

Но для высоконагруженного сервиса это тупиковый путь по двум причинам: 

  1. Во-первых, исчерпание квот: пока поток спит внутри стейта, стейт-машина считается активной. У нас есть жёсткие лимиты на количество одновременно выполняющихся машин, и такие «спящие» процессы быстро съели бы всю квоту, блокируя обработку других встреч. 

  2. Во-вторых, отсутствие отказоустойчивости: выполнение жёстко привязывается к конкретному инстансу бэкенда. Если инстанс перезагрузится или упадёт, состояние ожидания будет потеряно.

Чтобы обойти эти ограничения, мы внедрили новый тип состояния — Waiting State. Его ключевая идея: между проверками стейт-машина вообще не запускается и не занимает ресурсы бэкенда. Для каждого такого стейта задаётся интервал (Try Interval) — период, раз в который система проверяет наступление события. А поскольку состояние сохраняется в базе, следующую проверку может выполнить любой свободный инстанс приложения — никакой привязки к конкретной машине.

Отдельно предусмотрен контроль времени ожидания: если внешний сервис, например ASR, «залипает» в статусе in progress, процесс не зависнет навсегда. Для каждого Waiting State задаётся жёсткий тайм-аут, по истечении которого система генерирует алерт для оперативного вмешательства. Итоговая логика проста: если событие наступило — стейт-машина переходит к следующему шагу, если нет — выполнение откладывается до следующей итерации.

Автоматизация ретраев и скрытие сложности

@Override
public CloudRecordingState nextState(CloudRecordingStateTransitionContext ctx) {
    CloudRecordingState nextState = nextStateFunction.apply(ctx);
    int attempt = ctx.getCloudRecordingDtoOnStart().getProcessAttempt();

    if (ctx.err() && attempt + 1 >= propertyManager.getCloudRecordingStateProcessorRetryMaxCount()) {
        return failureActions == FailureActions.FINISH_AND_STALLED ? CLOUD_RECORDING_FINISHED : nextState;
    } else if (ctx.err()) {
        return ctx.getStateOnStart();
    } else {
        return nextState;
    }
}

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

Главная трудность была в том, чтобы сделать это красиво и переиспользуемо, не загромождая бизнес-логику самих стейтов. Мы скрыли всю механику в абстрактных классах. Логика выбора следующего шага теперь сводится к трём случаям: если ошибка есть и лимит попыток исчерпан — переходим в терминальный стейт (Error); если ошибка есть, но попытки остались — возвращаем state_on_start и перезапускаем текущий стейт; если всё прошло успешно — штатно идём дальше. Номер попытки сохраняется в базе и обнуляется только при успешном выходе из стейта.

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

Когда архитектурные вопросы были решены, мы запустили тестовую облачную запись — и получили шокирующий результат: видеозапись часовой встречи подготавливалась целых 10 часов.

Для сервиса такого уровня это, разумеется, неприемлемо. Узкое место нашлось быстро — этап сборки видеофайла. Но прежде чем рассказывать, как мы его ускорили, нужно немного погрузиться в то, как вообще устроена работа с медиаданными в Телемосте.

Структура исходных данных и медиапотоки

Чтобы понять, где возникли задержки, нужно разобраться, в каком виде данные вообще поступают на вход. Исходная информация по каждому пользователю сохраняется в облачное хранилище в виде аудио- и видеочанков — небольших фрагментов в среднем по 30 секунд, из которых формируются треки.

Со звуком всё относительно просто: последовательность чанков образует аудиотрек — голос участника. Но у одного пользователя таких треков может быть несколько: основной микрофон и Display Audio (звук при демонстрации экрана). При этом между чанками могут быть пропуски.

С видео сложнее. Мы оперируем треками разных типов и качества: поток с камеры в низком разрешении (layer: low), поток в высоком качестве и отдельный трек с шаринга экрана в Ultra. Всё это нужно корректно свести воедино.

FFmpeg и CPU-intensive задачи

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

Для всех манипуляций с медиа мы используем FFmpeg — индустриальный стандарт для записи, конвертации и стриминга аудио и видео. Типичная команда в нашем конвейере выглядит примерно так:

ffmpeg -i input.webm -vf "scale=1920:1080" -c:v libvpx -b:v 2M output.webm

Здесь мы берём исходный файл, приводим его к разрешению FullHD, кодируем с помощью libvpx с битрейтом 2 Мбит/с и сохраняем результат.

Но при интеграции FFmpeg в наш стек обнаружилась фундаментальная проблема: у этой библиотеки нет удобного нативного SDK для Java. Это заставило нас искать обходные пути для управления процессами обработки.

Работа с FFmpeg в Java: обертки и процессы

Поскольку у FFmpeg нет нативной библиотеки для Java, основным способом взаимодействия остаётся CLI. Чтобы не городить громоздкий код при каждом вызове, мы инкапсулировали логику формирования команд в класс FFmpegCommand. Внутри него хранится всё необходимое: путь до бинарника FFmpeg в системе, конфигурация входных файлов с флагами преобразования (кодеки, битрейт, разрешение) и путь для сохранения результата.

Метод toCommandString собирает эти данные в строковую команду, которую мы передаём в CommandExecutor. Тот через ShellUtils запускает процесс в операционной системе, дожидается его завершения и обрабатывает результат: если FFmpeg возвращает ошибку — выбрасываем исключение, если всё прошло успешно — получаем stdout.

public class FFmpegCommand implements Command {

    private final String executablePath;

    @Singular
    private final List<FFmpegParam> parameters;

    @Singular
    private final List<String> outputParameters;

    private final String outputSource;

    @Override
    public String toCommandString() {
        String args = parameters.stream()
                .map(p -> p.name() + " \"" + p.value() + "\"")
                .collect(Collectors.joining(" "));
        String outputArgs = String.join(" ", outputParameters);
        return executablePath + " -y " + args + " " + outputArgs + " " + " \"" + outputSource + "\"";
    }
public class CommandExecutor {

    public String executeCommand(Command command) {
        return executeCommand(command.toCommandString(), command.includeStderrInOutput());
    }

    public String executeCommand(String command, boolean includeStderrInOutput) {
        log.info("Full command {}", command);
        long startTime = System.currentTimeMillis();
        ExecResult execResult = ShellUtils.executeGrabbingOutput(command);
        if (!execResult.isSuccess()) {
            throw new FFmpegExecutionException(
                    String.format("Command %s Failed: %s", command, execResult.getOutputJoined())
            );
        }
        log.info("Command {} took {} ms to complete. Output: {}",
                command, System.currentTimeMillis() - startTime, execResult.getOutputJoined());
        return includeStderrInOutput ? execResult.getOutput() : execResult.getStdout();
    }
}

Поиск узкого горлышка

Возвращаемся к главной проблеме — десятичасовой сборке часового видео. Анализ показал, что команды FFmpeg сами по себе крайне тяжёлые: энкодинг видео — классическая CPU-intensive задача. В первоначальной реализации все команды выполнялись строго последовательно, что и создавало колоссальные задержки.

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

Единственный рабочий вариант — распределять задачи между разными воркерами. Но это потребовало механизмов синхронизации через облачное хранилище: разные узлы системы должны уметь эффективно обмениваться результатами промежуточной обработки.

Оптимизация через MapReduce: слоты и сегменты

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

Мы выбрали длительность сегмента в 4–5 минут. Ключевая особенность: внутри такого интервала «сетка» — расположение участников на экране — статична. Никто не заходит в конференцию, не выходит, не включает и не выключает камеру. Это критически упрощает финальную сборку кадра. Поскольку сегменты независимы, их можно рендерить параллельно, а в конце просто склеить — в FFmpeg склейка готовых файлов является очень «дешёвой» и быстрой операцией.

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

В итоге процесс выглядит так: мы запускаем сотни независимых задач на сборку слотов, распределяем их по всему пулу воркеров, затем объединяем в сегменты — и наконец сводим в единый файл.

Изменения в стейт-машине

Чтобы поддержать эту логику, нам пришлось пересмотреть структуру стейтов.

Раньше процесс состоял всего из двух этапов: в первом стейте ставилась одна огромная задача на сборку всего видео, во втором (Waiting State) система просто ждала её завершения. Медленно и неэффективно.

Теперь стейтов стало пять, что позволило гибко управлять параллелизмом. В первом — Starting Slots Video Build — происходит основная магия планирования: вся видеозапись нарезается на временные сегменты, внутри каждого выделяются слоты по числу активных пользователей, и система сразу генерирует и ставит в очередь все задачи по сборке слотов для всех сегментов одновременно.

Завершение конвейера: сборка сегментов и медиа-комбайнер

После подготовки слотов стейт-машина переходит к этапу Starting Segments Video Build. Это типичный Waiting State: система периодически опрашивает базу, проверяя готовность всех слотов для конкретного сегмента. Как только пазл из пользовательских видео для пятиминутного отрезка собран, запускается задача на рендер самого сегмента.

Когда все сегменты готовы, в игру вступают финальные стадии — Starting Media Combiner и Waiting for Media Combiner. Здесь выполняется одна высокоуровневая задача: все видеосегменты склеиваются в единое полотно, аудиотреки всех пиров миксуются в итоговую аудиодорожку, она накладывается на видеоряд, и результат упаковывается в контейнер для загрузки на Яндекс Диск.

Проблема взрывного роста задач и 30-кратное ускорение

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

Чтобы разгрести этот завал, мы сделали два шага. 

  1. Масштабирование: расширили пул воркеров, подобрав оптимальное соотношение CPU и RAM. 

  2. Тонкая настройка FFmpeg: оптимизировали параметры энкодинга, чтобы снизить нагрузку на процессор без потери качества.

Результат превзошёл ожидания: подготовка часовой записи сократилась с 10 часов до 20 минут — ускорение в 30 раз.

На мониторинге разница «до» и «после» видна наглядно. Мы сравнивали два дня с одинаковой нагрузкой — зелёная область на графиках показывает активные записи в конференциях.

Данные с тестирования фичи

Данные с тестирования фичи

По постпроцессингу: в первый день синяя область (записи в обработке) росла как огромный горб — задачи копились и не успевали завершаться. Во второй день она стала едва заметной: данные обрабатываются практически в реальном времени. График очереди задач рассказывает ту же историю: гигантский наплыв первого дня во второй день исчез полностью.

Внедрение стейт-машин и парадигмы MapReduce превратило неповоротливый процесс в масштабируемую и быструю систему, готовую к нагрузкам Телемоста.

Итоги и выводы

Из этой истории я бы выделил три главных урока.

  1. Стейт-машины — спасение для сложных и долгих процессов. Когда у вас десятки интеграций с внешними сервисами и высокий риск инфраструктурных сбоев, линейный код не выдерживает критики. Стейт-машина гарантирует, что даже после череды ошибок процесс рестартует, а пользователь в итоге получит свой конспект или видеозапись.

  2. Не бойтесь кастомизировать инструменты. Мы не просто взяли готовую концепцию, а адаптировали её под свои нужды: внедрили Waiting State для экономии ресурсов и автоматизировали ретраи на уровне базовых классов. Система стала не только надёжной, но и удобной для переиспользования в новых проектах.

  3. Разделяй и властвуй. Если перед вами тяжёлая монолитная задача, которая выполняется часами — дробите её. Переход к модели «сегменты и слоты» позволил эффективно утилизировать ресурсы распределённого пула воркеров и ускорить обработку в 30 раз.

Проектируйте надёжно, оптимизируйте смело — и не бойтесь сложных вызовов.