Learn (Almost) Anything
КаталогСкачать курс

Автоматизация в GitHub

Практики ускорения: правильный cache, matrix pruning, concurrency cancellation, incremental builds, artifact handoff, split jobs и анализ job execution time.

ruЭнциклопедия33 урока6 модулей9
GitHub actionsАвтоматизация в GitHubМодель workflowсостояние и артефактыматрицы и runtimeрелизы и environmentsПереиспользование и собственные actionsgovernance и эксплуатацияgithubactions

Оптимизация времени CI

Практики ускорения: правильный cache, matrix pruning, concurrency cancellation, incremental builds, artifact handoff, split jobs и анализ job execution time.

Оптимизация времени CI

Оптимизация CI в GitHub Actions — это не один трюк, а работа с двумя разными метриками. Первая — wall-clock time: сколько разработчик ждёт зелёный PR. Вторая — runner minutes: сколько суммарно отработали jobs, особенно в matrix. Быстрый pipeline может быть дорогим, а дешёвый pipeline может давать слишком поздний feedback. Хороший CI pipeline оптимизирует оба параметра: сначала быстро ловит очевидные ошибки, потом запускает дорогие проверки там, где они действительно нужны.

flowchart TD A[push или pull_request] --> B{concurrency group} B -->|старый run отменён| C[последний commit] C --> D[setup runtime + dependency cache] D --> E[fast gate: lint, typecheck, unit] E --> F[build один раз] F --> G[upload artifact: dist/report] G --> H[matrix: smoke/integration] H --> I[summary: cache hit, durations, artifacts] E -. быстрая ошибка .-> J[fail early] H -. дорогая диагностика .-> K[полная картина по runtime/OS]
flowchart TD
  A[push или pull_request] --> B{concurrency group}
  B -->|старый run отменён| C[последний commit]
  C --> D[setup runtime + dependency cache]
  D --> E[fast gate: lint, typecheck, unit]
  E --> F[build один раз]
  F --> G[upload artifact: dist/report]
  G --> H[matrix: smoke/integration]
  H --> I[summary: cache hit, durations, artifacts]

  E -. быстрая ошибка .-> J[fail early]
  H -. дорогая диагностика .-> K[полная картина по runtime/OS]
Типичный путь ускорения CI: отменить устаревшие runs, кешировать зависимости, быстро падать на дешёвых проверках и передавать build result через artifact.

Сначала измерять, потом ускорять

В GitHub UI время видно на странице workflow run: откройте Actions, выберите run и посмотрите длительность jobs; для private repositories на GitHub-hosted runners отдельно отображаются billable minutes. Через CLI удобно быстро смотреть историю и конкретный run:

gh run list --workflow ci.yml --limit 10
gh run view <run-id> --verbose
gh run view --job <job-id> --log

Цель первого прохода — найти критический путь. Если lint идёт 2 минуты, test — 7, build — 4, а integration ждёт build и потом работает 10 минут, PR ждёт не сумму всех jobs, а самую длинную цепочку зависимостей через needs. Но деньги и лимиты считаются по суммарному исполнению: 6 параллельных matrix jobs по 8 минут — это примерно 48 runner-minutes, даже если человек ждал около 8–10 минут.

Cache: ускорять скачивание, а не прятать state

Dependency cache нужен для повторно создаваемых, но дорогих данных: package manager store, Gradle cache, Maven repository, pip cache, Rust registry, build cache конкретного инструмента. Он не должен быть тайным shared disk между jobs. Cache в Actions восстанавливается по key, затем по restore-keys; существующий cache по ключу не меняется, при miss создаётся новый cache после успешного job.

Для Node чаще всего достаточно встроенного cache в actions/setup-node:

jobs:
  test:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm test

Если нужен прямой actions/cache, включайте в key всё, что реально влияет на совместимость: OS, runtime version, lockfile. restore-keys должны идти от специфичного к более широкому.

- name: Cache pnpm store
  uses: actions/cache@v4
  with:
    path: ~/.local/share/pnpm/store
    key: ${{ runner.os }}-node20-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
    restore-keys: |
      ${{ runner.os }}-node20-pnpm-
      ${{ runner.os }}-pnpm-

Плохой cache обычно выглядит так: ключ слишком общий, path содержит dist с артефактами старого commit, внутрь случайно попали токены или .env, или cache скачивается дольше, чем заново ставятся зависимости. Для секретов действует правило из Variables, env и secrets: не хранить credentials в cache path, потому что cache доступен workflow runs в рамках своей модели доступа.

Matrix pruning: не умножать проверки без причины

Matrix strategy ускоряет покрытие, но легко раздувает стоимость. Полная комбинация 3 OS × 4 Node versions × 3 databases создаёт 36 jobs. Часто PR не нуждается в такой сетке. Практичный вариант: быстрый PR gate на одной основной платформе, небольшой compatibility smoke, а полный matrix — на main, release branch или nightly schedule из Events и filters.

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: true
      max-parallel: 4
      matrix:
        os: [ubuntu-24.04]
        node: [20]
        include:
          - os: windows-latest
            node: 20
            smoke: true
          - os: macos-latest
            node: 20
            smoke: true
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: npm
      - run: npm ci
      - run: npm test
        if: ${{ !matrix.smoke }}
      - run: npm run test:smoke
        if: ${{ matrix.smoke }}

fail-fast: true отменяет остальные matrix jobs, если обязательная комбинация упала; это хорошо для PR, где важен быстрый feedback. Для диагностики редких платформ иногда лучше fail-fast: false, чтобы собрать все результаты. max-parallel ограничивает одновременный запуск: pipeline может идти чуть дольше, зато не забивает лимиты runners, базы, внешние API или budget.

Concurrency cancellation: не тестировать устаревшие commits

На активном PR разработчик может запушить три commit за пять минут. Без concurrency GitHub может продолжать гонять старые runs, хотя нужен только последний. Для CI обычно нормальна отмена старых runs в той же branch или PR:

name: ci

on:
  pull_request:
  push:
    branches: [main]

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

Группа должна включать github.workflow, иначе разные workflows могут случайно отменять друг друга. Для deploy на production логика другая: старый deploy нельзя просто оборвать посередине. Там часто используют отдельную concurrency group на environment и очередь, а не агрессивную отмену; это уже ближе к Deployment workflows и Environments и protection rules.

Incremental builds и split jobs

Incremental build полезен, когда сам инструмент умеет корректно определять изменённые части: Gradle build cache, Turborepo/Nx affected tasks, Bazel, cargo incremental в подходящих сценариях. В GitHub Actions это обычно комбинируют с dependency cache и аккуратными filters. В monorepo не обязательно гонять все пакеты на каждый markdown diff, но фильтры должны быть консервативными: если shared package изменился, зависящие apps тоже должны попасть в проверку.

Split jobs помогают отделить быстрый отказ от дорогой диагностики. Например: lint и typecheck идут первыми; unit стартует параллельно; build создаёт production bundle; integration скачивает готовый bundle artifact и не пересобирает его заново.

jobs:
  build:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: web-dist
          path: dist
          retention-days: 5

  integration:
    needs: build
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/download-artifact@v5
        with:
          name: web-dist
          path: dist
      - run: ./scripts/run-integration-against-dist.sh

Это место, где Artifacts и reports отличаются от cache. Artifact — результат конкретного run: dist, coverage report, test report, binary. Cache — ускоритель будущих runs. Если downstream job должен проверить ровно тот bundle, который потом уйдёт в package или deploy, передавайте artifact, а не пересобирайте «такой же» bundle второй раз.

Summaries: сделать медленное видимым

Оптимизация ломается, если никто не видит, куда ушло время. Добавьте короткий job summary: cache hit/miss, длительность ключевых команд, размер artifact, количество test files. $GITHUB_STEP_SUMMARY принимает Markdown и появляется на странице run после завершения job.

- name: Timing summary
  if: always()
  run: |
    {
      echo "### CI timing"
      echo "| Segment | Value |"
      echo "|---|---:|"
      echo "| npm cache hit | ${{ steps.cache.outputs.cache-hit || 'n/a' }} |"
      echo "| artifact | web-dist |"
    } >> "$GITHUB_STEP_SUMMARY"

Практичная эвристика: сначала уберите устаревшие runs через concurrency, затем настройте dependency cache, потом сократите matrix, после этого разделяйте jobs и передавайте artifacts. Если начинать с тонкой настройки cache, пока workflow запускает 20 ненужных jobs на каждый push, выигрыш будет незаметен.

ИнтерактивИнтуиция runner-minutes#int-1
Упрощённая модель: wall-clock здесь не считается, только суммарные runner-minutes. Она хорошо показывает, почему matrix pruning и concurrency cancellation дают сильный эффект.
Суммарные runner-minutes144 мин
После отмены старых runs48 мин
Экономия от concurrency96 мин
Поменяйте число matrix jobs, среднюю длительность и количество устаревших runs, чтобы увидеть, как быстро растёт суммарное время runner.

See also

  • CI pipeline — базовая цепочка checkout, setup, install, test, build и status checks.
  • Matrix strategyinclude, exclude, fail-fast, max-parallel и цена комбинаций.
  • Dependency cache — cache keys, restore keys, cache hit/miss и ограничения cache.
  • Artifacts и reports — передача build outputs и отчётов между jobs.
  • Workflow commands и summaries$GITHUB_STEP_SUMMARY и служебные environment files.
  • Events и filters — как запускать тяжёлые проверки только на нужных событиях.
  • Deployment workflows — почему deploy concurrency отличается от PR CI cancellation.

Внешние справки: GitHub Docs по dependency caching, matrix jobs, workflow syntax и concurrency, artifacts, job summaries, workflow run history и job execution time.

Быстрое повторение #rc-1

Почему при оптимизации GitHub Actions нужно смотреть отдельно wall-clock time и runner minutes?

Показать ответ

Wall-clock time показывает, сколько разработчик ждёт зелёный PR. Runner minutes показывают суммарную работу всех jobs, поэтому параллельная matrix может быть быстрой для человека, но дорогой по минутам.

Источники

  1. GitHub Docs: Dependency caching reference
  2. GitHub Docs: Running variations of jobs in a workflow
  3. GitHub Docs: Workflow syntax for GitHub Actions
  4. GitHub Docs: Workflow commands for GitHub Actions
  5. GitHub Docs: Store and share data with workflow artifacts
  6. GitHub Docs: Viewing workflow run history
  7. GitHub Docs: Viewing job execution time