Дайджесты новостей
Концептуальная иллюстрация ускорения CI в Linear: параллельный поток сборки преодолевает лавину автотестов от AI-агентов.

Как Linear ускорил CI вдвое при четырехкратном росте автотестов от AI-агентов

Внедрение автономных AI-агентов для генерации кода часто воспринимается как чистая победа продуктивности: фичи создаются быстрее, покрытие тестами растет на глазах, а рутина перекладывается на плечи нейросетей. Однако у этой медали обнаружилась суровая инфраструктурная изнанка. В компании Linear активное использование кодинг-ассистентов привело к тому, что кодовая база начала пополняться двумя тысячами новых автоматических тестов каждую неделю. С начала года объем тестового набора увеличился в четыре раза, а общее время выполнения проверок в CI стремительно приближалось к критической отметке в 11 минут.

Для сервиса, где непрерывный деплой и быстрая очередь слияния (merge queue) являются основой инженерной культуры, такая задержка означала паралич релизного цикла. Инженерная команда Linear провела масштабную ревизию всего конвейера автоматизации. В результате серии системных оптимизаций общее время прогона CI удалось сократить вдвое, попутно сэкономив десятки тысяч раннер-минут в месяц.

Кризис масштабирования: когда тесты пишутся быстрее, чем выполняются

Главная особенность кода, сгенерированного AI-агентами, заключается в его плотности. Модели добросовестно покрывают интеграционными и модульными проверками даже мелкие ветвления логики. Монорепозиторий Linear на TypeScript под управлением менеджера пакетов pnpm workspaces стал жертвой собственного успеха:

  • Количество тестов на бэкенде перешагнуло за десятки тысяч.
  • Очередь слияния коммитов начала растягиваться, блокируя релизы инженеров.
  • Стандартные облачные раннеры GitHub Actions исчерпали запас производительности процессора.

Команда поняла: косметическими правками проблему не решить. Требовалось пересмотреть каждый шаг конвейера — от клонирования репозитория до внутреннего устройства тестового рантайма.

Уровень 1: смена кремния и переход на нативный компилятор tsgo

Первым шагом стала модернизация вычислительных мощностей. Переход со стандартных виртуальных машин GitHub Actions на выделенные раннеры с мощными современными процессорами сразу дал прирост скорости в 34% на процессорных задачах.

Однако настоящим прорывом на этапе проверки типов стала замена стандартного компилятора TypeScript (tsc) на tsgo — высокопроизводительный нативный порт тайпчекера, написанный на языке Go. Компиляция и проверка контрактов типов в огромном монорепозитории занимали львиную долю времени. После внедрения tsgo время статической проверки типов сократилось на 73%, превратив многоминутное ожидание в считанные секунды.

Уровень 2: отказ от типов в линтинге ради чистого синтаксического анализа

Долгое время в проекте использовались строгие правила ESLint, зависящие от полного графа типов проекта (@typescript-eslint/parser с флагом project). Хотя проверка типов на уровне линтера защищает от некоторых архитектурных неточностей, цена этого решения оказалась колоссальной: линтер фактически заново строил семантический граф всего монорепозитория.

Инженеры Linear переписали ключевые кастомные правила линтинга, переведя их на анализ чистого абстрактного синтаксического дерева (AST) без обращения к типам. Это решение снизило время линтинга API на 68%, а суммарную проверку всего монорепозитория ускорило на 55%. Кроме того, отказ от связывания с типами открыл прямую дорогу к миграции на сверхбыстрый линтер Oxlint, написанный на Rust.

Уровень 3: хирургия критического пути и парадокс кэширования

При профилировании конвейера выяснилось, что до запуска первой строчки тестов раннер тратил почти две минуты на рутинную подготовку окружения. Команда устранила потери на каждом миллиметре критического пути:

Техническая схема оптимизации CI в Linear: сравнение медленного кэширования с быстрой установкой и поверхностным клонированием репозитория.

  1. Оптимизация чекаута кода: Замена стандартного шага actions/checkout на собственный композитный скрипт с поверхностным клонированием без блобов (--filter=blob:none --depth=1) сократила время получения репозитория с 94 до 20 секунд. Дополнительно были выставлены агрессивные таймауты на сетевой поток, спасающие раннеры от зависания при медленном канале:
# Кастомный чекаут: поверхностное клонирование без тяжелых блобов истории
export GIT_HTTP_LOW_SPEED_LIMIT=1000
export GIT_HTTP_LOW_SPEED_TIME=30

git clone --filter=blob:none --no-checkout --depth=1 https://github.com/linear/linear.git .
git checkout $GITHUB_SHA
  1. Отказ от кэширования node_modules: Один из самых контринтуитивных выводов аудита. Распаковка архива кэша зависимостей из облачного хранилища занимала 28 секунд. При этом чистая установка через pnpm на быстром SSD занимает всего 7.5 секунд! Кэширование папки node_modules было полностью удалено.
  2. Фильтрованная установка зависимостей: Для прогона бэкенд-тестов раннеру не нужны пакеты клиентских приложений. Использование фильтра pnpm сократило установку с 44–73 секунд до 16–18 секунд:
# Установка зависимостей только для пакета API и его прямых внутренних связей
pnpm --filter=@linear/api... install --frozen-lockfile
  1. Мгновенный подъем базы данных: Вместо последовательного выполнения всех миграций схемы PostgreSQL с нуля (12 секунд на каждый шард), в тестовый контейнер загружается готовый скомпилированный SQL-дамп структуры, занимающий 1–2 секунды.

Уровень 4: параллелизм Vitest и отключение изоляции модулей

Серьезнейший резерв ускорения крылся в настройке самого тестового раннера Vitest. Изначально тестовый набор был разбит на 4 шарда, а самый медленный поток выполнялся 379 секунд (более 6 минут).

Команда увеличила количество шардов до 8, предварительно разбив самые тяжелые монолитные файлы тестов на более компактные сьюты. Но главным архитектурным решением стало отключение изоляции модулей (isolate: false) для надежных модульных проверок.

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

// vitest.config.ts: оптимизация изоляции модулей для быстрых тестов
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    // Включение параллельного исполнения без избыточной очистки окружения
    isolate: false,
    pool: 'threads',
    poolOptions: {
      threads: {
        singleThread: false,
        useAtomics: true
      }
    },
    // Изоляция сохраняется только для интеграционных тестов с мутациями глобального состояния
    exclude: [
      '**/node_modules/**',
      '**/integration-stateful/**'
    ]
  }
});

Это изменение сэкономило еще 17% суммарного времени, а время прогона самого медленного шарда сократилось с 379 до 195 секунд.

Результаты и практические выводы

Комплексный подход позволил Linear не просто вернуть контроль над CI, но и сформировать запас прочности для дальнейшего роста автотестов. Совокупный результат оптимизации:

  • Время прогона полного набора проверок сократилось в 2 раза.
  • Самый медленный шаг упал с 6.5 до 3.2 минут.
  • Объединение 7 мелких проверочных задач в 2 параллельных потока сэкономило 87 000 раннер-минут в месяц.

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