Ночью катили миграцию на таблицу событий, в ней около 30 млн строк. Индекс добавляли как положено:
class AddIndexOnEventsAccountIdCreatedAt < ActiveRecord::Migration[7.2]
disable_ddl_transaction!
def change
add_index :events, [:account_id, :created_at], algorithm: :concurrently
end
end
Через сорок минут деплой отвалился по таймауту в CI, миграция упала, в schema_migrations записи нет. Алерт увидел утром ещё в метро от Беговой, на работе открываю \d events, а индекс там есть, только с пометкой INVALID. Повторный запуск миграции падает, потому что индекс уже существует. Он вообще используется в таком виде? И как повторить, чтобы не повесить прод днём? Rails 7.2, PostgreSQL 16.
Невалидный индекс планировщик не использует, а обновлять его при каждой вставке база продолжает. То есть сейчас вы платите за запись и ничего не получаете. Найти все такие:
SELECT indexrelid::regclass AS index_name,
indrelid::regclass AS table_name
FROM pg_index
WHERE NOT indisvalid;
Дальше либо DROP INDEX CONCURRENTLY и заново, либо пересобрать. И не гоняйте такие миграции внутри деплоя с таймаутом CI: сорок минут для составного индекса на 30 млн строк вполне нормальная цифра.
В 16-м проще REINDEX INDEX CONCURRENTLY имя_индекса, он пересоберёт невалидный на месте, удалять не нужно.
Пересобрал через REINDEX CONCURRENTLY из psql в обед, ушло 37 минут, запросы не тормозили. В миграцию дописал if_not_exists: true и прогнал, она просто проскочила и записалась в schema_migrations. Нам такое повторять ещё на двух таблицах. Как вы отделяете тяжёлые миграции от обычного деплоя?
Долгие миграции я бы из деплоя убрал совсем. В пайплайне идут только быстрые, а тяжёлые помечаются и запускаются отдельной ручной джобой в CI, ночью, без сорокаминутного таймаута и с большим statement_timeout на сессию. И пусть в это время кто-то не спит и смотрит на графики, а не узнаёт утром из алертов.
strong_migrations ещё поставьте, он add_index без concurrently просто не пропустит. Новые люди в команде забывают про это чаще всего.