Cloudflare Workflows: когда разделять сценарии публикации по каналам

Практический разбор Cloudflare Workflows: когда каналу публикации нужен отдельный Dynamic Workflow, как перейти на V2 и где хранить ключ идемпотентности.

Friday, September 4, 2026Omid Saffari
Cloudflare Workflows: когда разделять сценарии публикации по каналам

Каждый день со стороны Anthropic у меня запускаются шесть ИИ-процессов публикации. Каждый отправляет POST в один статический PublishWorkflow с ключом publish-{brief_id}. 1 мая Cloudflare выпустила @cloudflare/dynamic-workflows, а ещё через несколько дней — Workflows V2 с пересобранной плоскостью управления, рассчитанной на нагрузку от агентов. Но самое интересное здесь — не релиз Cloudflare Workflows как таковой. Архитектурное решение с одним статическим workflow, которое я принял ради идемпотентности, теперь приходится осознанно защищать, а не просто принимать как унаследованное ограничение.

Чем меня удивил релиз Cloudflare Workflows

Я ждал анонса в духе «Workflows теперь масштабируется ещё лучше»: выше лимиты, глубже очередь — обычное ежегодное увеличение. Именно эта часть объявления оказалась менее значимой.

Главное — теперь код workflow может меняться для каждого тенанта прямо во время выполнения. @cloudflare/dynamic-workflows вышел 1 мая под лицензией MIT и построен на Dynamic Workers. Библиотека позволяет зарегистрировать один WorkflowEntrypoint, тело которого подтягивается из Dynamic Worker при создании экземпляра. В результате сам граф этапов подстраивается под тенанта, а не зашивается в развёртку. Cloudflare формулирует это как «отказоустойчивое выполнение, которое следует за тенантом». Если вам когда-нибудь приходилось прикручивать к одному workflow логику для отдельных клиентов через feature flags, вы знаете, насколько точно это сказано.

Мои шесть процессов публикации — news, dev, build, design, marketing, founders, business — по сути работают как шесть тенантов внутри одного PublishWorkflow. Конечно, это не клиенты, а ИИ-процессы на стороне Anthropic, каждый из которых готовит свой тип контента. Но мультитенантная модель сюда ложится идеально: общий каркас оркестрации, небольшие отличия для каждого канала, а запускается всё в одной учётной записи Cloudflare.

Через несколько дней вышел Workflows V2, и здесь важнее не цифры, а сама постановка задачи. V2 специально перепроектировали исходя из того, что экземпляры workflow создают агенты с машинной скоростью, а не люди, нажимающие кнопки. Моя схема запуска именно такая. Когда процесс публикации решает выпустить статью, нечеловеческое событие создаёт экземпляр с гарантированным выполнением. А плоскость управления V1 создавалась под другой профиль нагрузки.

Что это значит для фаундера без технического бэкграунда

Термин «отказоустойчивое выполнение» словно придуман, чтобы отпугнуть нетехнического читателя. На деле всё проще: многошаговая задача переживает сбой и продолжает с последнего успешно завершённого этапа, вместо того чтобы начинать заново. Если на этапе 5 из 8 не ответил API, система повторит только этап 5. Она не будет заново выполнять этапы с 1 по 4 и повторно выставлять за них счёт.

Для фаундера критично именно это: граница повтора каждого этапа одновременно служит границей расходов. Если конвейер вызывает платную ИИ-модель на этапах 3 и 6, а на этапе 7 падает, повторить нужно только этап 7, а не всю задачу. Неидемпотентный повтор платного конвейера означает не дубль строки в базе, а дубль счёта.

Вот решение «строить или отложить», которое можно одной фразой передать подрядчику: разделяйте workflow по продуктовым направлениям, только когда их логика действительно расходится, а не просто потому, что платформа теперь это позволяет. Cloudflare дала мощную новую абстракцию, и первый порыв — перепроектировать систему под неё. Но N определений workflow означают N-кратные затраты на сопровождение, а выигрыш появляется только тогда, когда каналы и правда выполняют разную работу. Фаундеру, услышавшему «теперь можно вынести каждое продуктовое направление в собственный workflow», стоит спросить: что изменилось в самой работе, а не в платформе?

Как устроена моя рабочая архитектура

Схема достаточно компактна, чтобы держать её в голове целиком. Каждый процесс публикации на стороне Anthropic завершает исследование, готовит бриф и отправляет POST на /api/admin/publish сайта. Эта конечная точка проверяет нагрузку, создаёт ID экземпляра и вызывает instances.create для единого WorkflowEntrypoint под названием PublishWorkflow.

Сам workflow состоит из восьми идемпотентных этапов step.do:

  1. validate (схема брифа, уникальность slug)
  2. ground (загрузка источников, разрешение ссылок)
  3. generate (вызов Claude для генерации текста)
  4. clean (проверка Markdown-директив)
  5. persist (запись в Postgres, версионирование)
  6. index (эмбеддинги, обновление поиска)
  7. cover (генерация изображения и загрузка в R2)
  8. publish (смена статуса, ping карты сайта)
TypeScript
export class PublishWorkflow extends WorkflowEntrypoint<Env, PublishParams> {
  async run(event: WorkflowEvent<PublishParams>, step: WorkflowStep) {
    const brief = await step.do("validate", () => validateBrief(event.payload));
    const grounded = await step.do("ground", () => groundCitations(brief));
    const draft = await step.do("generate", () => generateBody(grounded));
    const cleaned = await step.do("clean", () => validateDirectives(draft));
    const row = await step.do("persist", () => persistArticle(cleaned));
    await step.do("index", () => reindex(row.id));
    await step.do("cover", () => generateCover(row.id));
    await step.do("publish", () => flipStatus(row.id));
  }
}

Граница диспетчеризации выглядит так:

TypeScript
const id = `publish-${brief.brief_id}`;
try {
  await env.PUBLISH.create({ id, params: brief });
} catch (e) {
  if (isDuplicateIdError(e)) return new Response("already queued", { status: 200 });
  throw e;
}

instances.create выбрасывает ошибку при повторе ID. Эта одна строка несёт на себе критическую нагрузку: только она не даёт повторно сработавшему процессу Anthropic опубликовать одну статью дважды и дважды списать с меня стоимость внутренних вызовов ИИ.

Почему на шесть каналов приходится одно определение? Потому что все восемь этапов для них одинаковы. Меняется только редакционный пакет: требования к тону и аудитории, список антипаттернов и подсказки из брифа. Всё это приходит в конверте диспетчеризации как данные времени выполнения, а не как код. Имя канала определяет, какой пакет попадёт на этап 3 (generate); все остальные этапы от канала не зависят.

Единственная причина всерьёз рассматривать Dynamic Workflows — одному из каналов уже нужен другой граф этапов, а не просто другой пакет. Каналу с углублённым исследованием нужен дополнительный этап проверки источников до генерации и, возможно, фактчек после неё. Это уже структурное, а не информационное отличие. Сейчас оно оформлено как условная ветка внутри ground. Схема работает, но быстро начнёт гнить, если ещё три канала обзаведутся своими особыми ветками.

Во что обойдётся миграция Cloudflare Workflows

Вот что даёт createDynamicWorkflowEntrypoint. Код workflow для каждого канала загружается во время выполнения из Dynamic Worker, поэтому каналы, которые сегодня не запускались, почти ничего не стоят. Больше не нужно платить за сопровождение «всех каналов в одном пакете, постоянно», а код каждого канала можно развивать независимо, не переразвёртывая общий каркас.

Схема: HTTP-граница /publish владеет ключом идемпотентности publish-{brief_id} и служит шлюзом перед разветвлением на workflow по каналам
Ключ идемпотентности живёт на границе диспетчеризации, а не в коде отдельного канала.

Есть и честная цена, о которой в анонсе говорят меньше. Вместо одного типизированного WorkflowEntrypoint, где компилятор предупреждает о расхождении входного контракта этапа, появляются N загружаемых в рантайме определений без такой защиты. Расхождение контрактов между каналами из ошибки сборки превращается в ошибку времени выполнения. Если у вас шесть каналов, а этап persist в двух из них ждёт немного другую форму строки — потому что кто-то отрефакторил половину каналов и забыл остальные, — вы узнаете об этом не на CI, а при попытке опубликовать реальную статью.

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

  1. Оставить общий 8-этапный каркас статическим WorkflowEntrypoint. Это горячий путь. Пять из шести каналов проходят его без изменений.
  2. Собрать канал с углублённым исследованием как Dynamic Workflow со своим графом этапов: дополнительная проверка источников и фактчек.
  3. На границе /publish выбирать маршрут по каналу: однострочный switch по brief.lane решает, для какого binding вызвать create.

Числовое правило для решения: переводите канал на собственный Dynamic Workflow, только если его граф отличается от общего каркаса больше чем на один этап. До этого порога условная ветка в существующем этапе обходится в сопровождении дешевле второго определения workflow. После него ветвления начинают скрывать смысл работы канала, и отдельное определение начинает себя окупать.

Один канал выше порога, пять — ниже. Вот и весь план миграции в двух предложениях. Если бы подрядчик принёс мне тот же бриф, но предложил в первый же день разнести все шесть каналов по Dynamic Workflows, я бы этого не одобрил. Затраты на разработку реальны, а выигрыш сосредоточен ровно в одном канале.

Что Workflows V2 меняет для запусков с машинной скоростью

Ключевые цифры V2: 50,000 одновременных экземпляров и 2,000,000 экземпляров в очереди на один workflow вместо 1,000,000 в очереди V1. Это не маркетинговые цифры, но и к моим масштабам они не имеют никакого отношения. Шесть процессов, каждый из которых срабатывает раз или два в день, оставляют меня примерно на девять порядков ниже нового потолка.

Для моего стека важны не цифры, а то, что плоскость управления V2 с нуля строилась под запуск экземпляров агентами. V1 была оптимизирована под человеческий сценарий: пользователь нажимает кнопку, запускается экземпляр, а система переваривает всплески нагрузки, которые всё же ограничены числом кликов. V2 исходит из того, что запускает процесс, а не человек, и что темп задаёт пропускная способность вышестоящих агентов, а не клики в UI.

Детерминированное воспроизведение — ключевое свойство V2, которое стоит объяснить без жаргона. Каждый этап изолирован, воспроизводим и идемпотентен, а при повторе workflow продолжает с последнего успешного этапа. Ровно это свойство я вручную защищал на границе диспетчеризации с помощью ID экземпляра publish-{brief_id}. V1 уже давала повтор по этапам; V2 усиливает семантику воспроизведения, поэтому теперь платформа сама подкрепляет структурную гарантию, которую я берегу.

Практическое решение на эту неделю — не трогать горячий путь. На модель V2 переходят через повторную развёртку, и худшее, что можно сделать с рабочим идемпотентным конвейером, — в спешке перенести его на новую плоскость управления ради семантики, которая у него уже была. Канал с углублённым исследованием я переведу на V2, когда буду собирать его как Dynamic Workflow: новый код сразу писать под новую модель недорого. Остальные пять статических каналов останутся на месте, пока не появится причина менять их, кроме самого факта выхода новой версии.

Инвариант, от которого я не откажусь

ID экземпляра publish-{brief_id} — несущий элемент системы. Уберите его — и повторная попытка процесса Anthropic заново запустит конвейер, дважды опубликует одну и ту же статью и дважды спишет оплату за каждый платный API-вызов внутри workflow.

Dynamic Workflows сам по себе этому инварианту не угрожает: контракт ID в instances.create не изменился. Опасность в другом — в небрежном рефакторинге по каналам, при котором код канала сам начинает заново выводить ID. Например, какой-то канал решит добавить к ID метку времени «для надёжности». Такая подстраховка незаметно ломает дедупликацию: каждая повторная попытка теперь получает уникальный ID.

Вот правило, которое переживёт любое обновление версии и которое я бы написал на стене прежде, чем подпускать к коду кого-то ещё:

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

Что бы я сделал иначе, начиная с нуля в мае 2026 года

Если бы я собирал этот стек сегодня, а не наследовал год назад принятые решения, я бы изменил три вещи.

Во-первых, сразу взял бы общий статический каркас и один Dynamic Workflow для любого по-настоящему отличающегося канала, а не шесть разных определений. Новую абстракцию хочется применить повсюду, но налог на сопровождение N определений workflow приходит раньше, чем выигрыш от масштабирования. Шесть определений — это шесть мест для исправления бага, шесть мест для обновления зависимости и шесть мест, где может разъехаться контракт этапа. Один каркас и один выброс — минимально достаточное разделение.

Во-вторых, с первого дня разместил бы ключ идемпотентности на HTTP-границе. Добавлять publish-{brief_id} после инцидента с двойной публикацией дорого: нужно сверять дубли записей, возвращать затронутые расходы и инструментировать уже работающий в продакшене слой диспетчеризации. А паттерн try { create({id}) } catch (dup) {} до первого запуска конвейера занимает десять минут и закрывает целый класс сбоев.

В-третьих, считал бы детерминизм Workflows V2 базовым условием архитектуры, а не функцией, к которой можно перейти потом. Каждый этап нужно проектировать так, чтобы его можно было безопасно воспроизвести, даже если вы никогда не приблизитесь к потолку в 50k одновременных экземпляров. Безопасность повтора — не свойство масштаба, а свойство корректности. Небезопасный для повтора этап сломается при ретрае, а ретраи случаются в системе любого масштаба.

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

Нужны ли Dynamic Workflows, если логика всех тенантов одинакова?

Нет. Если граф этапов один и отличаются только данные, передавайте их в нагрузке диспетчеризации и сохраняйте один статический workflow. Dynamic Workflows оправдывают себя, когда для разных тенантов расходится сам код — то есть граф этапов, а не просто их параметры.

Сломает ли Workflows V2 существующие workflow на V1?

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

Какой теперь фактический лимит параллельного выполнения?

50,000 одновременных экземпляров и 2,000,000 экземпляров в очереди на один workflow вместо прежних 1,000,000 в очереди. Для большинства операторов это огромный запас; куда важнее в V2 детерминированное воспроизведение, а не новый потолок.

Готова ли @cloudflare/dynamic-workflows к продакшену или это preview-версия?

Библиотека вышла 1 мая 2026 года под лицензией MIT и построена на Dynamic Workers. Условием миграции должны быть зрелость библиотеки и ваш контракт идемпотентности, а не лицензия. Если слой диспетчеризации построен надёжно, библиотека будет готова раньше вашего плана миграции.

Когда фаундеру стоит платить инженеру за такую миграцию?

Когда логика автоматизации одного продуктового направления по-настоящему отличается от остальных: другой граф этапов, а не другие параметры. Делить только ради масштаба — преждевременно; разная логика — настоящий триггер. Если комане нужен именно такой архитектурный разбор, именно с этим я помогаю через DVNC.dev.

Последнее обновление

4 сент. 2026 г.

КатегорияBuild

Сделать этот сайт предпочтительным в Google

Добавить omidsaffari.com как предпочтительный источник в Google Поиске

Отметьте omidsaffari.com как предпочтительный источник — и Google будет поднимать его для вас в Top Stories, AI Overviews и AI Mode.

Ещё из Build

Все статьи Build
Рассылка

Одно письмо, каждое воскресенье. Работающие системы, а не горячие мнения.

Билд-логи, системы в продакшене и полевые заметки из портфеля ИИ-проектов.

Еженедельно. Без спама. Отписка в любой момент.