PostgreSQL を使っている運用中のサービスで、SQL のパフォーマンスチューニングをしています。その際にチューニングの一環でテーブルに Index を作成しました。 いくつか考慮したことがあったので、整理しておくことにします。
例については、通常の B-Tree index を操作する場合を想定します。
対象のテーブルが以下のような場合
Index 操作に対して CONCURRENTRY オプションを設定することが推奨されます。
PostgreSQL では Index の作成時にテーブルへの書き込みに対してロックが取られます(他のトランザクションでの読み取りは可能)。そのため、テーブルに対する挿入、更新、削除の同時実行を防止するロックを取らずにインデックスを作成する CONCURRENTRY オプションが用意されています。 仕組みについては、こちらを参照。
strong_migraions gem でもテーブルへの index 作成時には CONCURRENTRY オプションを使用することが推奨されています。
※いずれもトランザクションブロック(BEGIN; ... COMMIT;
)外で実行すること
CREATE INDEX CONCURRENTLY title_idx ON books (title);
DROP INDEX CONCURRENTLY title_idx;
参考: https://www.postgresql.org/docs/current/sql-dropindex.html
ただし、CONCURRENTRY オプションには以下のような注意点もあります。
参考: https://www.postgresql.org/docs/current/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY
そのため、CONCURRENTRY オプションとうまく付き合う必要があります。 以下にその観点を挙げておきます。
対象テーブルの特性を確認します。
上記の場合は CONCURRENTRY オプションを使用します。
逆に、マスタデータを扱うテーブルや書き込みが運用側で制御できるようなテーブルは CONCURRENTRY オプションを使用しないことで、上記の注意点について考慮する必要がなくなります。
INDEX の作成に失敗した場合は、REINDEX INDEX を実行して無効な index を削除します。
REINDEX INDEX CONCURRENTLY title_idx;
参考: https://www.postgresql.org/docs/current/sql-reindex.html
ActiveRecord の Migration ファイルで index 操作を行うサンプルです。
class AddTitleIndexToBooks < ActiveRecord::Migration[8.0]
disable_ddl_transaction!
def change
add_index :books, :title, algorithm: :concurrently
end
end
class RemoveTitleIndexFromBooks < ActiveRecord::Migration[8.0]
disable_ddl_transaction!
def change
remove_index :books, :title, algorithm: :concurrently
end
end
class AddTitleIndexToBooks < ActiveRecord::Migration[8.0]
def change
add_index :books, :title
end
end
class RemoveTitleIndexFromBooks < ActiveRecord::Migration[8.0]
def change
remove_index :books, :title
end
end