プロンプトとコンテキスト
各ジョブは約25のアクションを呼び出し、新しい出力の平均サイズは300 MBと想定します。繰り返されるビルドは入力が共通していることが多いですが、誤ったヒットが発生すると、不正なツールチェーンやシークレットを用いてコンパイルされたアーティファクトを出荷してしまうリスクがあります。キャッシュは最適化のための仕組みです。ミスや停止が発生した場合は実行にフォールバックしなければならず、一方で偽のヒット(誤検出)は正当性のインシデントとなります。アクション結果の検索と不変のアーティファクトストレージを分離し、リモート実行が信頼モデルとキャパシティモデルをどのように変えるかを説明する必要があります。
面接官が見ているポイント
- 決定論的な結果に影響を与えるすべての入力からアクションキーをモデル化しているか。
- 変更可能なアクションメタデータと、不変のコンテンツアドレス指定ストア(CAS)を分離しているか。
- 書き込みをアトミックにし、ダイジェストを検証し、テナント間のキャッシュ汚染を防止しているか。
- キャパシティ、ガベージコレクション、オブザーバビリティ、障害時のフォールバック、移行計画が明示的であるか。
- ビルドキャッシュを一般的なKey-Valueキャッシュと明確に区別しているか( stale-read の許容よりも正当性が最優先される)。
質問すべき確認事項
ビルドが密閉性(hermetic)を持つか、サポートされるOSとアーキテクチャは何か、リモート実行が必須か任意かを確認します。保持期間の目標、テナントおよびリポジトリの境界、最大アーティファクトサイズ、期待されるヒット率の目標、データレジデンシー、シークレットやプロプライエタリなソースコードが出力に含まれる可能性があるかを明確にします。結果をブランチ間やツールチェーンバージョン間で共有できるのか、それともコミットとプラットフォームのタプル内でのみ共有可能なのかを確認します。また、CIが書き込み権限を持ち、開発者マシンは読み取り専用かどうかも尋ねます。
30秒の回答フレームワーク
コマンド、ツールチェーンおよびプラットフォームの識別情報、宣言された入力のMerkleルート、関連する環境変数、ビルド設定を含む正規化されたアクション記述をハッシュ化します。アクションキャッシュはそのキーを出力ダイジェストと結果メタデータにマッピングし、CASはダイジェストによって不変のBLOBを保存します。リーダー側はファイルを実体化する前にメタデータとすべてのBLOBを検証します。ローカルまたはリモートでの実行が成功した場合、まずBLOBをアップロードし、最後にアクション結果を公開することで、部分的な処理結果がヒットになるのを防ぎます。名前空間、認証済み書き込み、クォータ、サンドボックス化によりテナントの漏洩を防ぎます。キャッシュ障害時はミスを返して制御された実行を行い、メトリクスとサンプリングされたクリーンリビルドによって偽ヒットを検出します。
ステップごとの詳細解説
1. キーとストレージ境界の定義
アクションコマンド、コンパイラおよびリンカーのバージョン、プラットフォーム、宣言された入力、関連フラグ、ホワイトリスト化された環境変数、外部依存関係のロックファイルを正規化します。入力ツリーをMerkleルートとしてハッシュ化します。シークレットや非決定論的なタイムスタンプは除外します。アクションがhermeticでない場合は、キャッシュ不可としてマークするか、意図的に範囲を限定します。アクション結果はCASとは別に保存します。結果には出力ファイル名、ダイジェスト、サイズ、終了コード、および任意のstdout/stderrダイジェストが含まれます。CASオブジェクトは不変であり、そのダイジェストによってのみアドレス指定されます。
2. ヒット、ミス、公開パスの設計
読み取り時は、テナントおよびアクションキーの名前空間を複製されたメタデータサービスにルーティングして結果を取得し、不足しているCAS BLOBを並列で取得します。ファイルを公開する前にサイズとダイジェストを検証します。ミス時は、ローカルまたはサンドボックス化されたワーカーで実行します。検証済みBLOBを冪等なダイジェスト操作でアップロードし、アクション結果を単一の条件付き公開でコミットします。複数の並行ライターが同じBLOBをアップロードすることは可能ですが、参照されるすべてのBLOBを含む完全な結果のみが可視化されます。破損したBLOBやメタデータの不一致はミスとして扱われ、アラートが発生します(ヒットと見なされることは決してありません)。
3. 分散、分離、セキュリティの追加
アクション結果の検索には、障害ゾーン間でレプリケーションされたコンシステントハッシュまたはメタデータサービスパーティションマップを使用します。大きなCASデータはオブジェクトストレージまたはシャーディングされたBLOB層に配置し、ホットなメタデータは低レイテンシストレージに保持します。すべてのリクエストを認証し、リポジトリおよびテナントの名前空間を認可し、開発者マシンはデフォルトで読み取り専用にします。転送中および保存時のデータを暗号化し、テナントごとのクォータを適用し、リモートアクションをサンドボックス化します。ポリシーで明示的に許可されていない限り、テナント間で重複排除を行わないでください。ダイジェスト単体で認可をバイパスしてはなりません。
4. キャパシティとライフサイクル管理の計画
提示されたワークロードは、1日あたり20,000ジョブ × 25アクション = 1日あたり500,000件のルックアップであり、平均で約5.8リクエスト/秒です。20倍のバースト時では、並列BLOB読み取りを除いて約120リクエスト/秒となります。アクションの10%が新しい300 MBの出力を生成する場合、非圧縮の取り込み量は1.5 TB/日になります。圧縮と重複排除によってストレージは削減されますが、設計では数テラバイトのオブジェクト容量と帯域幅のヘッドルームを確保する必要があります。ガベージコレクションはアクティブなアクション結果とマニフェストから開始し、そのダイジェスト参照を追跡し、猶予期間を適用した上で、サイズクォータとLRUまたは経過時間ポリシーを組み合わせます。到達可能なBLOBは絶対に削除せず、テナントごとに公平なクォータを強制します。
5. 障害と正当性のオブザーバビリティの確保
キャッシュタイムアウト、権限エラー、BLOBの欠落、ダイジェスト不一致、バックエンドの利用不可をそれぞれ個別の事象として扱います。ミス時は実行に移行できます。長時間の停止時には、CIがリトライストームに陥るのを防ぐため、アドミッション制御、ローカルキャッシュ優先、およびリトライ制限が必要です。リポジトリ、アクションクラス、プラットフォーム、ツールチェーンごとにヒット率を追跡し、ルックアップレイテンシ、BLOB帯域幅、アップロード中断、エビクション、データ破損、テナント拒否を監視します。ローカルキャッシュを削除したクリーンリビルドを定期的に実行し、実行ログや出力ダイジェストを比較して、非決定論性や偽ヒットを検出します。
優れた回答例
私は2つのサービスを公開します。認証されたアクションキャッシュインデックスと、不変のCASです。アクションキーは、正規化されたコマンド、コンパイラおよびプラットフォームの識別情報、宣言された入力のMerkleルート、ホワイトリスト化された環境変数、フラグ、ロックされた外部依存関係を網羅します。ヒットした場合は出力ダイジェストが返され、クライアントは実体化する前にそれらのBLOBを検証してダウンロードします。ミスした場合はサンドボックス内で実行し、BLOBを冪等にアップロードして検証し、すべての参照が存在することを確認した後にのみ結果を公開します。アクションメタデータはテナントおよびリポジトリごとにレプリケーションされ、CASデータはシャーディングされるかゾーンをまたいでオブジェクトストレージに配置されます。読み取りはキャッシュ障害時に実行へフォールバック可能です。書き込みは信頼されたCIに制限され、クォータ、暗号化が適用され、デフォルトではテナント間の再利用は行われません。ピーク時約120ルックアップ/秒および数テラバイトのストレージに対応するサイジングを行い、ヒット率、ダイジェスト不一致、非決定論的アクション、並行ライター、ツールチェーンのアップグレード、GCの到達可能性、障害復旧を検証します。
よくある落とし穴
- コンパイラ、フラグ、プラットフォーム、環境変数、ロックされた依存関係を除外し、ソースファイルのみをハッシュ化してしまう。
- アクション結果とその出力BLOBを単一の変更可能なレコードとして扱い、部分的な公開を許してしまう。
- 名前空間の認可を確認せずに、テナント間でダイジェストを共有してしまう。
- キャッシュ利用不可時の挙動を、制御されたミスとフォールバックではなく、ビルド全体の停止として設計してしまう。
- 単一のグローバルLRUを使用し、アクティブなアクション結果からの参照を追跡せずにBLOBを削除してしまう。
- stdoutやstderrのデータ量をキャッシュヒットのメトリクスと呼んでしまう(実行戦略と明示的なヒットカウンターが必要)。
- クリーンリビルド、マシン間の再現性、非決定論的アクションのテストを行わずに高いヒット率を主張してしまう。
フォローアップの質問と回答
コンパイラツールチェーンがアップグレードされたものの、コマンドラインが変更されていない場合はどうなりますか?
ツールチェーンの識別情報は、通常、固定ダイジェストまたはバージョン付き実行イメージを介してアクションキーの一部に含める必要があります。移行時にはロールバック用に古い名前空間のデュアルリードを行うこともできますが、新しい名前空間に書き込み、ミスを測定する必要があります。ソースとフラグが一致しているからといって、古い出力を決して再利用してはなりません。
キャッシュ汚染が発生した場合、どのように復旧しますか?
信頼できない書き込みを停止し、影響を受けた名前空間を隔離し、監査ログから不正なアクション結果と到達可能なBLOBを特定します。アクションインデックスを無効化し、信頼できる出力を再ビルドして、検証済みの実行結果からキャッシュを再構成します。復旧中はキャッシュの使用を任意とし、インシデントレビューのために証拠を保持します。
ビルド実行中にアクションがバージョン未固定の依存関係をダウンロードした場合はどうなりますか?
それはhermeticではありません。依存関係が固定され、取得されたバイトデータが入力クロージャに含まれるようになるまで、キャッシュ不可としてマークします。リポジトリスコープの短いTTLを明示的な緊急例外として設けることは可能ですが、決定論的な再利用として扱うべきではありません。
CIを停止させることなくローカルキャッシュから移行するにはどうすればよいですか?
リモート読み取りをシャドウモードで実行し、ローカルとリモートのキーおよび出力ダイジェストを比較した上で、リポジトリの小規模なコホートに対してリモートヒットを有効にします。ローカル実行とローカルキャッシュへのフォールバックを維持し、書き込みは信頼されたCIに限定し、ヒット率、レイテンシ、偽ヒットのチェックに合格した後にのみ範囲を拡大します。