Понимание работы кэширования в GitHub Actions

Источник: «Understanding how caching works in GitHub Actions»
GitHub предоставляет собственные экшены для кэширования в рабочих процессах.

Они относительно просты в использовании. Вы настраиваете их, указывая путь к кэшу и ключ, определяющий, когда кэш должен быть восстановлен.

Вот пример кэширования каталога vendor в PHP проекте:

- name: Cache composer dependencies
uses: actions/cache@v4
with:
path: vendor
key: composer-${{ hashFiles('composer.lock') }}

Если в файле composer.lock ничего не изменилось, то рабочий процесс может просто перезагрузить каталог vendor с момента последнего запуска рабочего процесса. Это может сэкономить немало времени при выполнении CI.

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

Например, мы используем PHPStan (через Larastan) во всех проектах. Его запуск может занять некоторое время, поэтому он использует папку кэша для отслеживания файлов, которые уже были просканированы и не изменились. Как использовать для этого экшен кэша GitHub?

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

- name: Cache Larastan result cache
uses: actions/cache@v4
with:
path: .phpstan.cache
key: "phpstan-result-cache-${​{ github.run_id }}"
restore-keys: |
phpstan-result-cache-

Что здесь происходит с key и restore-keys?

GitHub использует эти значения, чтобы попытаться определить, какой кэш нужно восстановить во время этого сеанса. Сначала он проверяет результат кэширования, совпадающий со значением key, но считает его совпадением только в том случае, если это полное, точное совпадение. Если он не находит такого совпадения, то проверяет одно или несколько значений restore-keys по порядку. При проверке этих значений он должен найти только частичное совпадение префикса. Первый найденный префикс будет восстановлен. А если несколько результатов кэширования соответствуют префиксу, будет использован самый последний.

Зная, как это работает, обратите внимание, что главный key имеет значение, включающее текущий run_id. Это значение уникально для каждого запуска. Благодаря этому мы знаем, что key никогда не будет совпадать, и поэтому он всегда будет возвращаться к значению restore-keys.

Почему мы должны настраивать его таким образом? Это может стать более понятным на конкретном примере:

Допустим, наш CI запуск имеет run_id 123, а предыдущий — run_id 122.

Таким образом, когда начнётся это выполнение, у нас в кэше будет значение с ключом phpstan-result-cache-122 из предыдущего выполнения.

Поскольку текущее выполнение равно 123, GitHub сначала попытается получить кэш с ключом phpstan-result-cache-123. А поскольку совпадений не будет, он вернётся к значению restore-keys phpstan-result-cache-. Помните, что это значение должно соответствовать только префиксу ключа, поэтому он найдёт кэш с ключом phpstan-result-cache-122 и восстановит его.

Когда это выполнение завершится, он сохранит новый кэш с ключом phpstan-result-cache-123, который, в свою очередь, будет использован следующим выполнением.

Это даёт нам лучшее из двух миров. Мы можем восстановить кэш PHPStan из предыдущего выполнения, что ускоряет текущее выполнение. Но мы также постоянно сохраняем новый кэш с результатами текущего выполнения. Таким образом, наш кэш не устаревает всё больше и больше.

Эта техника хорошо работает с различными инструментами. Мы также используем её с Rector и PHP CS Fixer.

Надеюсь, это вам поможет.

Дополнительные материалы

Предыдущая Статья

Современные команды и возможности Git

Следующая Статья

Миграция с MySQL на Postgres с помощью конструктора запросов Laravel