Мы очень часто выполняем миграцию, и то, что начиналось как обычная миграция, заставило Postgres выбрать плохой план запросов, который вызвал очень медленные запросы.Запросы были настолько плохими, что в итоге наш сайт отключился.
Миграция сняла нулевое ограничение для существующей таблицы.В этом примере он удалял нулевое ограничение для items.store_id
.
Индексы в таблице items
:
CREATE INDEX index_items_store_id ON public.items USING btree (store_id)
CREATE INDEX index_items_on_department_id ON public.items USING btree (department_id)
CREATE INDEX index_items_on_deleted_at ON public.items USING btree (deleted_at)
CREATE UNIQUE INDEX items_pkey ON public.items USING btree (id)
Пример запроса:
SELECT "stores".*
FROM "stores"
WHERE "stores"."organization_id" = 1337
AND "stores"."store_status_id" = 1
AND (EXISTS (SELECT "items".*
FROM "items"
INNER JOIN "departments"
ON "departments"."id" = "items"."department_id"
WHERE "items"."deleted_at" IS NULL
AND ("items"."department_id" IS NOT NULL)
AND (items.store_id = stores.id)
AND "items"."job_application_status_id" = 3
AND "departments"."department_status_id" = 3));
План неверных запросов:
Nested Loop (cost=216890.17..217379.24 rows=192 width=1236)
-> HashAggregate (cost=216889.74..216891.74 rows=200 width=4)
Group Key: items.store_id
-> Merge Join (cost=2.13..216769.74 rows=48000 width=4)
Merge Cond: (departments.id = items.department_id)
-> Index Scan using departments_pkey on departments (cost=0.41..10394.19 rows=8925 width=4)
Filter: (department_status_id = 3)
-> Index Scan using index_items_on_department_id on items (cost=0.55..309417.94 rows=8 8783 width=8)
Index Cond: (department_id IS NOT NULL)
Filter: ((deleted_at IS NULL) AND (job_application_status_id = 3))
-> Index Scan using stores_pkey on stores (cost=0.42..2.43 rows=1 width=1236)
Index Cond: (id = items.store_id)
Filter: ((organization_id = 1337) AND (store_status_id = 1))
хороший план запросов:
Nested Loop Semi Join (cost=1.26..4566.90 rows=21 width=1236)
-> Index Scan using index_stores_on_organization_id on stores (cost=0.42..2236.83 rows=385 width=1236)
Index Cond: (organization_id = 1337)
Filter: (store_status_id = 1)
-> Nested Loop (cost=0.84..6.04 rows=1 width=4)
-> Index Scan using index_gh_job_app_store_id on items (cost=0.43..5.46 rows=1 width=8)
Index Cond: (store_id = stores.id)
Filter: ((deleted_at IS NULL) AND (department_id IS NOT NULL) AND (job_application_status_id = 3))
-> Index Scan using departments_pkey on departments (cost=0.41..0.57 rows=1 width=4)
Index Cond: (id = items.department_id)
Filter: (department_status_id = 3)
После выполнения ANALYZE
для таблицы items
Postgres выбрал хороший планировщик запросов, и все снова было хорошо.Мы попытались воспроизвести эту миграцию локально и в других промежуточных средах и не смогли воспроизвести.Мы также делали этот тип миграций раньше много раз и никогда не сталкивались с подобными проблемами.Мы подозреваем, что это связано с количеством запросов, которые эта таблица получает в любой момент времени, поэтому трудно воспроизвести.
Но у нас нет большого опыта в том, как Postgres выбирает лучший планировщик запросов икак мы можем избежать этого в будущем.Если у кого-то есть идеи о том, почему это произошло или как этого избежать, это будет с благодарностью.