Almacenamiento en caché de las canalizaciones

El almacenamiento en caché de canalizaciones puede ayudar a reducir el tiempo de compilación al permitir que las salidas o dependencias descargadas de una ejecución se reutilicen en ejecuciones posteriores, lo que reduce o evita el costo de volver a crear o volver a descargar los mismos archivos. El almacenamiento en caché es especialmente útil en escenarios donde las mismas dependencias se descargan una y otra vez al principio de cada ejecución. Suele ser un proceso lento que implica cientos o miles de llamadas de red.

El almacenamiento en caché puede ser eficaz para mejorar el tiempo de compilación siempre que el tiempo para restaurar y guardar la memoria caché sea menor que el tiempo para volver a generar la salida desde cero. Por este problema, es posible que el almacenamiento en caché no sea efectivo en todos los escenarios y que realmente tenga un impacto negativo en el tiempo de compilación.

Actualmente, el almacenamiento en caché se admite en los trabajos de implementación y CI, pero no en los trabajos de versión clásica.

Cuándo usar artefactos frente al almacenamiento en caché

El almacenamiento en caché de canalizaciones y los artefactos de canalización realizan funciones similares, pero están diseñados para distintos escenarios y no se deben usar indistintamente. En general:

  • Use artefactos de canalización cuando necesite tomar archivos específicos generados en un trabajo y compartirlos con otros trabajos (y estos otros trabajos probablemente producirán un error sin ellos).

  • Use el almacenamiento en caché de canalizaciones cuando desee mejorar el tiempo de compilación mediante la reusación de archivos de ejecuciones anteriores (y no tener estos archivos no afectará a la capacidad de ejecución del trabajo).

Nota:

El almacenamiento en caché es actualmente gratuito y las memorias caché se almacenan en Azure Blob Storage.

Uso de la tarea Caché

El almacenamiento en caché se agrega a una canalización mediante la Cache tarea de canalización. Esta tarea funciona como cualquier otra tarea y se agrega a la steps sección de un trabajo.

Cuando se encuentra un paso de caché durante una ejecución, la tarea restaurará la memoria caché en función de las entradas proporcionadas. Si no se encuentra ninguna memoria caché, el paso se completa y se ejecuta el siguiente paso del trabajo. Una vez ejecutados todos los pasos del trabajo y suponiendo un estado de trabajo correcto, se ejecuta un paso especial de "guardar caché" para cada paso de "restauración de caché" que no se omitió. Este paso es responsable de guardar la memoria caché.

Nota:

Las memorias caché son inmutables, lo que significa que, una vez creada una memoria caché, no se puede cambiar su contenido.

Configurar la tarea Caché

La Cache tarea tiene dos entradas necesarias: y keypath .

Entrada de ruta de acceso

path la ruta de acceso de la carpeta que se almacenará en caché. Puede ser una ruta de acceso absoluta o relativa. Las rutas de acceso relativas se resuelven en $(System.DefaultWorkingDirectory) . Puede usar variables predefinidas, pero no se admiten caracteres comodín.

Entrada de clave

key debe establecerse en el identificador de la memoria caché que desea restaurar o guardar. Las claves se componen de una combinación de valores de cadena, rutas de acceso de archivo o patrones de archivo, donde cada segmento está separado por un | carácter.

  • Cadenas:
    valor fijo (como el nombre de la caché o un nombre de herramienta) o tomado de una variable de entorno (como el sistema operativo actual o el nombre del trabajo actual).

  • Rutas de acceso de archivo:
    ruta de acceso a un archivo específico cuyo contenido se va a hash. Este archivo debe existir en el momento en que se ejecuta la tarea. Tenga en cuenta que cualquier segmento de clave que "parece una ruta de acceso de archivo" se tratará como una ruta de acceso de archivo. En concreto, esto incluye segmentos que contienen un . . Esto podría provocar un error en la tarea cuando este "archivo" no existe.

    Sugerencia

    Para evitar que un segmento de cadena de tipo ruta de acceso se trate como una ruta de acceso de archivo, envolverlo con comillas dobles, por ejemplo: "my.key" | $(Agent.OS) | key.file

  • Patrones de archivo:
    Lista separada por comas del patrón de caracteres comodín de estilo glob que debe coincidir con al menos un archivo. Por ejemplo:

    • **/yarn.lock: todos los archivos yarn.lock en el directorio sources
    • */asset.json, !bin/**: todos los archivos asset.json ubicados en un directorio en el directorio sources, excepto en el directorio bin

El contenido de cualquier archivo identificado por una ruta de acceso de archivo o un patrón de archivo se aplica un algoritmo hash para generar una clave de caché dinámica. Esto resulta útil cuando el proyecto tiene archivos que identifican de forma única lo que se almacena en caché. Por ejemplo, se hace referencia a archivos como , , o normalmente en una clave de caché, ya que todos representan package-lock.json un conjunto único de yarn.lockGemfile.lockPipfile.lock dependencias.

Las rutas de acceso de archivo relativas o los patrones de archivo se resuelven en $(System.DefaultWorkingDirectory) .

Ejemplo:

Este es un ejemplo que muestra cómo almacenar en caché las dependencias instaladas por Yarn:

variables:
  YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn

steps:
- task: Cache@2
  inputs:
    key: '"yarn" | "$(Agent.OS)" | yarn.lock'
    restoreKeys: |
       yarn | "$(Agent.OS)"
       yarn
    path: $(YARN_CACHE_FOLDER)
  displayName: Cache Yarn packages

- script: yarn --frozen-lockfile

En este ejemplo, la clave de caché contiene tres partes: una cadena estática ("yarn"), el sistema operativo en el que se ejecuta el trabajo, ya que esta caché es única por sistema operativo y el hash del archivo que identifica de forma única el conjunto de dependencias de la memoria yarn.lock caché.

En la primera ejecución después de agregar la tarea, el paso de caché notificaría un "problema de caché", ya que la memoria caché identificada por esta clave no existe. Después del último paso, se creará una memoria caché a partir de los archivos de $(Pipeline.Workspace)/.yarn y se cargará. En la siguiente ejecución, el paso de caché mostrará un "acierto de caché" y el contenido de la memoria caché se descargará y restaurará.

Restaurar claves

restoreKeys se puede usar si se desea realizar consultas con varias claves exactas o prefijos de clave. Esto se usa para la reserva a otra clave en caso de que no key se produce una pulsación. Una clave de restauración buscará una clave por prefijo y dará como resultado la entrada de caché creada más reciente. Esto resulta útil si la canalización no puede encontrar una coincidencia exacta, pero quiere usar en su lugar un acierto de caché parcial. Para insertar varias claves de restauración, simplemente delimite mediante una nueva línea para indicar la clave de restauración (vea el ejemplo para obtener más detalles). El orden en el que se probarán las claves de restauración será de arriba abajo.

Software necesario en el agente auto-hospedado

Archivado de software o plataforma Windows Linux Mac
GNU Tar Obligatorio Obligatorio No
BSD Tar No No Requerido
7-Zip Recomendado No No

Los archivos ejecutables anteriores deben estar en una carpeta enumerada en la variable de entorno PATH. Tenga en cuenta que los agentes hospedados vienen con el software incluido, que solo es aplicable a los agentes auto-hospedados.

Ejemplo:

Este es un ejemplo sobre cómo usar claves de restauración mediante Yarn:

variables:
  YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn

steps:
- task: Cache@2
  inputs:
    key: '"yarn" | "$(Agent.OS)" | yarn.lock'
    restoreKeys: |
       yarn | "$(Agent.OS)"
       yarn
    path: $(YARN_CACHE_FOLDER)
  displayName: Cache Yarn packages

- script: yarn --frozen-lockfile

En este ejemplo, la tarea de caché intentará encontrar si la clave existe en la memoria caché. Si la clave no existe en la memoria caché, intentará usar la primera clave de yarn | $(Agent.OS) restauración. Esto intentará buscar todas las claves que coincidan exactamente con esa clave o que tenga esa clave como prefijo. Una coincidencia de prefijo puede producirse si hay un segmento yarn.lock hash diferente. Por ejemplo, si la clave siguiente estaba en la memoria caché donde el antiguo producía un hash diferente a , la clave de restauración yarn | $(Agent.OS) | old-yarn.lockyarn.lockyarn.lock produciría un acierto parcial. Si se pierde la primera clave de restauración, usará la siguiente clave de restauración que intentará encontrar cualquier clave que yarn comience por yarn . En el caso de los aciertos de prefijo, el resultado dará como resultado la clave de caché creada más recientemente.

Nota:

Una canalización puede tener una o varias tareas de almacenamiento en caché. No hay ningún límite en la capacidad de almacenamiento en caché y los trabajos y tareas de la misma canalización pueden acceder a la misma caché y compartirla.

Aislamiento y seguridad de caché

Para garantizar el aislamiento entre cachés de distintas canalizaciones y ramas diferentes, cada caché pertenece a un contenedor lógico denominado ámbito. Los ámbitos proporcionan un límite de seguridad que garantiza que un trabajo de una canalización no puede acceder a las memorias caché desde una canalización diferente y que un trabajo que compila una solicitud de acceso tiene acceso de lectura a las memorias caché de la rama de destino de la solicitud de solicitud (para la misma canalización), pero no puede escribir (crear) cachés en el ámbito de la rama de destino.

Cuando se encuentra un paso de caché durante una ejecución, la memoria caché identificada por la clave se solicita desde el servidor. A continuación, el servidor busca una memoria caché con esta clave de los ámbitos visibles para el trabajo y devuelve la memoria caché (si está disponible). Al guardar en caché (al final del trabajo), se escribe una memoria caché en el ámbito que representa la canalización y la rama. Consulte a continuación para más información.

CI, ejecuciones manuales y programadas

Ámbito Lectura Escritura
Rama de origen
Rama principal No

Ejecuciones de solicitudes de extracción

Ámbito Lectura Escritura
Rama de origen No
Rama de destino No
Rama intermedia (como refs/pull/1/merge )
Rama principal No

Ejecuciones de bifurcación de solicitud de extracción

Rama Lectura Escritura
Rama de destino No
Rama intermedia (como refs/pull/1/merge )
Rama principal No

Sugerencia

Dado que las memorias caché ya están en el ámbito de un proyecto, una canalización y una rama, no es necesario incluir ningún identificador de proyecto, canalización o rama en la clave de caché.

A acondicionador en la restauración de la memoria caché

En algunos escenarios, la correcta restauración de la memoria caché debe hacer que se ejecute un conjunto diferente de pasos. Por ejemplo, un paso que instala dependencias se puede omitir si se restauró la memoria caché. Esto es posible mediante la entrada cacheHitVar de tarea. Si esta entrada se establece en el nombre de una variable de entorno, la variable se establecerá en cuando se haya alcanzado la memoria caché, en un acierto de la caché de claves de restauración; de lo contrario, se establecerá trueinexact en false . A continuación, se puede hacer referencia a esta variable en una condición de paso o desde un script.

En el ejemplo siguiente, el install-deps.sh paso se omite cuando se restaura la memoria caché:

steps:
- task: Cache@2
  inputs:
    key: mykey | mylockfile
    restoreKeys: mykey
    path: $(Pipeline.Workspace)/mycache
    cacheHitVar: CACHE_RESTORED

- script: install-deps.sh
  condition: ne(variables.CACHE_RESTORED, 'true')

- script: build.sh

Bündler

En el caso de los proyectos de Ruby que usan Bundler, invalide la variable de entorno que usa Bundler para establecer la ruta de acceso en la que BUNDLE_PATHBUNDLE_PATH buscará gems.

Ejemplo:

variables:
  BUNDLE_PATH: $(Pipeline.Workspace)/.bundle

steps:
- task: Cache@2
  inputs:
    key: 'gems | "$(Agent.OS)" | my.gemspec'
    restoreKeys: | 
      gems | "$(Agent.OS)"
      gems
    path: $(BUNDLE_PATH)
  displayName: Cache gems

- script: bundle install

Ccache (C/C++)

Ccache es una caché del compilador para C/C++. Para usar Ccache en la canalización, asegúrese de que está instalado y, opcionalmente, agregado a su (consulte Los modos de ejecución CcachePATH de Ccache Establezca la CCACHE_DIR variable de entorno en una ruta de acceso en y almacenar en caché este $(Pipeline.Workspace) directorio.

Ejemplo:

variables:
  CCACHE_DIR: $(Pipeline.Workspace)/ccache

steps:
- bash: |
    sudo apt-get install ccache -y    
    echo "##vso[task.prependpath]/usr/lib/ccache"
  displayName: Install ccache and update PATH to use linked versions of gcc, cc, etc

- task: Cache@2
  inputs:
    key: 'ccache | "$(Agent.OS)"'
    path: $(CCACHE_DIR)
    restoreKeys: | 
      ccache | "$(Agent.OS)"
  displayName: ccache

Consulte Configuración de Ccache para obtener más detalles.

Imágenes de Docker

El almacenamiento en caché de imágenes de Docker reduce drásticamente el tiempo que se tarda en ejecutar la canalización.

pool:
  vmImage: 'Ubuntu-18.04'
steps:
  - task: Cache@2
    displayName: Cache task
    inputs:
      key: 'docker | "$(Agent.OS)" | cache'
      path: $(Pipeline.Workspace)/docker
      cacheHitVar: CACHE_RESTORED                #Variable to set to 'true' when the cache is restored
    
  - script: |
      docker load -i $(Pipeline.Workspace)/docker/cache.tar
    displayName: Docker restore
    condition: and(not(canceled()), eq(variables.CACHE_RESTORED, 'true'))

  - script: |
      mkdir -p $(Pipeline.Workspace)/docker
      docker save -o $(Pipeline.Workspace)/docker/cache.tar cache
    displayName: Docker save
    condition: and(not(canceled()), or(failed(), ne(variables.CACHE_RESTORED, 'true')))
  • key: (obligatorio): identificador único de la memoria caché.
  • path: (obligatorio): ruta de acceso de la carpeta o archivo que desea almacenar en caché.

Golang

Para los proyectos de Golang, puede especificar los paquetes que se van a descargar en el archivo go.mod. Si la GOCACHE variable aún no está establecida, esta establezca en donde desea que se descargue la memoria caché.

Ejemplo:

variables:
  GO_CACHE_DIR: $(Pipeline.Workspace)/.cache/go-build/

steps:
- task: Cache@2
  inputs:
    key: 'go | "$(Agent.OS)" | go.mod'
    restoreKeys: | 
      go | "$(Agent.OS)"
    path: $(GO_CACHE_DIR)
  displayName: Cache GO packages

Gradle

El uso de la compatibilidad con el almacenamiento en caché integrado de Gradle puede tener un impacto significativo en el tiempo de compilación. Para habilitar la caché de compilación, establezca la variable de entorno en una ruta de acceso en y GRADLE_USER_HOME$(Pipeline.Workspace) ejecute la compilación con o agregue --build-cache al org.gradle.caching=truegradle.properties archivo.

Ejemplo:

variables:
  GRADLE_USER_HOME: $(Pipeline.Workspace)/.gradle

steps:
- task: Cache@2
  inputs:
    key: 'gradle | "$(Agent.OS)" | **/build.gradle.kts' # Swap build.gradle.kts for build.gradle when using Groovy
    restoreKeys: |
      gradle | "$(Agent.OS)"
      gradle
    path: $(GRADLE_USER_HOME)
  displayName: Configure gradle caching

- task: Gradle@2
  inputs:
    gradleWrapperFile: 'gradlew'
    tasks: 'build'
    options: '--build-cache'
  displayName: Build

- script: |   
    # stop the Gradle daemon to ensure no files are left open (impacting the save cache operation later)
    ./gradlew --stop    
  displayName: Build
  • restoreKeys:las claves de reserva si se produce un error en la clave principal (opcional)

Nota:

Las memorias caché son inmutables, una vez que se crea una memoria caché con una clave determinada para un ámbito específico (rama), no se puede actualizar la memoria caché. Esto significa que si la clave es un valor fijo, todas las compilaciones posteriores de la misma rama no podrán actualizar la memoria caché aunque el contenido de la caché haya cambiado. Si desea usar un valor de clave fijo, debe usar el restoreKeys argumento como opción de reserva.

Maven

Maven tiene un repositorio local donde almacena descargas y artefactos creados. Para habilitarla, establezca la maven.repo.local opción en una ruta de acceso en y almacenar en caché esta $(Pipeline.Workspace) carpeta.

Ejemplo:

variables:
  MAVEN_CACHE_FOLDER: $(Pipeline.Workspace)/.m2/repository
  MAVEN_OPTS: '-Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'

steps:
- task: Cache@2
  inputs:
    key: 'maven | "$(Agent.OS)" | **/pom.xml'
    restoreKeys: |
      maven | "$(Agent.OS)"
      maven
    path: $(MAVEN_CACHE_FOLDER)
  displayName: Cache Maven local repo

- script: mvn install -B -e

Si usa una tarea de Maven,asegúrese de pasar también la variable porque se sobrescribe en caso contrario:

- task: Maven@3
  inputs:
    mavenPomFile: 'pom.xml'
    mavenOptions: '-Xmx3072m $(MAVEN_OPTS)'

.NET/NuGet

Si usa para administrar las dependencias de NuGet directamente dentro del archivo del proyecto y tiene archivos, puede habilitar el almacenamiento en caché estableciendo la variable de entorno en una ruta de acceso en y almacenando en caché este PackageReferencespackages.lock.jsonNUGET_PACKAGES$(UserProfile) directorio.

Ejemplo:

variables:
  NUGET_PACKAGES: $(Pipeline.Workspace)/.nuget/packages

steps:
- task: Cache@2
  inputs:
    key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/packages.lock.json'
    restoreKeys: |
       nuget | "$(Agent.OS)"
    path: $(NUGET_PACKAGES)
  displayName: Cache NuGet packages

Sugerencia

Las variables de entorno siempre invalidan cualquier configuración del NuGet.Config archivo. Si se produjo un error en la canalización: , debe crear una variable de canalización para que apunte a la nueva ruta de acceso local en el agente Information, There is a cache miss.NUGET_PACKAGES (exp d:\a\1). La canalización debe recoger los cambios y continuar la tarea correctamente.

En el ejemplo anterior, apunta al archivo de bloqueo generado $(Build.SourcesDirectory) del proyecto. Consulte Referencia de paquetes en archivos de proyecto para obtener más información sobre cómo habilitar la creación de archivos de bloqueo.

Node.js/npm

Hay diferentes maneras de habilitar el almacenamiento en caché en un proyecto de Node.js, pero la manera recomendada es almacenar en caché el directorio de caché compartida de npm . Npm administra este directorio y contiene una versión almacenada en caché de todos los módulos descargados. Durante la instalación, npm comprueba primero este directorio (de forma predeterminada) para los módulos que pueden reducir o eliminar las llamadas de red a la red registro npm o a un registro privado.

Dado que la ruta de acceso predeterminada al directorio de caché compartida de npm no es la misma en todas las plataformas, se recomienda invalidar la variable de entorno en una ruta de acceso en $(Pipeline.Workspace) . Esto también garantiza que la memoria caché sea accesible desde trabajos de contenedor y que no son de contenedor.

Ejemplo:

variables:
  npm_config_cache: $(Pipeline.Workspace)/.npm

steps:
- task: Cache@2
  inputs:
    key: 'npm | "$(Agent.OS)" | package-lock.json'
    restoreKeys: |
       npm | "$(Agent.OS)"
    path: $(npm_config_cache)
  displayName: Cache npm

- script: npm ci

Si el proyecto no tiene un archivo, haga referencia al archivo en la entrada de clave package-lock.json de caché en su package.json lugar.

Sugerencia

Dado que elimina la carpeta para asegurarse de que se usa un conjunto coherente y repetible de módulos, debe evitar el almacenamiento en caché npm cinode_modules al llamar a node_modulesnpm ci .

Node.js/Yarn

Al igual que con npm, hay diferentes maneras de almacenar en caché los paquetes instalados con Yarn. La manera recomendada es almacenar en caché la carpeta de caché compartida deYarn. Yarn administra este directorio y contiene una versión almacenada en caché de todos los paquetes descargados. Durante la instalación, Yarn comprueba primero este directorio (de forma predeterminada) para los módulos, lo que puede reducir o eliminar las llamadas de red a registros públicos o privados.

Ejemplo:

variables:
  YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn

steps:
- task: Cache@2
  inputs:
    key: 'yarn | "$(Agent.OS)" | yarn.lock'
    restoreKeys: |
       yarn | "$(Agent.OS)"
       yarn
    path: $(YARN_CACHE_FOLDER)
  displayName: Cache Yarn packages

- script: yarn --frozen-lockfile

Python/Anaconda

Configuración del almacenamiento en caché de la canalización con entornos de Anaconda

Ejemplo

variables:
  CONDA_CACHE_DIR: $(Pipeline.Workspace)/.condarc

# Add conda to system path
steps:
- script: echo "##vso[task.prependpath]$CONDA/bin"
  displayName: Add conda to PATH

- task: Cache@2
  displayName: Use cached Anaconda environment
  inputs:
    key: 'conda | "$(Agent.OS)" | environment.yml'
    restoreKeys: | 
      python | "$(Agent.OS)"
      python
    path: $(CONDA_CACHE_DIR)
    cacheHitVar: CONDA_CACHE_RESTORED

- script: conda env create --quiet --file environment.yml
  displayName: Create Anaconda environment
  condition: eq(variables.CONDA_CACHE_RESTORED, 'false')

PHP/Composer

En el caso de los proyectos PHP Composer, invalide la COMPOSER_CACHE_DIRCOMPOSER_CACHE_DIR usada por Composer.

Ejemplo:

variables:
  COMPOSER_CACHE_DIR: $(Pipeline.Workspace)/.composer

steps:
- task: Cache@2
  inputs:
    key: 'composer | "$(Agent.OS)" | composer.lock'
    restoreKeys: |
      composer | "$(Agent.OS)"
      composer
    path: $(COMPOSER_CACHE_DIR)
  displayName: Cache composer

- script: composer install

Problemas conocidos y comentarios

Si tiene problemas para configurar el almacenamiento en caché de la canalización, compruebe la lista de problemas abiertos en el repositorio. Si no ve el problema en la lista, cree uno nuevo y proporcione la información necesaria sobre el escenario.

Preguntas y respuestas

P: ¿Puedo borrar una caché?

A: Actualmente no se admite borrar una memoria caché. Sin embargo, puede agregar un literal de cadena (como ) a la clave de caché existente para cambiar la clave de forma que evite cualquier acierto en las memorias caché version2 existentes. Por ejemplo, cambie la siguiente clave de caché a partir de esto:

key: 'yarn | "$(Agent.OS)" | yarn.lock'

a:

key: 'version2 | yarn | "$(Agent.OS)" | yarn.lock'

P: ¿Cuándo expira una caché?

A. Las memorias caché expiran después de siete días sin actividad.

P: ¿Hay un límite en el tamaño de una memoria caché?

A. No hay ningún límite obligatorio en el tamaño de las memorias caché individuales o el tamaño total de todas las memorias caché de una organización.