INSERT実行時のプライマリキー検索をスキーマキャッシュで高速化
Rails 8.1では、INSERT実行時のプライマリキー検索が スキーマキャッシュ を利用するように改善されました。これにより、生SQLでのINSERT実行時にデータベースへの追加クエリを削減できます。
背景
Active Recordは、INSERT実行時にプライマリキー名が不明な場合、SQL文からテーブル名を抽出してプライマリキーを取得する必要がありました。従来は primary_key(table_ref) メソッドを使用していましたが、これはデータベースに直接問い合わせを行うため、毎回のINSERT実行でスキーマ取得のオーバーヘッドが発生していました。
モデル経由のINSERTではプライマリキー名が事前に分かっているため影響は限定的ですが、生SQLを使用する場合やバッチ処理では無視できないコストになる可能性がありました。#56727 はこの非効率性を解消しています。
技術的な変更
activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb の sql_for_insert メソッドと、PostgreSQLアダプタの _exec_insert メソッドが修正されました。
変更前:
if pk.nil?
table_ref = extract_table_ref_from_insert_sql(sql)
pk = primary_key(table_ref) if table_ref
end
変更後:
if pk.nil?
table_ref = extract_table_ref_from_insert_sql(sql)
pk = schema_cache.primary_keys(table_ref) if table_ref
end
変更は primary_key(table_ref) から schema_cache.primary_keys(table_ref) への置き換えのみです。PostgreSQLアダプタでも同様の変更が適用されています。
効果の検証
テストコードで実際の効果が確認されています。PostgreSQLアダプタのテストでは、2回目のINSERT実行時にスキーマキャッシュが効いていることを検証しています。
def test_insert_uses_schema_cache_with_insert_returning_disabled
connection = connection_without_insert_returning
# First call might need to populate the schema cache
connection.insert("INSERT INTO postgresql_partitioned_table_parent (number) VALUES (0)")
# We expect:
# 1. INSERT
# 2. SELECT pg_get_serial_sequence (ideally this would cached, but it's currently not)
# 3. SELECT currval
assert_queries_count(3, include_schema: true) do
connection.insert("INSERT INTO postgresql_partitioned_table_parent (number) VALUES (1)")
end
end
SQLite3アダプタのテストでは、さらに明確な改善が示されています。1回目のINSERTではスキーマ情報の取得が必要ですが、2回目以降はINSERTクエリのみで完了します。
# First insert after with_example_table has reset the schema cache
assert_logged [tables_query, pragma_query, schema_query, modified_insert_query] do
@conn.insert(sql, name)
end
# Subsequent inserts don't need extra schema queries
assert_logged [modified_insert_query] do
@conn.insert(sql, name)
end
設計判断
既存のスキーマキャッシュ機構を活用する方式 が採用されました。
schema_cache.primary_keys メソッドは、テーブルのスキーマ情報をメモリ上にキャッシュし、2回目以降のアクセスではデータベースへの問い合わせを省略します。この仕組みは既にActive Record全体で使用されており、新たなキャッシュ層を追加することなく一貫性を保っています。
PR内のコメント「This isn't a high-traffic code path」が示すように、この変更は頻繁に実行されるコードパスではありませんが、スキーマキャッシュという既存の最適化手法を適切に適用した改善といえます。
まとめ
本PRは、INSERT実行時のプライマリキー検索をスキーマキャッシュ経由に変更した最適化です。メソッド呼び出しを1行変更するだけで、生SQLでのINSERT実行時のデータベースアクセスを削減し、既存のキャッシュ機構との一貫性を高めています。影響範囲は限定的ですが、Active Recordの設計原則に沿った堅実な改善です。