Adding an index to a partitioned table and its children in PostgreSQL and Rails
Published: March 30, 2022
Postgres 10+ made partitioning tables much easier, however adding an index to a table with many partitions and data in them already is not straightforward if you want to avoid long table locking.
With partition tables in PostgreSQL, you can add an index to the parent table and it will cascade to all child tables. However, this operation will lock all child tables until the index is applied. While it is not possible to concurrently add an index to the parent table and have it propagate, you can add an index to the parent table only, apply the index concurrently to each of the child tables, and then attach the index to the parent.
Below is an example migration for Rails 7 using PostgreSQL 13 to do just that:
class AddSomeIndexToAParentTable < ActiveRecord::Migration[7.0]
PARENT_TABLE_NAME = "the_parent_partition_table"
INDEX_COLUMN_NAME = "column_to_index"
PARENT_INDEX_NAME = "index_#{PARENT_TABLE_NAME}_on_#{INDEX_COLUMN_NAME}"
disable_ddl_transaction!
def up
# Create base index on parent table for future tables to get
execute "CREATE INDEX #{PARENT_INDEX_NAME} ON ONLY #{PARENT_TABLE_NAME} (#{INDEX_COLUMN_NAME})"
child_tables = execute(<<~SQL).pluck("child")
SELECT inhrelid::regclass AS child
FROM pg_catalog.pg_inherits
WHERE inhparent = '#{PARENT_TABLE_NAME}'::regclass
SQL
child_tables.each do |child_table|
# Index name matched up with existing child table index names PostgreSQL automatically generates
child_index_name = "#{child_table}_#{INDEX_COLUMN_NAME}_idx"
# Add index to child table
execute "CREATE INDEX CONCURRENTLY #{child_index_name} ON #{child_table} (#{INDEX_COLUMN_NAME})"
# "Attach" child index to parent index so it can be dropped if the parent is dropped
execute "ALTER INDEX #{PARENT_INDEX_NAME} ATTACH PARTITION #{child_index_name}"
end
end
def down
# If we're running the "down" on this, I hope it's only locally
execute "DROP INDEX #{PARENT_INDEX_NAME}"
end
end
PostgreSQL Partitioning Docs (for version 13): https://www.postgresql.org/docs/13/ddl-partitioning.html