ルール記述の課題

問題を報告する ソースを表示 夜間 · 7.3 · 7.2 · 7.1 · 7.0 · 6.5

このページでは、具体的な問題と課題の概要を説明します。 効率的な Bazel ルールを作成できます。

要件の概要

  • 前提条件: 正確性、スループット、使いやすさ、および遅延
  • 前提: 大規模なリポジトリ
  • 前提: BUILD 形式の説明言語
  • 過去: 読み込み、分析、実行の間のハードな分離 古いが、引き続き API に影響する
  • 本質的: リモート実行とキャッシュ保存は難しい
  • 本質的: 変更情報を使用して正確かつ高速な増分ビルドを作成する 独特なコーディング パターンが必要
  • 本質的: 二次時間とメモリ消費の回避は困難

前提条件

ビルドシステムには、次のような前提条件があります。 正確性、使いやすさ、スループット、大規模リポジトリの特徴です。「 以降のセクションでは、これらの前提条件について説明し、 作成することが重要です。

正確性、スループット、使いやすさ、遅延

ビルドシステムは、何よりもまず パフォーマンスが向上します特定のソースツリーについて、 出力ツリーの外観に関係なく、同じビルドは常に同じである必要があります。 できます。最初の近似では、Bazel はすべてのノードを認識する必要がある 入力されたビルドステップがある場合にそのステップを再実行できるようにする 変化します。Bazel は液漏れするため、精度には制限があります。 ビルドの日付 / 時刻など一部の情報は除外され、特定のタイプのログは ファイル属性の変更などの変更を通知します。サンドボックス化 宣言されていない入力ファイルの読み取りを防ぐことで正確性を確保できます。その他 正確性の問題が いくつか確認されています そのほとんどは Fileset ルールまたは C++ ルールに関連していますが、 学習しますYouTube では、この問題の修正に長期的な取り組みを続けています。

ビルドシステムの 2 つ目の目標は、高スループットを実現することです。私たちは 現状で何ができるかの限界を永久に押し広げる リモート実行サービス用のマシン割り当ても行います。リモート実行が サービスが過負荷になると、誰も仕事を遂行できなくなります。

次は使いやすさです。同じ(または のフットプリントを指定している場合は、リモート実行サービスの 簡単に操作できます。

レイテンシとは、ビルドの開始から意図したとおりにビルドが完了するまでにかかる時間を指します。 テストの合格 / 不合格のテストログ、またはエラー BUILD ファイルに入力ミスがあるというメッセージが表示される。

多くの場合、これらの目標は重複します。レイテンシはスループットの関数である リモート実行サービスの正確さに左右されます。

大規模なリポジトリ

ビルドシステムは、大規模なリポジトリや 1 台のハードドライブには収まらないため、 ほぼすべてのデベロッパーのマシンで 完全な購入手続きが行えます中規模ビルド 数万個の BUILD ファイルを読み取って解析し、 何十万もの glob があるとします。理論上は、すべての読み取りトークンを BUILD ファイルが 1 台のマシン上にあるため、 ある程度の時間とメモリが必要です。そのため、BUILD ファイルには必ず 個別に読み込んで解析できます

BUILD 形式の説明言語

ここでは、サービス アカウント キーの ライブラリとバイナリのルールの宣言内の BUILD ファイルにほぼ類似しています 理解できます。BUILD ファイルを個別に読み取って解析できます。 可能な限りソースファイルを見ることも 避けています あります)。

歴史にゆかりがある場所

課題の原因となる Bazel バージョンには違いがあり、 その概要を次のセクションで説明します。

読み込み、分析、実行のハードな分離は時代遅れだが API に影響する

技術的には、ルールがデバイスの入出力ファイルを知っていれば十分です。 リモート実行に送信される直前のアクションです。ただし、 元の Bazel コードベースでは、読み込みパッケージが厳密に分離されていました。その後、 構成(基本的にはコマンドライン フラグ)を使用してルールを分析する アクションの実行のみです。この区別は、現在も Rules API に組み込まれています。 現在は、Bazel のコアでは不要になりました(詳細は下記を参照)。

つまり、Rules API ではルールの宣言型の記述が必要です。 インターフェース(どの属性、属性のタイプなど)ですか?他にも 例外として、読み込みフェーズで API がカスタムコードを実行できる 暗黙的な出力ファイルの名前と属性の暗黙的な値を計算します。対象 たとえば、「foo」という名前の java_library ルールが出力が暗黙的に生成され、 「libfoo.jar」。ビルドグラフ内の他のルールから参照できます。

さらに、ルールの分析では、ソースファイルの読み込みや アクションの出力その代わりに、部分有向二部生成関数を ビルドステップと、ルールによってのみ決定される出力ファイル名のグラフ 依存関係が存在します。

本質的

ルールの作成を困難にする固有の特性がいくつかあります。 その最も一般的なものを以降のセクションで説明します。

リモート実行とキャッシュ保存は難しい

リモート実行とキャッシュ保存は、大規模なリポジトリでのビルド時間を短縮します。 単一の Compute Engine で実行する場合と比べて、約 2 桁もの あります。しかし、その実行が必要な規模は驚異的です。Google の リモート実行サービスは、リモート実行サービスごとに 2 つ目は、プロトコルが不必要な往復を慎重に回避し、 サービス側では不要な作業が不要になります

現時点でプロトコルを使用するには、ビルドシステムがすべての入力を知っている必要があります。 事前に与えるか、すると、ビルドシステムが一意のアクションを スケジューラにキャッシュ ヒットを要求します。キャッシュヒットが見つかると スケジューラは出力ファイルのダイジェストを返します。ファイル自体は 後ほど説明します。ただし、これにより Bazel の制限が すべての入力ファイルを事前に宣言する必要があります。

変更情報を使用して正確かつ迅速な増分ビルドを行うには、通常とは異なるコーディング パターンが必要になる

上記では、Bazel が正確であるためには、すべての入力を Bazel が知っている必要があると主張しました。 そのビルドステップが最新であるかどうかを検出するために、 維持します。パッケージの読み込みとルール分析についても同様です。 Skyframe でこれに対応するように設計されている 見ていきましょう。Skyframe はグラフ ライブラリと評価フレームワークで、 (例: build //foo with these options)、それを その構成要素が評価、組み合わされて、 表示されます。このプロセスの一環として、Skyframe はパッケージを読み取り、ルールを分析し、 アクションを実行します。

Skyframe は各ノードで、特定のノードがコンピューティングに使用したノードを正確に追跡します。 目標ノードから入力ファイル( Skyframe ノードでもあります)。このグラフをメモリ内で明示的に表現すると これにより、ビルドシステムは、特定のルールによって影響を受けるノードを 入力ファイルへの変更(入力ファイルの作成や削除を含む)、 出力ツリーを目的の状態に復元するための最小限の作業量。

その一環として、各ノードで依存関係の検出プロセスが実行されます。各 ノードは依存関係を宣言してから、それらの依存関係のコンテンツを使用して さらに依存関係を宣言します。原則的には ノードあたりのスレッド数です。しかし中規模のビルドには 現行の Java では簡単に実現できない数千の Skyframe ノード (歴史的理由から、現時点では Java の使用に縛られているため、 軽量のスレッドや継続は使用しません)。

Bazel は固定サイズのスレッドプールを使用します。ただし、つまりノードに まだ利用できない依存関係を宣言している場合、その依存関係を 依存関係の問題が解決されたときに(別のスレッドなどで)再起動する必要が できます。つまり、ノードではこれを過度に行うべきではありません。 N 個の依存関係を順番に宣言しているノードは、N 回再起動される可能性があります。 O(N^2) 時間が必要になります。代わりに、事前の一括申告を目指します。 コードの再編成や、コードの分割や依存関係の 1 つのノードを複数のノードに分割して再起動の回数を制限します。

なお、この技術は現在 Rules API ではご利用いただけません。使用する代わりに、 Rules API は引き続き、読み込み、分析、 学習しますただし、基本的な制限は、リソースへのすべてのアクセス 他のノードがフレームワークを経由し、Google Cloud が 対応する依存関係があります。ビルドシステムで使用する言語や ルールが記述される場所(実際の場所ではなく、 ルールの作成者は、これらのルールをバイパスする標準のライブラリやパターンを スカイフレーム。Java では、java.io.File だけでなく、あらゆる形式の リフレクションを行うライブラリも存在します。依存関係をサポートするライブラリ そのような低レベル インターフェースの挿入も、例外なく正しく スカイフレーム。

ルール作成者を完全な言語ランタイムに公開しないようにすることを強く推奨します。 あります。このような API を誤って使用する危険性は大きすぎます。 過去のいくつかの Bazel バグは、安全でない API を使用したルールが原因で発生しました。 ただし、ルールは Bazel チームや他のドメイン専門家が記述したものです。

二次的な時間とメモリ使用量の回避が困難

さらに悪いことに、Skyframe によって課される要件とは別に、 従来の Java の制約、Rules API の時代遅れなど、 2 次的な時間またはメモリ消費量を誤って導入してしまうのは、 ビルドシステムで問題に注意する必要があります。2 つのモデル 2 次的なメモリ消費を引き起こすパターン(つまり、 二次時間消費)で表します。

  1. ライブラリ ルールの連鎖 - ライブラリ ルール A が B に依存し、C に依存し、 できます。次に、この推移的クロージングに対してなんらかのプロパティを計算します。 たとえば、Java ランタイム クラスパスや、Java 言語の C++ リンカー コマンドなど、 表示されます。単純に言えば、標準的なリストの実装を採用するかもしれません。ただし、 これにより、すでに二次的メモリ消費量が導入されています。 クラスパスにエントリが 1 つ、2 つ目のエントリが 2 つ、3 つ目のエントリが 3 つというように、 つまり、合計で 1+2+3+...+N = O(N^2) 個のエントリになります。

  2. 同じライブラリ規則に依存するバイナリルール - 同じライブラリに依存する一連のバイナリが (たとえば、同じテストを行う多数のテストルールがある場合や、 使用できます。N 個のルールのうち、半分がバイナリルールで、 ライブラリルールを使用します。今度は、各バイナリがファイルのコピーを作成し、 ライブラリ ルールの推移的クロージャに対して計算されたプロパティ。 C++ リンカー コマンドラインで指定することもできます。たとえば、 C++ リンク アクションのコマンドライン文字列表現を拡張できます。該当なし N/2 要素のコピーは O(N^2)メモリです。

二次計算を回避するカスタム コレクション クラス

Bazel はどちらのシナリオでも大きな影響を受けるため、Google は カスタム コレクション クラスを使用してメモリ内の情報を効果的に圧縮し、 各ステップでコピーせずに済みますデータ構造はほとんどすべて 意味があるので、これを depset (内部実装では NestedSet とも呼ばれます)。生成 AI の Bazel のメモリ消費量を削減するために過去数年間に実施された変更が、 以前使用していたものの代わりに depset を使用するように変更しました。

残念ながら、depsets を使用しても、すべての問題が自動的に解決されるわけではありません。 特に、各ルールで依存関係を反復処理するだけでも、 2 次時間消費で表します。内部的には、NestedSets にはヘルパー メソッドもあります。 通常のコレクション・クラスとの相互運用性を促進するため残念ながら 誤って NestedSet をこれらのメソッドのいずれかに渡すと、 メモリ消費を二次関数的に紹介します。