Перейти к содержанию

Anthropic

Патчи в CI у Anthropic жили 70 дней, 29 дней и меньше суток

6 мин чтения

Текст написан ИИ-моделью по источникам, на которые ведут ссылки, и сверен с ними второй моделью. Как мы работаем

Изображение: claude.com

За полгода число задач CI в Anthropic выросло в 25 раз. Внутренний сервис отбора тестов трижды латали, прежде чем переписать с нуля, и каждая заплатка жила короче предыдущей: 70 дней, 29 дней, меньше суток.

Как это было, инженер Anthropic рассказал 14 сентября в блоге Claude. Сами приёмы, по его словам, обычные: машина побольше, параллельная обработка, перезапуск сервиса («да, этот до сих пор работает на удивление хорошо»). Важнее другое: каждый из них выигрывал лишь долю того времени, что год назад. Зато полная переделка стала в разы дешевле, раз написание кода перестало быть узким местом. Главный урок автор формулирует так: «всегда закладывайтесь на экспоненту».

Откуда 25x

Инженеры Anthropic за квартал отгружают в 8 раз больше кода, чем в 2021-2025 годах, и 80% этого кода пишет Claude. Обе цифры взяты из отчёта Anthropic Institute о рекурсивном самоулучшении, опубликованного 4 июня на внутренних инженерных метриках. Claude к тому же всё активнее ревьюит и одобряет PR, так что ускорилось и ревью. Нагрузка дошла до CI.

Тестов в кодовой базе за тот же период стало в 10 раз больше, инженеров добавилось немного. Задачи CI выросли сильнее тестов, хотя на каждом PR запускается далеко не каждый тест. Именно этим отбором и занимается сервис, который пришлось перестраивать.

Слушатель и селектор

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

В Anthropic построили детерминированный сервис test impact analysis. Он решает, какие тесты запускать на каждое изменение, по истории их прошлых результатов и по связи с затронутыми пакетами. Практика не редкая, и на рынке есть целая категория вендоров с такими продуктами. Сервис держится на двух компонентах, которые должны оставаться синхронными. Listener записывает результаты тестов из каждого прогона CI. Selector читает накопленную историю и решает, какие тесты запускать на открытых PR.

Когда каждую секунду идёт по несколько задач CI, listener начинает отставать от очереди PR. Двадцать минут такого отставания превращаются в десятки тысяч обновлений по тестам, которые не дошли до selector. Если в основную ветку влили плохое изменение, тест начинает падать у всех остальных, и несколько человек разбираются в одном и том же. Если начинает «флакать» зависимость (тесты на ней то проходят, то падают), ложные красные прогоны блокируют слияния. Починенный или новый тест не запустится, пока listener не догонит очередь, и вместе с этим растёт риск регрессии.

Весь сервис работал одним процессом. Чтобы вести историю по каждому тесту, результаты должен был применять единственный писатель, и такая архитектура v0 не давала шардировать сервис горизонтально.

Три заплатки

В октябре 2025 года сервис уже трещал, и команду два дня подряд будили пейджером. Первое решение было простым: удвоили число ядер, заранее понимая, что надолго не хватит. Хватило на 70 дней. Тренд был очевиден, а вот с ответственностью было мутно: «Никто не хотел брать на себя ещё один кусок инфраструктуры», а у команды CI были задачи поважнее.

Пейджер срабатывал всё чаще, и автор завёл долгую сессию во внутренней версии Claude Tag, того самого, который в Anthropic ведёт первую линию дежурства CI. Сессия следила только за этим сервисом: стоило отставанию listener превысить 50 000 задач, Claude писал автору и продолжал прежний разговор о следующих шагах. Так продолжалось месяцами, и контекст прошлых попыток напоминать не приходилось. «Claude часто настаивал на полной переделке, но обычно мы останавливались на очередной заплатке», признаётся автор.

В феврале экспоненциальный рост задач снова упёрся в сервис, и его распараллелили. Оказалось, что listener нужен не один писатель на всё, а один писатель на пакет: этого достаточно, чтобы результаты по каждому участку кодовой базы шли в правильном порядке. Claude сгенерировал код, который разложил состояние каждого пакета по отдельному шарду со своим воркером. Что решение временное, тоже понимали, но не ждали, что оно продержится всего 29 дней.

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

К тому же после каждого перезапуска сервис отставал всё сильнее. Несколько раз отставание превышало час, и тогда listener терял заметную часть результатов. Автор уточняет, что CI на этих PR всё равно отрабатывал и непротестированный код в продакшен не уходил. Но selector решал, какие тесты запускать, по устаревшим данным, и на деле это чаще всего означало прогон тестов, которые и так сильно флакали или падали повсюду.

Что поменяли при пересборке

В итоге команда последовала совету Claude и дала сервису отдельное хранилище, а именно in-memory хранилище данных. Основную работу, которую раньше держал в памяти единственный процесс, вынесли туда.

  • Состояние: в памяти одного процесса → в журнале во внешнем in-memory хранилище.
  • Обработка результатов: один писатель, после февральской заплатки по одному на пакет → любой воркер listener берёт любой результат, дописывает его в журнал и идёт дальше, ничего не держа в памяти.
  • Очередь необработанных событий: копилась почти каждый день и росла от недели к неделе → после переключения и настройки держится ровной.

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

Работа заняла три недели у одного инженера. Год назад, по оценке автора, на неё ушло бы ближе к кварталу, около тринадцати недель. Подбор размера журнала и числа воркеров Claude провёл по большей части сам, и с тех пор сервис стабилен.

Что автор сделал бы иначе

Число задач CI растёт экспоненциально по двум причинам: у каждого инженера становится больше агентов, а автоматическое одобрение PR становится изощрённее. Меняется и форма PR: Claude предпочитает делать их мельче и дробнее, и это ещё один аргумент против прогона всех тестов на каждом PR. Агенты пушат ночью и в выходные, поэтому нижний уровень активности вырос. Но нагрузка по-прежнему идёт всплесками, ведь значительную часть PR всё ещё ведут и одобряют люди.

Автор ожидает, что горизонтально масштабируемый отбор тестов станет отраслевым стандартом: команды с агентами производят и больше PR, и больше тестов. Его советы короткие. Инструментировать сервисы так, чтобы они служили Claude глазами и ушами: так он находит и чинит проблемы шаг за шагом намного быстрее, чем люди вручную. В первую очередь проверять, что задач CI на выходе столько же, сколько на входе. С самого начала держать состояние вне процесса. Не запускать критичный сервис единственным экземпляром, если нельзя измерить его самого и любые canary-изменения.

Понятие «переусложнения», считает он, понемногу уходит, по крайней мере планка сильно поднялась: если позволяет бюджет, уже в v0 можно закладывать масштаб в 10-20 раз больше ожидаемого. Строите вы сами или покупаете, совет один: «исходите из того, что в пределах двух кварталов ваша архитектура окажется под 25-кратной нагрузкой».

Пока никто не написал. Будьте первым.

Присоединяйтесь к разговору

Войдите, чтобы оставить комментарий. Он появится после модерации.