Улучшенное кэширование CI/CD в системах сборки нового поколения

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

Поэтому большинство провайдеров CI/CD предлагают средства кэширования для ускорения работы путем выборочного восстановления состояния предыдущих запусков. Но поскольку детали ваших рабочих процессов сборки непрозрачны для CI-провайдеров, они могут кэшировать только на очень грубом уровне, и это ограничивает преимущества, которые вы можете получить от них. Читайте дальше, чтобы понять, почему!

Как работают кэши CI

Все эти встроенные средства кэширования работают одинаково:

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

Например, допустим, вы хотите кэшировать каталог, содержащий загруженные колеса Python.[1], чтобы последующим запускам не пришлось скачивать их снова. Для этого вы можете создать ключ кэша на основе хэша вашего файла requirements.txt.[2], скажем, wheel-downloads-a53be93cc8, и кэшировать каталог wheel по этому ключу. Когда процесс загрузки колеса успешно завершается, система CI загружает содержимое каталога wheel и ссылается на него с помощью этого ключа. При следующем запуске CI, если requirements.txt не изменился, тот же ключ кэша, wheel-downloads-a53be93cc8, будет вычислен из него, и таким образом эта версия каталога wheel будет восстановлена.

Частичное восстановление кэша

Но что произойдет, если requirements.txt изменится, даже незначительно? Придется ли нам снова загружать все колеса из-за того, что какая-то небольшая правка привела к пропуску кэша?

Чтобы справиться с этой ситуацией, некоторые CI-провайдеры, включая GitHub Actions и CircleCI, поддерживают так называемое «частичное восстановление кэша»: CI-провайдер может предоставить список ключевых префиксов в запросе на восстановление. Система будет искать все записи кэша с первым префиксом и восстановит самую последнюю из них, а если их нет, то попробует второй префикс, и так далее. Это позволяет вам вернуться к восстановлению более ранней версии состояния, поскольку это может быть лучше, чем ничего.

В нашем примере, допустим, после изменения requirements.txt новый ключ кэша вычисляется как wheel-downloads-80b5cfa6cb. При восстановлении каталога wheel вы можете указать такие префиксы: [wheel-downloads-80b5cfa6cb, wheel-downloads-]. Если есть точное совпадение по первому ключу, отлично. Но если нет, система восстановит последнее содержимое с префиксом wheel-downloads-. В данном случае, наш предыдущий wheel-downloads- a53be93cc8, который был создан из немного более ранней версии requirements.txt. В типичных случаях этот файл, вероятно, будет содержать многие из нужных вам колес, даже если это не полный набор, так что вы получите частичную выгоду.

Ограничения кэширования CI

Кэширование каталогов CI может помочь в решении проблемы «холодного старта», но, к сожалению, оно страдает от некоторых серьезных ограничений.

  • Загрузка и скачивание целых каталогов занимает время. Эти каталоги имеют тенденцию к неограниченному росту, например, новые файлы колеса добавляются, а старые не очищаются. Поэтому в какой-то момент кэширование и восстановление каталога колес становится более трудоемким, чем повторная загрузка колес с нуля! Работа с обрезкой кэша еще больше усложняется.
  • Вы должны вручную сгенерировать ключ кэша. Это может быть сложно, потому что вам придется рассуждать о том, какие входные данные могут повлиять на содержимое каталога, а затем эффективно отпечатать эти входные данные, используя только те конфигурационные конструкции, которые предоставляет система CI.
  • Не все состояние сборки можно аккуратно поместить в небольшое количество разделяемых, кэшируемых каталогов. Например, компилятор Java может быть настроен на запись файлов .class в большое количество локальных выходных каталогов, а не в один центральный.

Современные системы сборки на помощь

Более эффективным решением проблемы производительности CI является использование современной системы сборки, которая поддерживает удаленное кэширование:

Система сборки — это программа, которая организует выполнение базовых инструментов, таких как компиляторы, генераторы кода, программы запуска тестов, линтеры и так далее. Примерами систем сборки являются старинный Make, JVM-ориентированные Ant, Maven и Gradle, а также более новые системы, такие как Pants и Bazel (полное раскрытие: я являюсь одним из сопровождающих Pants).

Такая система, как Pants, принимает простые аргументы командной строки, такие как ./pants test path/to/some/tests и рассчитывает весь рабочий процесс, который должен произойти, чтобы удовлетворить этот запрос. Это включает: разбиение работы на отдельные процессы (такие как «разрешение зависимостей от третьих сторон», «генерация кода», «компиляция каждого исходного файла», «выполнение программы запуска тестов на каждом файле теста»), загрузка, установка и настройка всех базовых инструментов (таких как преобразователь зависимостей, генератор кода, компилятор, программа запуска тестов), выполнение их в правильном порядке, подача выходов на входы по мере необходимости. А поскольку система понимает рабочий процесс сборки очень тонко, она может эффективно использовать кэширование и параллелизм для ускорения сборки.

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

На практике мы видим огромное ускорение при таком удаленном кэшировании. Например, тестовое задание, занимающее 40 минут при отсутствии кэша, может занять всего 1-2 минуты, если небольшое изменение затронуло лишь несколько тестов.

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

PS Не стесняйтесь обращаться в Slack, если хотите узнать больше о Pants и его поддержке Python, Go, JVM, Shell и многого другого.

[1] Колесо — это формат двоичного распространения кода Python.

[2] `requirements.txt` — это файл, указывающий набор сторонних пакетов Python, от которых зависит наш код.

Оцените статью
devanswers.ru
Добавить комментарий