Использование rclone или restic или аналогичного для хранения дифференциальных кэшей между сборками для непрерывной интеграции

#c #caching #continuous-integration #devops #rclone

Вопрос:

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

Рассмотрим пример из https://github.com/actions/cache

 - name: Cache multiple paths
  uses: actions/cache@v2
  with:
    path: |
      ~/cache
      !~/cache/exclude
    key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
 

Что объявляет этот фрагмент? В нем говорится: «пожалуйста , попробуйте восстановить мой кэш, исключив некоторые из них, например, с именем ubuntu-abfacada123456 , затем выполните другие действия, и конец кэша хранилища заданий вернется с тем же именем».
Мы также можем предоставить

     restore-keys: ${{ runner.os }}-*
 

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

Это один из самых распространенных методов кэширования благодаря своей простоте и понятности.


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

  • подготовка изображения докера
  • создание зависимостей, управляемых vcpkg
  • создайте проект с помощью cmake, используя ccache
  • запуск тестов

Есть 3 пункта для кэширования

  • слои изображений docker; около 1,4 Гб, ?? на молнии
  • загрузка vcpkg, данные сборки, установленный корень сборки; около 5 ГБ, 1,4 Гб на молнии
  • ccache проекта; до 8 ГБ, 2 ГБ на молнии

Я уже пытался использовать обычные и обычные методы кэширования в действиях GitLab CI и GitHub. Они недостаточно эффективны для такого большого объема данных. Хранилище кэша по умолчанию часто переполняется, и старые, но все еще актуальные архивы удаляются.

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

Проблема

  1. Слишком много места требуется для хранения почти одних и тех же файлов. Разница от сборки к сборке составляет менее 5% от размера кэша, обычно до 1%
  2. Слишком много времени требуется для архивирования 5 ГБ данных сборки, а затем для их загрузки с машины сборки в удаленное постоянное хранилище
  3. Хранилище с архивами кэша переполняется чаще, чем данные устаревают
  4. Для новой инициализированной машины чистой сборки по-прежнему требуется загрузка полного кэша, но время загрузки и распаковки всегда намного меньше, чем для архивирования и загрузки.

For example my vcpkg build_dir of 5Gb is downloaded and uploaded in 1min40s but archiving and uploading takes 4min45s. We rarely change vcpkg, usually no changes to upload.

So the task is to reduce traffic over the network.

Ways to solve

I’d like to utilize some backup/restore utility like rclone or restic to store differential data.

The workflow is almost the same:

  • download cache;
  • do things, i.e. build/rebuild, assuming this step also autodetects what to rebuild;
  • upload updated cache, only difference;
  • mark uploaded snapshoot with labels for future use: date, branch-name, build-number, etc.

The key issue is to detect which snapshot to download.
Let’s consider the next use case. Given a PR from branch-A to branch-main. Branch-A is still under construction and developers push often. Every next build of PR should use cache from the previous one. The first build of the PR refers to cache of the base branch.

And here is the question

Is there any ready to use solution of the problem?

How do you optimize huge builds?