テーブル作成時のSQL文をバッチ実行して高速化

rails/rails

create_table で発行される複数のSQL文を束ねて一括実行することで、大規模スキーマのロード時間を大幅に短縮します。1000以上のテーブルを持つPostgreSQLデータベースでの実測では、スキーマロードが約120秒から約25秒に改善されています。

背景

大規模なPostgreSQLデータベースで db:schema:load の実行時間が問題になっていました。PR作者の環境では1000以上のテーブルを持つスキーマのロードに約120秒かかっており、その原因が create_table 内で発行される複数のSQL文にありました。

これまでの create_table の実装では、1テーブルの作成に対して次のSQL文が個別に実行されていました:

  • DROP TABLEforce オプション使用時)
  • CREATE TABLE
  • CREATE INDEXsupports_indexes_in_create? が false の場合、インデックスごと)
  • COMMENT ON TABLE(コメントがある場合)
  • COMMENT ON COLUMN(コメントのあるカラムごと)
  • COMMENT ON INDEX(コメントのあるインデックスごと)

これらのSQL文が各テーブル作成のたびに個別のラウンドトリップとして発行されており、テーブル数に比例してオーバーヘッドが蓄積していました。

技術的な変更

create_table 内でSQL文を配列に蓄積し、最後に execute_batch で一括送信する方式に変更されました。

変更前:

td = build_create_table_definition(table_name, ...)

if force
  drop_table(table_name, force: force, if_exists: true)
else
  schema_cache.clear_data_source_cache!(table_name.to_s)
end

result = execute schema_creation.accept(td)

unless supports_indexes_in_create?
  td.indexes.each do |column_name, index_options|
    add_index(table_name, column_name, **index_options, ...)
  end
end

if supports_comments? && !supports_comments_in_create?
  change_table_comment(table_name, table_comment) if table_comment
  td.columns.each do |column|
    change_column_comment(table_name, column.name, column.comment) if column.comment.present?
  end
end

変更後:

statements = []

if force
  statements << drop_table_sql(table_name, force: force, if_exists: true)
end

schema_cache.clear_data_source_cache!(table_name.to_s)

td = build_create_table_definition(table_name, ...)
statements << schema_creation.accept(td)

unless supports_indexes_in_create?
  td.indexes.each do |column_name, index_options|
    index_definition = build_create_index_definition(table_name, column_name, **index_options, ...)
    statements << schema_creation.accept(index_definition)
    if supports_comments? && !supports_comments_in_create?
      statements << change_index_comment_sql(index_definition.index) if index_definition.index.comment.present?
    end
  end
end

if supports_comments? && !supports_comments_in_create?
  statements << change_table_comment_sql(table_name, table_comment) if table_comment
  td.columns.each do |column|
    statements << change_column_comment_sql(table_name, column.name, column.comment) if column.comment.present?
  end
end

execute_batch statements

この変更を実現するために、SQL文字列を返す新しいヘルパーメソッド群が導入されています。これらは実行まで担っていた既存メソッドから「SQL生成」の責務を分離したものです:

  • drop_table_sql: DROP TABLE 文を返す(MySQL・PostgreSQLそれぞれに実装)
  • change_table_comment_sql: COMMENT ON TABLE 文を返す(PostgreSQL)
  • change_column_comment_sql: COMMENT ON COLUMN 文を返す(PostgreSQL)
  • change_index_comment_sql: COMMENT ON INDEX 文を返す(PostgreSQL)

既存の change_table_commentchange_column_comment などのパブリックメソッドは、内部でこれらの新しい _sql メソッドを呼び出す形にリファクタリングされており、外部からの呼び出しインターフェースは変わりません。

変更前後で schema_cache.clear_data_source_cache! の呼び出し位置にも注目できます。変更前は force の有無で分岐していましたが、変更後は force の有無にかかわらず常に呼び出されるようになっています。

設計判断

既存の executedrop_table を直接呼ぶ代わりに、SQL文字列を返す _sql メソッド群を新設する設計が採用されました。

副作用(DB実行)とSQL生成を分離することで、create_table の文脈ではSQL文字列のみを収集し、最後にまとめて送信できるようになっています。一方で change_column_comment のような既存のパブリックAPIはそのまま残され、_sql メソッドに委譲する形をとることで後方互換性を維持しています。

テスト面では、test_create_table_uses_batched_statements が追加されており、コメント・インデックス・カラムすべてを含む create_tableforce: true)が クエリ1回 で完了することを assert_queries_count(1, include_schema: true) で保証しています。また、MySQLのスキーマテスト用スタブにも execute_batch のオーバーライドが追加され、バッチ実行に対応したテスト環境が整備されています。

まとめ

本PRは、create_table 内の複数SQL発行を execute_batch による一括送信に集約することで、大規模スキーマの読み込みを大幅に高速化した変更です。SQL生成と実行を分離する _sql ヘルパー群の導入により、既存APIへの互換性を保ちながらバッチ最適化を実現しており、テーブル数が多い環境ほど効果が顕著に現れる設計となっています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
69c3616c

この記事は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リンク記法の正確性

ファイル名付きのシンタックスハイライト、PR番号のリンク記法ともに正しく使用されています。

対象読者への適合性 ✓ PASS

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

Active Recordの内部実装に関する内容であり、専門知識を持つエンジニアという対象読者に完全に適合しています。

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

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

各セクション、各パラグラフが要点を先に述べる構成になっており、非常に読みやすいです。1段落1トピックの原則も守られています。

Diff内容との照合 ✓ PASS

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

記事で引用されている変更前後のコードは、提供されたDiff情報と完全に一致しており、正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`execute_batch`、`_sql`ヘルパーメソッド、ラウンドトリップなど、技術用語の選択と使用法が正確かつ適切です。

説明の技術的正確性 ✓ PASS

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

SQL文を配列に蓄積して一括実行するという変更内容の説明は、Diffの内容と完全に整合しており、技術的に正確です。

事実の突合 ✓ PASS

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

「120秒から25秒への改善」という具体的なパフォーマンス数値や、新設されたテスト名など、記事内のすべての主張がPRのDescriptionやDiffで裏付けられています。ハルシネーションは見られません。

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

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

PR番号(#57000)、パフォーマンス改善に関する数値(1000+, 120s, 25s)など、すべての数値・固有名詞が正確です。

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

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

記事タイトル「テーブル作成時のSQL文をバッチ実行して高速化」は、PRの主題を的確に要約しており、内容との整合性も取れています。

外部知識の正確性 ✓ PASS

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

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

時間表現の正確性 ✓ PASS

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

PR情報に含まれる時間的な前後関係を歪めるような表現はなく、正確です。