← CI/CD: практика
13 мин · средне · Урок 8 из 10

Кеш: чем он отличается от артефактов

Ускоряем установку зависимостей между прогонами. Ключ по хешу lock-файла и разбор, почему кешировать node_modules при npm ci бессмысленно.

Две похожие сущности с разной задачей

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

Разница в направлении. Артефакт едет вперёд, от стадии к стадии внутри одного пайплайна. Кеш едет вбок, из вчерашнего пайплайна в сегодняшний.

Отсюда и правило выбора. Результат сборки, который нужен деплою, это артефакт. Скачанные зависимости, которые не жалко потерять, это кеш.

Материалы урока:

git pull --no-rebase upstream main
cp lesson-08-cache/pipeline.yml .gitlab-ci.yml

Ключ решает всё

build:
  image: node:22-alpine
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - .npm
  script:
    - npm ci --cache .npm --prefer-offline
    - npm run build

key.files считает хеш от перечисленных файлов, и он попадает в имя кеша. В логе это видно:

Checking cache for 0_package-lock-69002804654f84e32d6750cf64eeedd8a1476ef2-protected...
.npm: found 21 matching artifact files and directories
Successfully extracted cache

Поменяли зависимости — поменялся package-lock.json — поменялся хеш — кеш поехал новый. Не поменяли, значит переиспользуется старый. Ровно то поведение, которое нужно.

Ключом может быть и статичная строка (key: node-deps), но тогда после обновления зависимостей вы будете тянуть протухший кеш и разбираться, почему в сборке старая версия пакета.

В key.files помещается максимум два файла. Для монорепозитория с пятью lock-файлами это ограничение всплывает сразу, и обходят его отдельным кешем на каждый пакет.

Почему .npm, а не node_modules

Самая частая ошибка в этом месте: кешируют node_modules. С npm install это работает, с npm ci бессмысленно.

npm ci начинает работу с того, что удаляет node_modules целиком. Такой у него контракт: воспроизводимая установка с нуля по lock-файлу. Восстановленная из кеша папка будет снесена в первую же секунду.

Поэтому кешируют папку загрузок npm, а не установленные пакеты:

    - npm ci --cache .npm --prefer-offline

--cache .npm переносит весь кеш npm внутрь проекта: и скачанные архивы пакетов, и метаданные реестра. По умолчанию он лежит в домашней папке, а GitLab кеширует только пути внутри проекта. --prefer-offline велит сначала смотреть в этот кеш и лезть в сеть только за тем, чего нет.

Насколько это ускоряет

На учебном проекте с двумя пакетами разница копеечная: установка занимает 392 мс против 507 мс. Кеш здесь не столько ускоряет, сколько показывает механику.

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

Смотреть надо не на общее время job, а на строки Restoring cache и Created cache в логе: сколько занял сам обмен с хранилищем. Если кеш весит сотни мегабайт, скачивание может съесть выигрыш от установки.

Кеш может врать

У кеша нет гарантий. GitLab обещает «постараться», а не «сохранить»: кеш живёт на конкретном раннере или в объектном хранилище, чистится по своим правилам и может просто не найтись.

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

Отсюда практика: cache для того, что скачивается заново, artifacts для того, что должно доехать гарантированно. И Clear runner caches в интерфейсе, когда сборка начала вести себя странно без явных причин.

Что важно запомнить

Артефакты передают файлы вперёд по стадиям, кеш переносит их между прогонами пайплайна.

key.files привязывает кеш к хешу lock-файла: обновились зависимости — приехал новый кеш.

При npm ci кешируют .npm с флагом --cache .npm, потому что node_modules эта команда удаляет перед установкой.

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

Дальше учимся управлять тем, какие job вообще запускаются: rules, ручные кнопки и ветки.

Прошли материал до конца? Отметьте урок пройденным.