Active RecordのRactor対応:定数のfreeze化とキャッシュの共有化

rails/rails

Active Recordの各モジュールで定数やキャッシュオブジェクトを積極的にfreezeすることで、Ractorコンテキストでの安全な共有を可能にする変更が導入されました。#57323に続くRactor対応の一環です。

背景

RactorはRubyの並行実行機構であり、Ractor間でオブジェクトを共有するには、そのオブジェクトが shareable(共有可能)である必要があります。Rubyのshareableオブジェクトの条件のひとつが「凍結(freeze)されていること」です。Active Recordのモジュールがクラス・定数レベルで可変オブジェクトを保持している場合、そのモジュールはRactorをまたいで安全に利用できません。

前回の#57323ではStyle/MutableConstant Copを有効化し、リテラルの定数定義に対してfreezeを強制する仕組みが整備されました。今回のPR #57475はその続きとして、Active Record内の具体的なモジュールに対してfreezeと定数化を適用しています。

技術的な変更

このPRでは4ファイルにわたり、可変オブジェクトのfreezeと、メモ化パターンから定数参照への切り替えが行われています。変更はいずれも「Ractor間で共有されうるオブジェクトを不変にする」という一貫した方針に従っています。

ReadonlyAttributesのデフォルト値のfreezeactiverecord/lib/active_record/readonly_attributes.rb)では、_attr_readonlyのデフォルト値として渡していた[][].freezeに変更しました。

変更前:

class_attribute :_attr_readonly, instance_accessor: false, default: []

変更後:

class_attribute :_attr_readonly, instance_accessor: false, default: [].freeze

WhereClauseの空インスタンスの定数化activerecord/lib/active_record/relation/where_clause.rb)では、@empty ||= new([]).freezeというメモ化パターンを廃止し、EMPTY定数として定義し直しました。

変更前:

def self.empty
  @empty ||= new([]).freeze
end

変更後:

EMPTY = new([]).freeze

def self.empty
  EMPTY
end

Typeモジュールのデフォルト型の定数化activerecord/lib/active_record/type.rb)では、@default_value ||= Value.newというメモ化を廃止し、DEFAULT_TYPE定数として定義し直しました。

変更前:

def default_value
  @default_value ||= Value.new
end

変更後:

DEFAULT_TYPE = Value.new.freeze

def default_value
  DEFAULT_TYPE
end

delegation.rbのキャッシュのfreezeactiverecord/lib/active_record/relation/delegation.rb)では、initialize_relation_delegate_cacheメソッドの末尾で@relation_delegate_cache.freezeを呼び出し、構築後のキャッシュHashを不変化します。これにより、キャッシュが初期化後に変更されないことをRactorの仕組みとして保証します。

設計判断

メモ化パターン(||=)から定数への置き換えが、Ractor対応の主要な設計戦略として採用されています。

インスタンス変数を用いたメモ化(@foo ||= SomeObject.new)は、初回アクセス時にオブジェクトを生成してインスタンス変数に格納する慣用的なパターンです。しかしこの方式では、オブジェクトがRactor間で共有される前にfreezeされる保証がなく、shareableの条件を満たせません。定数として宣言し起動時に即座にfreezeすることで、ロード時点でshareableな状態が確定します。

また、ARRAY_WITH_EMPTY_STRING = [""}.freezeのような既存の定数定義もwhere_clause.rb内に存在しており、今回のEMPTY定数の追加はその一貫した書き方に沿っています。変更後のメソッドself.emptyは定数への委譲のみになり、インターフェースは変わらずに内部実装だけがRactor対応に切り替わっています。

まとめ

このPRはメモ化パターンや可変デフォルト値を定数・freeze済みオブジェクトに置き換えることで、Active RecordをRactorの共有可能オブジェクトの要件に近づけています。インターフェースを変えずに内部実装のみを不変化する手法は、既存アプリケーションへの影響を最小化しながらRactor対応を段階的に進める上で有効なアプローチです。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
6e94dfa1

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

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

「リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)」という構成が明確で、ガイドラインの要件を完全に満たしています。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きのシンタックスハイライト(```言語:ファイルパス)やGitHubのPR番号リンク([#123](URL))の記法が正しく使用されています。

対象読者への適合性 ✓ PASS

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

Ractor、shareable、freeze、メモ化といった用語を前提知識として扱っており、専門知識を持つエンジニアという対象読者に完全に適合しています。

パラグラフ・ライティング ✓ PASS

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

各セクション、各パラグラフが総論→各論の構造で書かれており、トピックセンテンスも明確です。非常に読みやすい構成になっています。

Diff内容との照合 ✓ PASS

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

記事内で引用されている4つのファイルのコード変更は、提供されたDiff情報と完全に一致しており、省略や改変もありません。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「Ractor」「shareable」「freeze」「メモ化」といった技術用語が、文脈に沿って正確かつ適切に使用されています。

説明の技術的正確性 ✓ PASS

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

Ractorの共有可能性の要件とfreezeの関係、メモ化パターンの問題点、定数化による解決策など、すべての技術的説明が論理的かつ正確です。

事実の突合 ✓ PASS

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

記事の主張はすべてPRのDescription(「Ractor対応の継続」「#57323 との関連性」など)やDiff内のコード変更によって裏付けられており、ハルシネーションは一切見られません。

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

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

PR番号(#57475, #57323)やファイルパス、コード内の定数名などがすべて正確に記載されています。

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

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

記事のタイトルは、PRのタイトル「Active Record Ractor-safe freezing」の内容を「定数のfreeze化とキャッシュの共有化」と具体的に表現しており、主題と完全に一致しています。

外部知識の正確性 ✓ PASS

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

記事の内容はPR情報に限定されており、サポート状況やリリース予定といったPRに記載のない外部知識の追記はありません。

時間表現の正確性 ✓ PASS

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

PRが継続的な取り組みの一部であることを正しく伝えており、時間表現に関する歪曲や誤解を招く記述はありません。