Создание индексов в PostgreSQL — мощный инструмент для ускорения запросов, но бездумное их добавление может привести к блокировкам таблиц на проде. В статье разберём, как безопасно добавлять индексы в Rails-приложениях с помощью алгоритма CONCURRENTLY и избежать типичных ошибок, которые приводят к простоям.
Ты пишешь миграцию:
add_index :users, :email
Всё быстро на staging — миграция за секунду. На проде: таблица на 5 миллионов строк, база повисает, запросы стопорятся, devops кидают в тебя тапками.
🚨 Что пошло не так?
На staging — 10 строк. На проде — миллионы. Создание индекса по умолчанию блокирует таблицу на запись, если не указана стратегия.
🧠 Как надо было
Нужно использовать algorithm: :concurrently:
add_index :users, :email, algorithm: :concurrently
Это позволит создать индекс без блокировки таблицы, но:
- нельзя использовать внутри
change, толькоup/down - нельзя внутри транзакции
🧯 Типичный факап
# плохо
class AddIndexToUsers < ActiveRecord::Migration[7.1]
def change
add_index :users, :email, algorithm: :concurrently
end
end
❌ Ошибка: can’t run concurrent index creation inside a transaction
✅ Как правильно
class AddIndexToUsers < ActiveRecord::Migration[7.1]
disable_ddl_transaction!
def change
add_index :users, :email, algorithm: :concurrently
end
end
🧪 Проверь себя
- Индекс создаётся на большую таблицу? ➜
concurrently - Таблица активно пишется? ➜
disable_ddl_transaction! - В staging мало данных? ➜ тестируй миграцию на дампе из прода
💡 Бонус: удаление индекса тоже может блокировать
remove_index :users, :email, algorithm: :concurrently
🛡️ Используй strong_migrations
Подключи гем strong_migrations, и он предупредит о рисках:
gem "strong_migrations"
Он не даст случайно повесить прод, если ты:
- создаёшь индекс без
concurrently - удаляешь колонку
- меняешь тип поля без бэкапа
📢 Это не панацея, но первая линия обороны. Особенно, если миграции делает не только синьор.
📌 Вывод: в миграциях важна не только логика, но и стратегия. Особенно на проде.