Ractor 安全なカウンターキャッシュ設定の導入

rails/rails

ActiveRecord の内部で使用される ActiveRecord::CounterCaches._counter_cache_columnscounter_cached_association_names をデフォルトで freeze し、Ractor 間で安全に共有できるようにしました。機能的な振る舞いは変わらず、マルチスレッド/マルチRactor 環境での不変性が確保されます。

背景

これまで _counter_cache_columnscounter_cached_association_names は可変の配列 として定義されており、Ractor にオブジェクトを渡す際に共有状態が予期せぬ変更を招くリスクがありました。Ractor はデータをコピーせずに共有できるオブジェクトに対して不変性を要求しますが、Mutable 配列はその条件を満たさないためエラーが発生する可能性がありました。この PR はその安全性の欠如を解消し、Ractor 環境でもカウンターキャッシュ機能をそのまま利用できるようにすることが目的です。

技術的な変更

CounterCache モジュールの class_attribute 定義を変更し、デフォルト値を凍結した空配列に置き換えました。class_attribute :_counter_cache_columns, instance_accessor: false, default: [].freezeclass_attribute :counter_cached_association_names, instance_writer: false, default: [].freeze にすることで、属性が生成時点から変更不可となります。

@@
-      class_attribute :_counter_cache_columns, instance_accessor: false, default: []
-      class_attribute :counter_cached_association_names, instance_writer: false, default: []
+      class_attribute :_counter_cache_columns, instance_accessor: false, default: [].freeze
+      class_attribute :counter_cached_association_names, instance_writer: false, default: [].freeze

belongs_to ビルダーでのカウンターキャッシュ追加ロジックを凍結付き代入に変更しました。古い実装は |= 演算子で配列を拡張していましたが、変更後は (existing | [new]).freeze の結果を再代入することで、常に凍結された配列が保持されます。

@@
-      klass._counter_cache_columns |= [cache_column] if klass && klass.respond_to?(:_counter_cache_columns)
-      model.counter_cached_association_names |= [reflection.name]
+      klass._counter_cache_columns = (klass._counter_cache_columns | [cache_column]).freeze if klass && klass.respond_to?(:_counter_cache_columns)
+      model.counter_cached_association_names = (model.counter_cached_association_names | [reflection.name]).freeze

スキーマ読み込み時の集合演算でも凍結を適用し、load_schema! 内で counter_cached_association_names を更新した直後に .freeze しています。これにより、スキーマロード後の属性も不変性が保証されます。

@@
-        self.counter_cached_association_names |= association_names
+        self.counter_cached_association_names = (counter_cached_association_names | association_names).freeze

テストスイートに Ractor 共有性の検証を追加し、ActiveSupport::Testing::RactorsAssertions をインクルードして assert_ractor_shareable マクロで属性が共有可能であることを確認しています。このテストは新しい凍結ロジックが正しく機能していることを CI で保証します。

@@
-  require "active_support/core_ext/enumerable"
+  require "active_support/core_ext/enumerable"
+  require "active_support/testing/ractors_assertions"
@@
-  class CounterCacheTest < ActiveRecord::TestCase
+  class CounterCacheTest < ActiveRecord::TestCase
+    include ActiveSupport::Testing::RactorsAssertions
@@
-  test "counter cache configuration is ractor shareable" do
-    assert_ractor_shareable SpecialReply.counter_cached_association_names
-    assert_ractor_shareable SpecialReply._counter_cache_columns
-  end
+  test "counter cache configuration is ractor shareable" do
+    assert_ractor_shareable SpecialReply.counter_cached_association_names
+    assert_ractor_shareable SpecialReply._counter_cache_columns
+  end

設計判断

属性を凍結する方針は、既存の API をそのまま残しつつ不変性を保証する最小侵襲のアプローチです。代替案としては新しい設定キーやコピーオンライトのロジックを導入することが考えられましたが、freeze による単一行の代入で済むため、互換性リスクが低く、コードベースへの影響範囲も限定的です。さらに、class_attribute のデフォルトを凍結した配列にすることで、オブジェクト生成時点から安全性が確保され、Ractor での共有に関するバグが事前に防止されます。この設計は「不変性をデフォルトに」する Ruby の慣例に沿っており、将来的な拡張や他の内部キャッシュ実装への適用もしやすい形となっています。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
cc9d524a

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ⚠ WARNING

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的変更は記載されているが、記事全体の結論・まとめセクションが欠如しているため警告とします。

カスタムMarkdown構文 ⚠ WARNING

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックはファイル名付きシンタックスハイライトが正しく使用されている。ただし PR リンクは `[PR #57955](...)` と記載されており、推奨形式の `[#57955](...)` から若干逸脱しているため警告です。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Rails エンジニア向けの高度な技術内容で、初心者向けの過度な説明はなく適切です。

パラグラフ・ライティング ⚠ WARNING

トピックセンテンス・1段落1トピック・段落長

各セクションは冒頭に要点が示されているが、セクションごとの結論パラグラフが不足しており、段落構成が完全に「総論→各論→結論」になっていない点が見受けられます。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事中のコードブロックは提供された Diff と完全に一致しており、正確に反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

使用されている技術用語は PR 内容と一致し、誤用や不適切な表現はありません。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的説明は Diff と PR の記述に基づき正確で、根拠のある主張となっています。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

全ての事実は PR のタイトル、説明、Diff に裏付けられており、捏造や推測はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR 番号 #57955 など数値情報は正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR タイトルの意味合いを正確に反映しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

外部知識(バージョンサポート状況等)の記述はなく、PR 以外の情報は含まれていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現のずれは見られません。