MariaDBカラム取得時の不要なクエリを削減する最適化

rails/rails

MariaDBの関数デフォルト検出を new_column_from_field から column_definitions へ移動することで、非MariaDB接続での余分なクエリを完全に排除し、MariaDB接続でも SHOW CREATE TABLE の発行を最小化しました。

背景

#44654 によるMariaDBの関数デフォルト検出の修正が、大規模スキーマで深刻なクエリ過多を引き起こしていました。MariaDBでは、スキーマダンプ時に関数デフォルト(UUIDの自動設定など)が文字列として誤認識される問題があり、#44654 はこれを new_column_from_field 内で SHOW CREATE TABLE を呼び出すことで解決しました。しかしこの実装では、カラムの数だけ SHOW CREATE TABLE が実行されていました。

問題の本質は、このチェックが MariaDB以外の接続でも実行されていた 点にあります。new_column_from_field はMySQL・MariaDB共通のコードパスであるため、MySQLを使用するアプリケーションでも大量の不要クエリが発生していました。テストコードの変更が示す通り、スキーマ情報取得に必要なクエリ数は4回から3回に削減されています。

技術的な変更

column_definitions メソッドで SHOW FULL FIELDS の結果を受け取った直後に、MariaDB専用の処理を一括で行う update_fields_for_mariadb メソッドが追加されました。これにより、MariaDB接続でのみ追加処理が実行されるようになります。

変更前: column_definitions は単純なクエリを返すだけで、関数デフォルト判定は new_column_from_field 内で列ごとに実行されていました。

def column_definitions(table_name) # :nodoc:
  query_all("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}")
end

変更後: column_definitionsupdate_fields_for_mariadb を呼び出し、フィールド情報を前処理します。

def column_definitions(table_name) # :nodoc:
  fields = query_all("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}")

  update_fields_for_mariadb(table_name, fields) if mariadb?

  fields
end

def update_fields_for_mariadb(table_name, fields)
  has_function_default_candidate = fields.any? do |field|
    default = field["Default"]
    default&.match?(/[a-zA-Z_]\w*\(/) && !/\ACURRENT_TIMESTAMP/i.match?(default)
  end

  if has_function_default_candidate
    table_info = create_table_info(table_name)
    fields.each do |field|
      default = field["Default"]
      next unless default&.match?(/[a-zA-Z_]\w*\(/)
      next if /\ACURRENT_TIMESTAMP/i.match?(default)

      field_name = field["Field"]
      match = table_info&.match(/`#{field_name}` .+ DEFAULT ('|\d+|[A-z]+)/)
      if match && match[1].match?(/\A[A-z]/)
        field["Extra"] = "DEFAULT_GENERATED"
      end
    end
  end
end

update_fields_for_mariadb の処理は2段階になっています。まず全フィールドをスキャンして「関数デフォルトの候補」(func_name( パターンかつ CURRENT_TIMESTAMP 以外)が1つでも存在するか確認します。候補がある場合のみ SHOW CREATE TABLE を1回だけ発行し、各フィールドに DEFAULT_GENERATED を設定することで、既存のMySQLコードパスへ統合します。

new_column_from_field 側では、削除されたコードも重要です。MariaDB専用だった default_type メソッドが完全に削除され、DEFAULT_GENERATED ブランチがMariaDB・MySQL別に分岐するよう修正されました。

elif type_metadata.extra == "DEFAULT_GENERATED"
  if mariadb?
    default, default_function = nil, default
  else
    default = "(#{default})" unless default.start_with?("(")
    default = default.gsub("\\'", "'")
    default, default_function = nil, default
  end

MariaDBの場合はデフォルト値をそのまま default_function として扱い、MySQLの場合は従来通りに括弧付きへの変換とエスケープ処理を行います。これは両者の SHOW FULL FIELDS 出力形式の差異に対応しています。

設計判断

「候補の事前スクリーニング」 によって SHOW CREATE TABLE の発行を条件付きにする2段階の判定が採用されました。

ナイーブな実装では MariaDB 接続のたびに SHOW CREATE TABLE を発行することも考えられますが、本PR では全フィールドの Defaultany? でスキャンして候補がない場合は追加クエリを完全にスキップします。関数デフォルトを持たない大多数のテーブルではオーバーヘッドがゼロになります。

また、DEFAULT_GENERATEDExtra フィールドに書き込む設計により、new_column_from_field の既存分岐をそのまま再利用できています。MariaDB専用の検出ロジックを column_definitions 層に隔離しつつ、後段の処理は共通インターフェースで統合するというレイヤー分離の判断といえます。

まとめ

この変更は、#44654 で導入された正しい修正に伴うパフォーマンス上の副作用を、責務の再配置によって解消しています。関数デフォルト検出を適切な抽象レイヤー(column_definitions)に移動し、接続種別によるショートサーキットと候補スクリーニングを組み合わせることで、非MariaDB接続での余分なクエリを完全に排除しつつ、MariaDB接続でも最悪ケースを「テーブルあたり1クエリ」に抑えています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
9e81e0f6

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)という「総論→各論→結論」の構成が明確に適用されており、非常に分かりやすいです。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライトやGitHubのPR番号リンク記法がガイドラインに沿って正しく使用されています。

対象読者への適合性 ✓ PASS

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

Railsの内部実装に関する知識を前提としており、専門知識を持つエンジニアという対象読者に完全に適合しています。冗長な説明がなく簡潔です。

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

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

各セクションが総論→各論の構成で書かれ、各段落はトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が徹底されています。可読性が非常に高いです。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードは、提供されたDiffの内容と完全に一致しています。ファイルパスも正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`new_column_from_field`, `column_definitions`, `DEFAULT_GENERATED`などの技術用語が、PRの文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

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

変更の背景、技術的な詳細、パフォーマンス改善の効果(クエリ数の削減)に関する説明は、すべてPR DescriptionとDiffによって裏付けられており、技術的に正確です。

事実の突合 ✓ PASS

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

記事内のすべての主張が、PRのTitle, Description, Diffの内容に完全に基づいています。ハルシネーションや根拠のない推測は見られません。

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

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

PR番号(#57110, #44654)や、テストコードから引用したクエリ数の削減(4回から3回)など、すべての数値・固有名詞が正確です。

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

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

記事のタイトル「MariaDBカラム取得時の不要なクエリを削減する最適化」は、PRの主題「Reduce number of queries during MariaDB column introspection」を的確に日本語で表現しており、内容と一致しています。

外部知識の正確性 ✓ PASS

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

記事はPRで提供された情報のみに基づいており、バージョンサポート状況やリリース日程など、PR外の知識を不適切に含んでいません。

時間表現の正確性 ✓ PASS

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

記事内の時間表現は、PRの文脈と一致しており、事実関係を歪めるような記述はありません。