Rush.js — как можно значительно ускорить сборку ваших проектов, используя кеширование

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

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

В Rush по умолчанию реализован механизм инкрементной (ускоренной) сборки. При повторном вызове команды rush build пропускаются уже обновлённые проекты. Инкрементный анализ Rush опирается на хеши зависимостей. Их можно найти в файле project>/.rush/temp/shrinkwrap-deps.json внутри каждого проекта. Но результаты сборок никуда не сохраняются и поэтому, как правило, при переключении на другую ветку потребуется сделать полный rebuild.

Проект считается обновлённым в следующих случаях:

  • проект создан локально;

  • с момента последней сборки не изменились исходные файлы и npm-зависимости;

  • проект зависит от других локальных проектов из монорепозитория и эти проекты обновлены;

  • параметры командной строки не изменялись. Например, если сначала была выполнена команда rush build, а затем rush build --production, то в таком случае проекты требуют пересборки.

Механизм выбора проектов для rebuild

Предположим, что у нас есть монорепозиторий, состоящий из семи проектов: A, B, C, D, E, F, G.

Эти кружки представляют локальные проекты монорепозитория, а не внешние npm-пакеты. Проект D зависит от C и G, и это означает, что С и G нужно собрать до сборки D.

Если мы внесём изменения в проект B, то на момент вызова последующего build произойдёт rebuild сначала проекта B, потом C, и в последнюю очередь D, так как они зависят друг от друга. Все остальные проекты останутся без изменений.

В основе кеширования сборок лежит похожий механизм определения проектов, которые необходимо пересобрать. Разница в том, что результат успешной сборки помещается в tar-архив. Имя файла с архивом содержит специальный хеш. Созданный архив кешируется и помещается в хранилище.

Перед сборкой проекта вычисляется его хеш, который далее запрашивается в кеше. Если запись кеша есть в хранилище, то все существующие выходные папки удаляются, tar-архив из предыдущей сборки извлекается в папку проекта, а сборка пропускается. Кешируются не все папки проекта, а только те, которые указаны в конфигурации, например, папка dist.

Есть два варианта хранения закешированных архивов:

  • В папке кеша на локальном диске. Расположение по умолчанию — common/temp/build-cache.

  • В контейнере облачного хранилища. По умолчанию система CI будет настроена на запись в облачное хранилище, а отдельным пользователям будет предоставлен доступ только для чтения. Например, каждый раз, когда PR объединяется с основной ветвью, система CI получает архивы сборок и загружает их в облачное хранилище. И поэтому даже самая первая сборка после команды git clone будет очень быстрой.

Хеш формируется следующим образом. Создаётся хэш SHA1, и в него в определённом порядке добавляются данные:

  • список названий output-папок в JSON-формате, которые должны быть закешированы;

  • название последней введённой команды;

  • хеш каждой зависимости проекта.

Сформированный хеш помещается в название файла с tar-архивом, который будет сохранён в хранилище. Исходный код получения хеша:

Функция _getCacheId
Функция _getCacheId

Включение функции кеширования сборок

Для того, чтобы кеширование заработало, необходимо добавить файл с конфигурацией build-cache.json. Он помещается в корень папки common/config/rush.

/**
 * Пример конфигурации build-cache.json
 */
{
  "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/build-cache.schema.json",

  /**
   * Для в включения функции кеширования параметр buildCacheEnabled должен иметь значение true
   */
  "buildCacheEnabled": true,

  /**
   * (Обязательный параметр) определяется где будет храниться результат сборки проекта
   *
   * Доступные значения: "local-only", "azure-blob-storage", "amazon-s3"
   */
  "cacheProvider": "local-only",

   /**
   * Переопределение параметра cacheEntryNamePattern позволяет изменить формат названия файлов с кэшом проектов. По дефолту добавляется только hash  
   */
  //"cacheEntryNamePattern": "[projectName:normalize]-[phaseName:normalize]-[hash]" 

  /**
   *  azureBlobStorageConfiguration необходимо добавить, если "cacheProvider"="azure-blob-storage"
   */
  "azureBlobStorageConfiguration": {
    /**
     * (Required) The name of the the Azure storage account to use for build cache.
     */
    // "storageAccountName": "example",

    /**
     * (Required) The name of the container in the Azure storage account to use for build cache.
     */
    // "storageContainerName": "my-container",

    /**
     * The Azure environment the storage account exists in. Defaults to AzurePublicCloud.
     *
     * Possible values: "AzurePublicCloud", "AzureChina", "AzureGermany", "AzureGovernment"
     */
    // "azureEnvironment": "AzurePublicCloud",

    /**
     * An optional prefix for cache item blob names.
     */
    // "blobPrefix": "my-prefix",

    /**
     * If set to true, allow writing to the cache. Defaults to false.
     */
    // "isCacheWriteAllowed": true
  },

  /**
   * amazonS3Configuration необходимо добавить, если "cacheProvider"="amazon-s3"
   */
  "amazonS3Configuration": {
    /**
     * (Required unless s3Endpoint is specified) The name of the bucket to use for build cache.
     * Example: "my-bucket"
     */
    // "s3Bucket": "my-bucket",

    /**
     * (Required unless s3Bucket is specified) The Amazon S3 endpoint of the bucket to use for build cache.
     * This should not include any path; use the s3Prefix to set the path.
     * Examples: "my-bucket.s3.us-east-2.amazonaws.com" or "http://localhost:9000"
     */
    // "s3Endpoint": "https://my-bucket.s3.us-east-2.amazonaws.com",

    /**
     * (Required) The Amazon S3 region of the bucket to use for build cache.
     * Example: "us-east-1"
     */
    // "s3Region": "us-east-1",

    /**
     * An optional prefix ("folder") for cache items. It should not start with "/".
     */
    // "s3Prefix": "my-prefix",

    /**
     * If set to true, allow writing to the cache. Defaults to false.
     */
    // "isCacheWriteAllowed": true
  }
}

Чтобы включить запись кеша на локальный диск, необходимо добавить в конфигурацию build-cache.json только два параметра: buildCacheEnabled со значением true и cacheProvider со значением local-only. При таком варианте tar-архивы для каждого проекта после запуска команды rush build помещаются в папку common/temp/build-cache.

На момент написания статьи, помимо сохранения кеша в локальном хранилище, доступны ещё два варианта хранения в облачных контейнерах: Microsoft Azure blob storage container и Amazon S3 bucket. Для них необходимо передать в параметр cacheProvider значения azure-blob-storage или amazon-s3 соответственно, а также настройки для конфигурации облачного контейнера azureBlobStorageConfiguration либо amazonS3Configuration (смотрите конфигурацию выше).

Настройка параметров кеширования отдельно для каждого проекта

Если на этом этапе запустить команду rush build --verbose (при добавлении флага --verbose в консоли отображаются логи, которые обычно скрыты во время сборки), то появится предупреждение:

Project does not have a rush-project.json configuration file, or one provided by a rig,
so it does not support caching.

Чтобы избавиться от этого предупреждения, дополнительно помимо конфигурации build-cache.json нужно внутри каждого проекта настроить, какие папки и при вызове каких команд нужно закешировать. Для этого в папку config внутри каждого проекта (важный момент, не в папке common, а в каждом проекте) добавляется файл rush-project.json.

<your-project>/config/rush-project.json

{
  "incrementalBuildIgnoredGlobs": ["temp/**"],

  "disableBuildCacheForProject": false,  // при необходимости можно выключить кеширование для отдельных проектов

  "operationSettings": [  
    {
      "operationName": "build", // operationName — это название команда или фаза, которая вызывается в проекте

      // Название папок верхнего уровня, которые необходимо закешировать. Например: lib, dist 
      "outputFolderNames": ["output-folder-1", "output-folder-2"]
    },
    {
      "operationName": "_phase:build", //фазы - это еще одна интересная эксперементальная функция, см. Enabling phased builds  
      "outputFolderNames": ["output-folder-a", "output-folder-b"]
    },
    {
      "operationName": "test",
      "disableBuildCacheForOperation": true
    }
  ]
}

Также этот файл будет полезен при настройке ещё одной интересной экспериментальной функции — phased builds. Она позволяет определить некоторые отдельные операции как фазы, которые можно выполнять параллельно в один момент времени. Например одновременный запуск rush build и rush test. Скажем, если сборка проекта A завершилась, а он находится в зависимостях проектов B и С, то сначала соберётся проект A, затем одновременно со сборкой проекта C и B начнётся запуск unit-тестов в A. Подробнее про это можно прочитать здесь. И кстати, включение кеширования — одно из обязательных условий для этой фичи.

После добавления файлов rush-project.json в проекты при запуске команды rush build --verbose результат сборки запишется в хранилище, а в консоли появится запись:

This project was not found in the build cache.

Caching build output folders: dist
Successfully set cache entry.

Время сборки 31 проекта при настроенном локальном хранилище: 6 минут 10,7 секунды.

При повторном запуске rush build проверяется наличие проекта в кеше. Если запись кеша есть, то существующие выходные папки удаляются, tar-архив из предыдущей сборки извлекается в папку проекта, а сборка пропускается. В результате в терминале появится запись:

Build cache hit.
Clearing cached folders: dist
Successfully restored output from the build cache.

Время повторной сборки: 1,44 секунды.

Заключение

Если сравнивать инкрементную сборку по умолчанию при локальном запуске с кешированием сборок, то длительность повторных сборок примерно одинаковая. Но инкрементная сборка не учитывает, были ли изменения в output-папках, и в таком случае (либо после переключения на новую ветку) может потребоваться полный rebuild.

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

Источник: https://habr.com/ru/company/domclick/blog/709916/


Интересные статьи

Интересные статьи

Много раз при обсуждении преимуществ и прелестей Go, как языка разработки, мне приходилось слышать что-то вроде «у вас даже нет дженериков» или «вот завезут дженерики, тогда и поговорим». Так вот, дже...
Количество киберпреступлений постоянно и довольно быстро увеличивается. Так, за прошлый год потери российской экономики от деятельности хакеров составили около 6 трлн рублей. По словам экспертов, злоу...
В этой статье я хотел бы познакомить сообщество с библиотекой JPAstreamer. Идея этой библиотеки очень проста, но в то же время гениальна - получать нужные нам сущности из...
Компания «Деловой разговор» — Титановый партнер 3СХ — осуществила расширенную интеграцию IP-АТС 3CX с Битрикс 24. Ранее уже существовали отдельные модули, решающие конкретные задачи, напр...
Продуктовые команды Google вот уже несколько лет проводят исследования, чтобы понять, как правильно проектировать приложения для пользователей из Индии, Южной Азии, Африки, Латинской Америки и ...