コンテンツにスキップ

Plugin-side Cache Producers (slice 6)

ステータス:v0.1.0スライス(slice)6規範的。プラグイン作成者がキャッシュ済みプロデューサーを宣言するためのサーフェス(surface)を固定します——Plugin::Base.producer DSL・Plugin::Base#cache_for呼び出し可能オブジェクト・自動的なPluginEntryの追加・plugin.<manifest.id>.キャッシュidサンドボックス。これらのサーフェスの背後にある設計上の決定はADR-7 §「スライス6」に記録されています;このドキュメントがADRと矛盾する場合、ADRが優先されます。

Rigorの永続キャッシュ(ADR-6、v0.0.8/v0.0.9)は(producer_id, params, descriptor)をキーとするバイナリエントリーの単一シャードディレクトリです。スライス6はその契約(contract)のプロデューサー側をプラグイン作成者に拡張することで、計算コストが高いプラグイン貢献(スキーマの解析、動的メンバーテーブルの構築、生成メタデータのインデックス作成)がrigor checkの実行をまたいでキャッシュされ、入力が変更されたときに正しく無効化されるようにします。

ADR-7 §「スライス6」は3つの実装上の選択を固定します:

  • 6-A. DSL宣言(Plugin::Base.producer)と命令型ヘルパー(Plugin::Base#cache_for)のハイブリッド。宣言はidとシリアライザのペアを持ち;ヘルパーがラウンドトリップを実行するため、プラグイン作成者はCache::Descriptorを手作業で構築する必要がありません。
  • 6-B.ローダー/サービスヘルパーがすべてのcache_forラウンドトリップにプラグインごとのPluginEntryテンプレート(id・バージョン・config_hash)を自動的に追加します。プラグインid・バージョン・config不変条件が構築時に強制されます。
  • 6-C.プラグインが宣言したプロデューサーidにはplugin.<manifest.id>.が自動的にプレフィックスとして追加されるため、プラグインキャッシュは組み込みプロデューサー(rbs.*など)や互いから切り離されたサンドボックスに保たれます。

パブリックサーフェス(ドリフト固定済み)

Section titled “パブリックサーフェス(ドリフト固定済み)”

Rigor::Plugin::Base.producer(id, serialize: nil, deserialize: nil, &block)

Section titled “Rigor::Plugin::Base.producer(id, serialize: nil, deserialize: nil, &block)”

プロデューサーを登録するクラスレベルDSL。ブロックはプロデューサー本体です;instance_execを通じて実行されるため、ブロック内のselfはプラグインインスタンス——io_boundaryservicesmanifestconfigがすべてスコープ内にあります。ブロックは呼び出しサイトのparamsハッシュを唯一の引数として受け取ります;paramsCache::Descriptor#cache_key_for(v0.0.8)に従ってキャッシュキーに混合されます。

serialize: / deserialize:Cache::Store#fetch_or_computeにそのまま転送されます。デフォルトのラウンドトリップはv0.0.9の呼び出し可能サーフェスに従うMarshal.dump / Marshal.loadです;返り値がMarshalクリーンでないプロデューサー(RBS::Locationメンバーを持つRBSネイティブオブジェクト・生のIOなど)は独自のペアを提供しなければなりません(MUST)。

Plugin::Base.producersは凍結された{ id => entry }スナップショットを返します。スーパークラスからの継承されたプロデューサーは表面化されません——ローダーは登録ごとに1つのサブクラスをインスタンス化し、プロデューサーテーブルはフラットのままです。

プラグインごとにメモ化されたRigor::Plugin::IoBoundary(スライス2)。境界が蓄積したエントリーがcache_forラウンドトリップのキャッシュ無効化に使われます: cache_forが呼び出される前にio_boundaryを通じて行われた読み込みはディスクリプタに折り込まれます。#read_file(path):digestFileEntryを記録します;#open_url(url)"url:#{url}"でキーされたConfigEntryを記録し、そのvalue_hashはレスポンスボディのSHA-256です。これにより、変更されたリモートペイロードは変更されたファイルと同じ方法でスライス(slice)を無効化します。以下の「無効化契約」を参照してください。

Rigor::Plugin::Base#glob_descriptor(roots, *patterns)

Section titled “Rigor::Plugin::Base#glob_descriptor(roots, *patterns)”

roots下でpatternsのいずれかにマッチして見つかったファイルごとに:digestFileEntryを1つfiles:スロットに持つCache::Descriptorを返す発見グロブヘルパーです(File.join(root, pattern)で結合;複数パターンはユニオン(union、合併型とも)になります)。結果をcache_fordescriptor:として渡すと、発見されたファイルの集合をスキャンするプロデューサー(あらゆるファクトリー、あらゆるapp/policies/**/*.rb)が、いずれかのファイルのコンテンツ変更・追加・削除で無効化されます——これは、コールドコールでプロデューサーがまだファイルを読んでいないためにIoBoundary単独では取り逃すケースです。rootsはプロジェクトルートからの相対か絶対です。ヘルパーはマッチしたファイルごとに呼び出し時点で1回のSHA-256読み込みを支払います;10〜100ファイル範囲の発見グロブでは、ミス時のプロデューサーのパース&ウォークに比べて無視できる程度です。これは手書きの発見ダイジェストディスクリプタ(rigor-factorybotがヘルパーへ移行する前に独自に再発明していたもの)の代わりにサポートされる方法です。

Rigor::Plugin::Base#cache_for(producer_id, params: {}, descriptor: nil)

Section titled “Rigor::Plugin::Base#cache_for(producer_id, params: {}, descriptor: nil)”

名前付きプロデューサーのキャッシュラウンドトリップを実行する呼び出し可能オブジェクトを返します。その呼び出し可能オブジェクトは、呼び出されるとキャッシュされた値(ヒット時)またはプロデューサーブロックの実行結果(ミス時)を返し、結果を書き込みます。

services.cache_storenil(例:CLI --no-cache)の場合、呼び出し可能オブジェクトはキャッシュをバイパスしてプロデューサーブロックを毎回実行します——組み込みプロデューサーのv0.0.9キャッシュサーフェスと同じセマンティクスです。

プロデューサーidにはplugin.<manifest.id>.が自動的にプレフィックスとして追加されます;manifest.id = "rails"のプラグインに:schema_tableとして登録されたプロデューサーのキャッシュストアレイアウトは<root>/plugin.rails.schema_table/<2-prefix>/<62-suffix>.entryにあります。

オプションのdescriptor:キーワード引数はプラグイン作成者が自動構築ディスクリプタに合成したい追加のCache::Descriptor行を提供します——通常はgemバージョンのGemEntry・設定ファイルのFileEntryダイジェスト・または{IoBoundary}が自身でキャプチャできない外部状態のConfigEntry行です。渡されたディスクリプタは自動構築されたもの(PluginEntryテンプレート+境界読み込み)とCache::Descriptor.composeを通じて流れます;スロットごとの競合はCache::Descriptor::Conflictを発生させ、異なる入力が暗黙に上書きされることなく表面化します。

キャッシュディスクリプタ合成(6-B)

Section titled “キャッシュディスクリプタ合成(6-B)”

Plugin::Base#cache_forは以下からディスクリプタを自動組み立てします:

  • プラグインのPluginEntryテンプレート(id, version, config_hash)config_hashは正規化されたプラグインconfig(キーソート済み・再帰的なSymbol→String変換)のSHA-256であるため、config:の値が異なる同じプラグインの2つのインスタンスは異なるキャッシュスライスに置かれます。
  • プラグインのIoBoundary#cache_descriptorcache_forが呼び出される時点で境界が記録したすべての:digestFileEntry
  • ユーザーのparams:ハッシュ(Descriptor#cache_key_forを通じてキャッシュキーに混合される)。

プラグイン作成者はディスクリプタを手動で構築しません。カスタムディスクリプタ拡張(境界の読み込みを超えた追加のFileEntry / GemEntry / ConfigEntry行)は将来のAPIで対応します;スライス6は自動構築パスのみを出荷します。

IoBoundary統合はcache_forが呼び出される前に行われた読み込みのみを反映します。推奨パターン:

class MyRailsPlugin < Rigor::Plugin::Base
manifest(id: "rails", version: "0.1.0")
producer :schema_table do |params|
schema = io_boundary.read_file(params.fetch(:schema_path))
parse_schema(schema, params.fetch(:table))
end
def schema_for(table)
schema_path = "db/schema.rb"
io_boundary.read_file(schema_path) # populate boundary BEFORE cache_for
cache_for(:schema_table, params: { schema_path: schema_path, table: table }).call
end
end

cache_forの前のread_fileはディスクリプタに格納される:digestFileEntryを記録します;ファイルが実行間で変更された場合、ダイジェストが変わり、キャッシュキーが変わり、cache_forはプロデューサーにフォールスルーします。プロデューサー本体は同じパスのファイルを再読み込みします;キャッシュミス時に境界が再びデータを収集し、事後のダイジェストが新しいエントリーに書き込まれます。

より豊かな無効化(gemバージョン・外部設定ファイル・兄弟プラグインの状態)を求めるプラグイン作成者は現在それらをparamsハッシュに合成します;将来の拡張がcache_forに明示的なディスクリプタパラメータを追加するかもしれません。

キャッシュidサンドボックス(6-C)

Section titled “キャッシュidサンドボックス(6-C)”

Plugin::Base#cache_forはプロデューサーidをplugin.<manifest.id>.<id>に書き換えるため、プラグイン作成者は組み込みプロデューサー(現在は接頭辞なしのrbs.*idを使用)や互いと衝突できません(すべてのプラグインのidはそれぞれのマニフェストidネームスペース下に置かれます)。プレフィックスは既存のCache::Store::VALID_PRODUCER_ID = /\A[a-z][a-z0-9._-]*\z/正規表現の範囲内にあります;ディスク上の帰属はrigor check --cache-statsを通じて明確に確認できます。

スライス6が意図的に行わないこと

Section titled “スライス6が意図的に行わないこと”
  • v0.0.9のメソッドごとのReflectionキャッシュの持ち越しの再試行。ADR-7 §「スライス6-D」に従い、その作業は別のv0.1.xチケットに移管されており、エンジン内部の回帰調査が新しいパブリックプラグインAPIと絡み合わないようにしています。
  • マシン間キャッシュ共有。ADR-6に従い、キャッシュは単一マシン用です;プラグイン側プロデューサーはその制約を継承します。
  • LRU退去/サイズ上限。プラグインキャッシュはADR-6で説明された無制限レイアウトを共有します;ユーザーは必要に応じて--clear-cacheを実行します。

© 2026 TypedDuck. Licensed under CC BY-SA 4.0.