w_tl00’s blog

インプットをまとめておく

SchemaCache, BoundSchemaReflection, SchemaReflectionについて

概要

Rails v7.1で改修があったスキーマキャッシュ周りのSchemaCache, BoundSchemaReflection, SchemaReflectionの3つのクラスについての解説があまりなかったので、調査した。

version

at: Rails v7.2.1 https://github.com/rails/rails/releases/tag/v7.2.1

SchemaCache, BoundSchemaReflection, SchemaReflectionについて

ConnectionPoolはApplicationRecordクラスなどに設定したrole, databaseごとに存在している。 ConnectionPool@schema_cache を持っているのでこれを手がかりに調べる。

rails/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb at v7.2.1 · rails/rails

     class ConnectionPool
       ...
       attr_accessor :automatic_reconnect, :checkout_timeout
       attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
 
       delegate :schema_reflection, :server_version, to: :pool_config
       ...
       def initialize(pool_config)
         ...
         @schema_cache = nil
         ...
       end
 
       def schema_cache
         # pool_configにschema_reflectionがdelegateされている
         @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
       end 
       
      def schema_reflection=(schema_reflection)
        # pool_configにschema_reflectionを設定している
        pool_config.schema_reflection = schema_reflection
        @schema_cache = nil
      end
    ...
    end

delegate先のPoolConfigを見る。

rails/activerecord/lib/active_record/connection_adapters/pool_config.rb at v7.2.1 · rails/rails

     class PoolConfig # :nodoc:
       include MonitorMixin
 
       attr_reader :db_config, :role, :shard
       attr_writer :schema_reflection, :server_version
       attr_accessor :connection_class
 
       # ConnectionPoolからのdelegate先
       def schema_reflection
         @schema_reflection ||= SchemaReflection.new(db_config.lazy_schema_cache_path)
       end
    ...
    end

lazy_schema_cache_pathはHashConfigにある

rails/activerecord/lib/active_record/database_configurations/hash_config.rb at v7.2.1 · rails/rails

      # The path to the schema cache dump file for a database. If omitted, the
      # filename will be read from ENV or a default will be derived.
      def schema_cache_path
        # 設定してたら、優先される
        configuration_hash[:schema_cache_path]
      end
      
       def default_schema_cache_path(db_dir = "db")
         if primary?
           File.join(db_dir, "schema_cache.yml")
         else
           File.join(db_dir, "#{name}_schema_cache.yml")
         end
       end
 
       def lazy_schema_cache_path
         schema_cache_path || default_schema_cache_path
       end

まとめ

  • ConnectionPoolスキーマキャッシュを保持していると考えて良い。正確に言うとConnectionPoolBoundSchemaReflectionを保持している
  • ConnectionPoolが保持しているBoundSchemaReflectionには、ConnectionPoolに紐づくPoolConfigSchemaReflectionが入る
  • SchemaReflection
    • 求めていたスキーマキャッシュを保持している
    • attrs
      • cache: SchemaCache (これがスキーマキャッシュ本体)
      • cache_path: db/schema_cache.yml など
  • BoundSchemaReflection *ConnectionPoolSchemaReflectionの橋渡し役
    • attrs
      • schema_reflection: 上記のSchemaReflection
      • pool: ConnectionPoolPoolConfig
  • スキーマキャッシュはBoundSchemaReflection -> SchemaReflection -> SchemaCacheの3つで構成するイメージ

関連するPR