Search Results for ""

依存関係のキャッシュ

キャッシュは、以前のジョブの高コストなフェッチ操作から取得したデータを再利用することで、CircleCI のジョブを効果的に高速化します。

ジョブを 1 回実行すると、以降のジョブ インスタンスでは同じ処理をやり直す必要がなくなり、その分高速化されます。

キャッシュのデータ フロー

キャッシュは、Yarn、Bundler、Pip などのパッケージ依存関係管理ツールと共に使用すると特に有効です。 キャッシュから依存関係を復元することで、yarn install などのコマンドを実行するときに、ビルドごとにすべてを再ダウンロードするのではなく、新しい依存関係をダウンロードするだけで済むようになります。

キャッシュ構成の例

キャッシュ キーは簡単に構成できます。 以下の例では、pom.xml のチェックサムとカスケード フォールバックを使用して、変更があった場合にキャッシュを更新しています。

    steps:

      - restore_cache:
         keys:
           - m2-{{ checksum "pom.xml" }}
           - m2- # チェックサムが失敗した場合に使用されます

はじめに

CircleCI 2.0 では依存関係キャッシュの自動化を利用できません。このため、最適なパフォーマンスを得るには、キャッシュ戦略を計画して実装することが重要です。 2.0 では、キャッシュを手動で構成し、より高度な戦略を立て、きめ細かに制御することができます。

ここでは、キャッシュの手動構成、選択した戦略のコストとメリット、およびキャッシュに関する問題を回避するためのヒントについて説明します。 メモ: CircleCI 2.0 のジョブ実行に使用される Docker イメージは、サーバー インフラストラクチャに自動的にキャッシュされます (可能な場合)。

Docker イメージの未変更レイヤーを再利用するプレミアム機能を有効にする方法については、「Docker レイヤー キャッシュの有効化」を参照してください。

概要

キャッシュは、キーに基づいてファイルの階層を保存します。 キャッシュを使用してデータを保存するとジョブが高速に実行されますが、キャッシュ ミス (ゼロ キャッシュ リストア) が起きた場合でも、ジョブは正常に実行されます。 たとえば、npm パッケージ ディレクトリ (node_modules) をキャッシュする場合は、初めてジョブを実行するときに、すべての依存関係がダウンロードされてキャッシュされます。キャッシュが有効な限り、次回そのジョブを実行するときにキャッシュが使用され、ジョブ実行が高速化されます。

キャッシュは、信頼性の確保 (古いキャッシュや不適切なキャッシュを使用しない) と最大限のパフォーマンス (すべてのビルドで完全にキャッシュを使用する) のどちらを優先するかを考慮して構成します。

通常は、ビルドが壊れる危険を冒したり、古い依存関係を使用して高速にビルドするよりも、信頼性の維持を優先した方が安全です。 このため、高い信頼性を確保しつつ、最大限のパフォーマンスを得られるようにバランスを取ることが理想的と言えます。

ライブラリのキャッシュ

ジョブ実行中にキャッシュすることが最も重要な依存関係は、プロジェクトが依存するライブラリです。 たとえば、Python なら pip、Node.js なら npm でインストールされるライブラリをキャッシュします。 さまざまな言語の依存関係管理ツール (npmpip など) には、依存関係がインストールされるパスがそれぞれ指定されています。 お使いのスタックの仕様については、各言語ガイドおよびデモ プロジェクトを参照してください。

プロジェクトに明示的に必要でないツールは、Docker イメージに保存するのが理想的です。 CircleCI のビルド済み Docker イメージには、そのイメージが対象としている言語を使用してプロジェクトをビルドするための汎用ツールがプリインストールされています。 たとえば、circleci/ruby:2.4.1 イメージには git、openssh-client、gzip などの便利なツールがプリインストールされています。

依存関係のキャッシュ

ワークフローでのキャッシュへの書き込み

同じワークフロー内のジョブどうしはキャッシュを共有できます。 このため、複数のワークフローの複数のジョブにまたがってキャッシュを実行すると、競合状態が発生する可能性があります。

キャッシュは書き込み後に変更不可です。つまり、node-cache-master などの特定のキーにキャッシュが書き込まれると、そのキーに再度書き込むことはできません。 たとえば、3 個のジョブを含むワークフローがあるとします。この中で、Job3 は Job1 と Job2 に依存しています ({Job1, Job2} -> Job3)。 これらは、すべて同じキャッシュ キーに対して読み書きを行います。

このワークフローの実行中、Job3 は Job1 または Job2 によって書き込まれたキャッシュを使用します。 キャッシュは変更不可なので、どちらかのジョブによって最初に保存されたキャッシュが使用されます。 結果が確定的ではなく、その時々によって結果が異なるため、通常、この動作は好ましくありません。 これを確定的なワークフローにするには、ジョブの依存関係を変更します。Job1 と Job2 で別のキャッシュに書き込み、Job3 ではいずれかのキャッシュから読み込みます。または、一方向の依存関係を指定します (Job1 -> Job2 ->Job3)。

node-cache-{{ checksum "package-lock.json" }} のような動的キーを使用して保存を行い、node-cache- のようなキーの部分一致を使用して復元を行うような、より複雑なジョブのケースもあります。 競合状態が発生する可能性がありますが、詳細はケースによって異なります。 たとえば、ダウンストリーム ジョブがアップストリーム ジョブのキャッシュを使用して最後に実行されるような場合です。

ジョブ間でキャッシュを共有している場合に発生する競合状態もあります。 Job1 と Job2 の間に依存関係がないワークフローを例に考えます。 Job2 は Job1 によって保存されたキャッシュを使用します。 Job1 がキャッシュの保存を報告したとしても、Job2 でキャッシュを正常に復元できることもあれば、キャッシュが見つからないと報告することもあります。 また、Job2 が以前のワークフローからキャッシュを読み込むこともあります。 その場合は、Job1 がキャッシュを保存する前に、Job2 がキャッシュを読み込もうとすることになります。 この問題を解決するには、ワークフローの依存関係 (Job1 -> Job2) を作成します。 こうすることで、Job1 の実行が終了するまで、Job2 の実行を待機させることができます。

キャッシュの復元

CircleCI では、restore_cache ステップにリストされているキーの順番でキャッシュが復元されます。 各キャッシュ キーはプロジェクトの名前空間にあり、プレフィックスが一致すると取得されます。 最初に一致したキーのキャッシュが復元されます。 複数の一致がある場合は、最も新しく生成されたキャッシュが使用されます。

次の例では、2 つのキーが提供されています。

    steps:

      - restore_cache:
          keys:
            # この package-lock.json のチェックサムに一致するキャッシュを検索します
            # このファイルが変更されている場合、このキーは失敗します
            - v1-npm-deps-{{ checksum "package-lock.json" }}
            # 任意のブランチから使用される、最も新しく生成されたキャッシュを検索します
            - v1-npm-deps-

2 つ目のキーは最初のキーよりも特定度が低いため、現在の状態と最も新しく生成されたキャッシュとの間に差がある可能性が高くなります。 依存関係ツールを実行すると、古い依存関係が検出されて更新されます。 これを部分キャッシュ リストアと言います。

上のキャッシュ キーの使用方法について、詳しく見ていきましょう。

keys: リストのすべての行は 1 つのキャッシュを管理します (各行が固有のキャッシュに対応しているわけではありません)。 この例でリストされているキー

(v1-npm-deps-{{ checksum "package-lock.json" }} および v1-npm-deps-) は、単一のキャッシュを表しています。 キャッシュの復元が必要になると、まず (最も特定度の高い) 最初のキーに基づいてキャッシュがバリデーションされ、次に他のキーを順に調べて、他のキャッシュ キーに変更があるかどうかが確認されます。

ここでは、最初のキーによって package-lock.json ファイルのチェックサムが文字列 v1-npm-deps- に連結されます。コミットでこのファイルが変更されている場合は、新しいキャッシュ キーが調べられます。

次のキーには動的コンポーネントが連結されていません。これは静的な文字列 v1-npm-deps- です。 キャッシュを手動で無効にするには、config.yml ファイルで v1v2 にバンプします。 これで、キャッシュ キーが新しい v2-npm-deps になり、新しいキャッシュの保存がトリガーされます。

モノレポ (モノリポ) でのキャッシュの使用

モノレポでキャッシュを活用する際のアプローチは数多くあります。 ここで紹介するアプローチは、モノレポのさまざまな部分にある複数のファイルに基づいて共有キャッシュを管理する必要がある場合に使用できます。

連結 package-lock ファイルの作成と構築

1) カスタム コマンドを設定ファイルに追加します。

    commands:
        create_concatenated_package_lock:
        description: "lerna.js で認識されるすべての package-lock.json ファイルを単一のファイルに連結します。 ファイルは、チェックサム ソースとしてキャッシュ キーの一部に使用します"
    parameters:
      filename:
        type: string
    steps:

      - run:
          name: package-lock.json ファイルの単一ファイルへの統合
          command: npx lerna list -p -a | awk -F packages '{printf "\"packages%s/package-lock.json\" ", $2}' | xargs cat > << parameters.filename >>

2) ビルド時にカスタム コマンドを使用して、連結 package-lock ファイルを生成します。

    steps:

        - checkout
        - create_concatenated_package_lock:
          filename: combined-package-lock.txt
    ## combined-package-lock.text をキャッシュ キーに使用します
        - restore_cache:
          keys:
            - v3-deps-{{ checksum "package-lock.json" }}-{{ checksum "combined-package-lock.txt" }}
            - v3-deps

キャッシュの管理

キャッシュの有効期限

save_cache ステップで作成されたキャッシュは、最長 30 日間保存されます。

キャッシュのクリア

言語または依存関係管理ツールのバージョンが変更され、キャッシュをクリアする必要がある場合は、上の例のような命名戦略を使用し、config.yml ファイルのキャッシュ キー名を変更して、変更をコミットします。

たとえば、以下のような場合に、キャッシュ キー名の数字を増やすことでキャッシュをクリアできます。

  • npm のバージョンが 4 から 5 に変更されるなど、依存関係管理ツールのバージョンが変更された場合
  • Ruby のバージョンが 2.3 から 2.4 に変更されるなど、言語のバージョンが変更された場合
  • プロジェクトから依存関係が削除された場合

キャッシュ サイズ

キャッシュ サイズは 500 MB 未満に抑えることをお勧めします。 これは、破損チェックを効率的に実行するための上限のサイズです。500 MB を超えると、チェック時間が非常に長くなります。 キャッシュ サイズは、CircleCI の [Jobs (ジョブ)] ページの restore_cache ステップで確認できます。 キャッシュ サイズを増やすこともできますが、キャッシュの復元中に問題が発生したり、ダウンロード中に破損する可能性が高くなるため、お勧めできません。 キャッシュ サイズを抑えるため、複数のキャッシュに分割することを検討してください。

基本的な依存関係キャッシュの例

CircleCI 2.0 の手動で構成可能な依存関係キャッシュを最大限に活用するには、キャッシュの対象と方法を明確にする必要があります。 その他の例については、「CircleCI を設定する」の「save_cache」セクションを参照してください。

ファイルやディレクトリのキャッシュを保存するには、.circleci/config.yml ファイルでジョブに save_cache ステップを追加します。

    steps:
      - save_cache:
          key: my-cache
          paths:
            - my-file.txt
            - my-project/my-dependencies-directory

ディレクトリのパスは、ジョブの working_directory からの相対パスです。 必要に応じて、絶対パスも指定できます。

メモ: 特別なステップ persist_to_workspace とは異なり、save_cache および restore_cachepaths キーのグロブをサポートしていません。

キーとテンプレートの使用

各キャッシュ キーは、1 つのデータ キャッシュに対応するユーザー定義の文字列です。 動的な値を挿入してキャッシュ キーを作成することができます。これはテンプレートと呼ばれます。 キャッシュ キー内の中かっこで囲まれている部分がテンプレートです。 以下を例に考えてみましょう。

myapp-{{ checksum "package-lock.json" }}

上の例の出力は、このキーを表す一意の文字列です。 ここでは、チェックサムを使用して、package-lock.json の内容を表す一意の文字列を作成しています。

この例では、以下のような文字列が出力されます。

myapp-+KlBebDceJh_zOWQIAJDLEkdkKoeldAldkaKiallQ<etc>

package-lock ファイルの内容が変更された場合、checksum 関数は別の一意の文字列を返し、キャッシュを無効化する必要があることが示されます。

キャッシュの key に使用するテンプレートを選択するうえでは、キャッシュの保存にはコストがかかること、キャッシュを CircleCI ストレージにアップロードするにはある程度の時間がかかることに留意してください。 ビルドのたびに新しいキャッシュが生成されないように、実際に何かが変更された場合にのみ新しいキャッシュが生成されるような key にします。

最初に、プロジェクトの何らかの側面を表す値を含むキーを使用して、キャッシュを保存または復元するタイミングを指定します。 たとえば、ビルド番号が増えたとき、リビジョン番号が増えたとき、依存関係マニフェスト ファイルのハッシュが変更されたときなどです。

以下に、さまざまな目的を持つキャッシュ戦略の例を示します。

- myapp-{{ checksum "package-lock.json" }}

  • package-lock.json ファイルの内容が変わるたびにキャッシュが再生成されます。このプロジェクトのさまざまなブランチで同じキャッシュ キーが生成されます。

    myapp-{{ .Branch }}-{{ checksum "package-lock.json" }}

  • package-lock.json ファイルの内容が変更されるたびにキャッシュが再生成されます。このプロジェクトのブランチでそれぞれ異なるキャッシュ キーが生成されます。

    myapp-{{ epoch }}

  • ビルドのたびに異なるキャッシュ キーが生成されます。

ステップの実行中に、上記のテンプレートが実行時の値に置き換えられ、その置換後の文字列が key として使用されます。 以下の表に、使用可能なキャッシュの key テンプレートを示します。

テンプレート 説明
{{ checksum "filename" }} filename で指定したファイルの内容の SHA256 ハッシュを Base64 エンコードした値。ファイルが変更されると、新しいキャッシュ キーが生成されます。 リポジトリにコミットされるファイルのみを指定できます。 依存関係マニフェスト ファイル (package-lock.jsonpom.xmlproject.clj など) の使用を検討してください。 また、restore_cache から save_cache までの間にファイルの内容が変更されないようにすることが重要です。ファイルの内容が変更された場合、restore_cache のタイミングで使用されるファイルとは異なるキャッシュ キーの下でキャッシュが保存されます。
{{ .Branch }} 現在ビルド中の VCS ブランチ。
{{ .BuildNum }} このビルドの CircleCI ジョブ番号。
{{ .Revision }} 現在ビルド中の VCS リビジョン。
{{ .Environment.variableName }} 環境変数 variableName (CircleCI からエクスポートされる環境変数、または特定のコンテキストに追加した環境変数がサポートされ、任意の環境変数は使用できません)。
{{ epoch }} 協定世界時 (UTC) 1970 年 1 月 1 日午前 0 時 0 分 0 秒からの経過秒数。POSIX や Unix エポックとも呼ばれます。 このキャッシュ キーは、実行のたびに新しいキャッシュを保存する必要がある場合に便利です。
{{ arch }} OS と CPU (アーキテクチャ、ファミリ、モデル) の情報を取得します。 OS や CPU アーキテクチャに依存するコンパイル済みバイナリをキャッシュする場合に便利です (darwin-amd64-6_58linux-amd64-6_62 など)。 See サポートされている CPU アーキテクチャを参照してください。

キーとテンプレートの使用に関する補足説明

  • キャッシュに一意の識別子を定義するときは、 {{ epoch }} などの特定度の高いテンプレート キーを過度に使用しないように注意してください。 {{ .Branch }}{{ checksum "filename" }} などの特定度の低いテンプレート キーを使用すると、キャッシュが使用される可能性が高くなります。
  • キャッシュ変数には、ビルドで使用しているパラメーターも使用できます。たとえば、 v1-deps-<< parameters.varname >> のように指定します。
  • キャッシュ キーに動的なテンプレートを使用する必要はありません。 静的な文字列を使用し、その名前を「バンプ」(変更) することで、キャッシュを強制的に無効化できます。

キャッシュの保存および復元の例

以下に、.circleci/config.yml ファイルで restore_cachesave_cache をテンプレートとキーと共に使用する例を示します。

    docker:
      - image: customimage/ruby:2.3-node-phantomjs-0.0.1
        environment:
          RAILS_ENV: test
          RACK_ENV: test
      - image: circleci/mysql:5.6

    steps:

      - checkout
      - run: cp config/{database_circleci,database}.yml

      # Bundler を実行します
      # 可能な場合は、インストールされている gem をキャッシュから読み込み、バンドル インストール後にキャッシュを保存します
      # 複数のキャッシュを使用して、キャッシュ ヒットの確率を上げます

      - restore_cache:
          keys:
            - gem-cache-v1-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
            - gem-cache-v1-{{ arch }}-{{ .Branch }}
            - gem-cache-v1

      - run: bundle install --path vendor/bundle

      - save_cache:
          key: gem-cache-v1-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle

      - run: bundle exec rubocop
      - run: bundle exec rake db:create db:schema:load --trace
      - run: bundle exec rake factory_girl:lint

      # アセットのプリコンパイルを行います
      # 可能な場合は、アセットをキャッシュから読み込み、アセットのプリコンパイル後にキャッシュを保存します
      # 複数のキャッシュを使用して、キャッシュ ヒットの確率を上げます

      - restore_cache:
          keys:
            - asset-cache-v1-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
            - asset-cache-v1-{{ arch }}-{{ .Branch }}
            - asset-cache-v1

      - run: bundle exec rake assets:precompile

      - save_cache:
          key: asset-cache-v1-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
          paths:
            - public/assets
            - tmp/cache/assets/sprockets

      - run: bundle exec rspec
      - run: bundle exec cucumber

部分的な依存関係キャッシュの使用方法

依存関係管理ツールの中には、部分的に復元された依存関係ツリー上へのインストールを正しく処理できないものがあります。

steps:
  - restore_cache:
      keys:
        - gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
        - gem-cache-{{ arch }}-{{ .Branch }}
        - gem-cache

上の例では、2 番目または 3 番目のキャッシュ キーによって依存関係ツリーが部分的に復元された場合に、依存関係管理ツールによっては古い依存関係ツリーの上に誤ってインストールを行ってしまいます。

カスケード フォールバックの代わりに、以下のように単一バージョンのプレフィックスが付いたキャッシュ キーを使用することで、動作の信頼性が高まります。

steps:
  - restore_cache:
      keys:
        - v1-gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}

キャッシュは変更不可なので、この方法でバージョン番号を増やすことで、すべてのキャッシュを再生成できます。 この方法は、以下のような場合に便利です。

  • npm などの依存関係管理ツールのバージョンを変更した場合
  • Ruby などの言語のバージョンを変更した場合
  • プロジェクトに依存関係を追加または削除した場合

部分的な依存関係キャッシュの信頼性は、依存関係管理ツールに依存します。 以下に、一般的な依存関係管理ツールについて、推奨される部分キャッシュの使用方法をその理由と共に示します。

Bundler (Ruby)

部分キャッシュ リストアを使用しても安全でしょうか? はい。ただし、注意点があります。

Bundler では、明示的に指定されないシステム gem が使用されるため、確定的でなく、部分キャッシュ リストアの信頼性が低下することがあります。

この問題を解決するには、キャッシュから依存関係を復元する前に Bundler をクリーンアップするステップを追加します。

steps:
  - restore_cache:
      keys:
        # ロック ファイルが変更されたら、パターンが一致する範囲を少しずつ広げてキャッシュを復元します
        - v1-gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
        - v1-gem-cache-{{ arch }}-{{ .Branch }}-
        - v1-gem-cache-{{ arch }}-
  - run: bundle install && bundle clean
  - save_cache:
      paths:
        - ~/.bundle
      key: v1-gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}

Gradle (Java)

部分キャッシュ リストアを使用しても安全でしょうか? はい。

Gradle リポジトリは、規模が大きく、一元化や共有が行われることが想定されています。 生成されたアーティファクトのクラスパスに実際に追加されるライブラリに影響を与えることなく、キャッシュの一部を復元できます。

steps:
  - restore_cache:
      keys:
        # ロック ファイルが変更されたら、パターンが一致する範囲を少しずつ広げてキャッシュを復元します
        - gradle-repo-v1-{{ .Branch }}-{{ checksum "dependencies.lockfile" }}
        - gradle-repo-v1-{{ .Branch }}-
        - gradle-repo-v1-
  - save_cache:
      paths:
        - ~/.gradle
      key: gradle-repo-v1-{{ .Branch }}-{{ checksum "dependencies.lockfile" }}

Maven (Java) および Leiningen (Clojure)

部分キャッシュ リストアを使用しても安全でしょうか? はい。

Maven リポジトリは、規模が大きく、一元化や共有が行われることが想定されています。 生成されたアーティファクトのクラスパスに実際に追加されるライブラリに影響を与えることなく、キャッシュの一部を復元できます。

Leiningen も内部で Maven を利用しているため、キャッシュの一部を復元できます。

steps:
  - restore_cache:
      keys:
        # ロック ファイルが変更されたら、パターンが一致する範囲を少しずつ広げてキャッシュを復元します
        - maven-repo-v1-{{ .Branch }}-{{ checksum "pom.xml" }}
        - maven-repo-v1-{{ .Branch }}-
        - maven-repo-v1-
  - save_cache:
      paths:
        - ~/.m2
      key: maven-repo-v1-{{ .Branch }}-{{ checksum "pom.xml" }}

npm (Node)

部分キャッシュ リストアを使用しても安全でしょうか? はい。ただし、npm5 以降を使用する必要があります。

npm5 以降でロック ファイルを使用すると、部分キャッシュ リストアを安全に行うことができます。

steps:
  - restore_cache:
      keys:
        # ロック ファイルが変更されたら、パターンが一致する範囲を少しずつ広げてキャッシュを復元します
        - node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }}
- node-v1-{{ .Branch }}-
        - node-v1-
  - save_cache:
      paths:
        - ~/usr/local/lib/node_modules  # 場所は npm のバージョンによって異なります
      key: node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }}

pip (Python)

部分キャッシュ リストアを使用しても安全でしょうか? はい。ただし、Pipenv を使用する必要があります。

Pip では、requirements.txt で明示的に指定されていないファイルを使用できます。 Pipenv を使用するには、ロック ファイルでバージョンを明示的に指定する必要があります。

steps:
  - restore_cache:
      keys:
        # ロック ファイルが変更されたら、パターンが一致する範囲を少しずつ広げてキャッシュを復元します
        - pip-packages-v1-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
        - pip-packages-v1-{{ .Branch }}-
        - pip-packages-v1-
  - save_cache:
      paths:
        - ~/.local/share/virtualenvs/venv  # このパスは、pipenv が virtualenv を作成する場所によって異なります
      key: pip-packages-v1-{{ .Branch }}-{{ checksum "Pipfile.lock" }}

Yarn (Node)

部分キャッシュ リストアを使用しても安全でしょうか? はい。

Yarn では、部分キャッシュ リストアを実行するために、既にロック ファイルが使用されています。

steps:
  - restore_cache:
      keys:
        # ロック ファイルが変更されたら、パターンが一致する範囲を少しずつ広げてキャッシュを復元します
        - yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
        - yarn-packages-v1-{{ .Branch }}-
        - yarn-packages-v1-
  - save_cache:
      paths:
        - ~/.cache/yarn
      key: yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}

キャッシュ戦略のトレードオフ

使用言語のビルド ツールが依存関係を難なく処理できる場合は、ゼロ キャッシュ リストアよりも部分キャッシュ リストアの方がパフォーマンス上は有利です。 ゼロ キャッシュ リストアでは、依存関係をすべて再インストールしなければならないため、パフォーマンスが低下することがあります。 この問題を回避するには、キャッシュをゼロから作り直すのではなく、依存関係の大部分を古いキャッシュから復元します。

一方、それ以外の言語では、部分キャッシュ リストアを実行すると、宣言された依存関係と矛盾するコード依存関係が作成されるリスクがあり、キャッシュなしでビルドを実行するまでその矛盾は解決されません。 依存関係が頻繁に変更されない場合は、ゼロ キャッシュ リストア キーをリストの最初に配置してみてください。

次に、ビルドにかかる時間を追跡します。 ゼロ キャッシュ リストア (キャッシュ ミス) に伴ってパフォーマンスが大幅に低下することがわかった場合には、部分キャッシュ リストア キーの追加を検討してください。

キャッシュを復元するためのキーを複数列挙すると、部分キャッシュがヒットする可能性が高くなります。 ただし、restore_cache の対象が時間的に広がることで、さらに多くの混乱を招く危険性もあります。 たとえば、アップグレードしたブランチに Node v6 の依存関係がある一方で、他のブランチでは Node v5 の依存関係が使用されている場合は、他のブランチを検索する restore_cache ステップで、アップグレードしたブランチとは互換性がない依存関係が復元される可能性があります。

ロック ファイルの使用

言語の依存関係管理ツールが扱うロック ファイル (Gemfile.lockyarn.lock など) のチェックサムは、キャッシュ キーとして便利に使用できます。

また、ls -laR your-deps-dir > deps_checksum を実行し、

{{ checksum "deps_checksum" }} で参照するという方法もあります。 たとえば、Python で requirements.txt ファイルのチェックサムよりも限定的なキャッシュを取得するには、プロジェクト ルート venv の virtualenv 内に依存関係をインストールし、ls -laR venv > python_deps_checksum を実行します。

言語ごとに異なるキャッシュを使用する

ジョブを複数のキャッシュに分割することで、キャッシュ ミスのコストを抑制できます。 異なるキーを使用して複数の restore_cache ステップを指定することで、各キャッシュのサイズを小さくし、キャッシュ ミスによるパフォーマンスへの影響を抑えることができます。 それぞれの依存関係管理ツールによるファイルの保存方法、ファイルのアップグレード方法、および依存関係のチェック方法がわかっている場合は、言語ごとに (npm、pip、bundler) キャッシュを分割することを検討してください。

高コストのステップのキャッシュ

言語やフレームワークによっては、キャッシュ可能で、キャッシュする方が望ましいものの、大きなコストがかかるステップがあります。 たとえば、Scala や Elixir では、コンパイル ステップをキャッシュすることで、効率が大幅に向上します。 Rails の開発者も、フロントエンドのアセットをキャッシュするとパフォーマンスが大幅に向上することをご存じでしょう。

すべてをキャッシュするのではなく、コンパイルのようなコストがかかるステップをキャッシュすることをお勧めします。

ソースのキャッシュ

CircleCI 1.0 と同様に、git リポジトリをキャッシュすると有効な場合があります。特に大規模なプロジェクトでは、checkout ステップにかかる時間を短縮できます。 以下に、ソースのキャッシュ例を示します。

    steps:
      - restore_cache:
          keys:
            - source-v1-{{ .Branch }}-{{ .Revision }}
            - source-v1-{{ .Branch }}-
            - source-v1-

      - checkout

      - save_cache:
          key: source-v1-{{ .Branch }}-{{ .Revision }}
          paths:
            - ".git"

この例では、restore_cache は最初に現在の git リビジョンからキャッシュ ヒットを探し、次に現在のブランチからキャッシュ ヒットを探します。最後に、すべてのブランチとリビジョンからキャッシュ ヒットを探します。 keys リストが検出されると、最初に一致するキーからキャッシュが復元されます。 複数の一致がある場合は、最も新しく生成されたキャッシュが使用されます。

ソース コードが頻繁に変更される場合は、特定度の高い少数のキーを使用することをお勧めします。 こうすることで、より細かなソース キャッシュが生成され、現在のブランチや git リビジョンが変更されるたびに更新されます。

最も限定的な restore_cache オプション(source-v1-{{ .Branch }}-{{ .Revision }}) を指定した場合でも、ソースのキャッシュはきわめて有効です。たとえば、同じ git リビジョンに対してビルドを繰り返す場合 (API トリガーのビルド) や、ワークフローを使用する場合です。ワークフローを使用するときには、ソースをキャッシュしないと、ワークフロー ジョブごとに同じリポジトリを 1 回ずつ checkout しなければならなくなるためです。

ただし、ソースをキャッシュした場合としなかった場合のビルド時間を比較することは重要です。git clonerestore_cache よりも速いことも少なくありません。

メモ: 組み込みの checkout コマンドを実行すると、git の自動ガベージ コレクションが無効になります。 save_cache を実行する前に、run ステップで git gc を手動で実行すると、保存されるキャッシュのサイズが小さくなります。

関連項目

最適化