Postgresql становится безответственным, когда добавляется новое значение индекса

В моем приложении у меня есть концепция «сезонов», которые дискретно меняются с течением времени. Все сущности связаны с каким-то временем года. Все объекты имеют сезонные индексы, а также некоторые индексы других полей. Когда происходит смена сезона, postgresql решает использовать фильтрованный план сканирования на основе индекса сезона, а не более конкретных индексов полей. В начале сезона стоимость планирования такого решения очень мала, так что все в порядке, но проблема в том, что смена сезона приводит к тому, что МНОГИЕ пользователи приходят в самом начале сезона, поэтому план запросов на основе сканирования postgresql очень быстро становится плохим. - он просто сканирует все сущности в новом сезоне и фильтрует целевые предметы. После первого автоматического анализа postgres решает использовать хороший план, НО автоматический анализ работает ОЧЕНЬ МЕДЛЕННО из-за разногласий, и я полагаю, что это похоже на снежный ком - чем больше запросов выполняется, тем больше разногласий происходит из-за плохого плана, и поэтому автоматический анализ работает медленно и медленно. Максимальное время работы автоматического анализа составляло около часа на прошлой неделе, и это становится настоящей проблемой. Я знаю, что архитекторы postgresql решили отключить возможность выбора индекса, используемого в запросе, но как тогда лучше всего решить мою проблему?

Просто чтобы уточнить, вот DDL, один из «медленных» запросов и объяснение результатов до и после автоматического анализа.

DDL

CREATE TABLE race_results (
  id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('race_results_id_seq'::regclass),
  user_id INTEGER NOT NULL,
  opponent_id INTEGER,
  season_id INTEGER NOT NULL,
  type RACE_TYPE NOT NULL DEFAULT 'battle'::race_type,
  elo_delta INTEGER NOT NULL,
  opponent_elo_delta INTEGER NOT NULL DEFAULT 0,
);
CREATE INDEX race_results_type_user_id_index ON race_results USING BTREE (season_id, type, user_id);
CREATE INDEX race_results_type_opponent_id_index ON race_results USING BTREE (season_id, type, opponent_id);
CREATE INDEX race_results_opponent_id_index ON race_results USING BTREE (opponent_id);
CREATE INDEX race_results_user_id_index ON race_results USING BTREE (user_id);

Запрос

SELECT 1000 + COALESCE(SUM(CASE WHEN user_id = 6446 THEN elo_delta ELSE opponent_elo_delta END), 0)
        FROM race_results
        WHERE type = 'battle' :: race_type AND (user_id = 6446 OR opponent_id = 6446) AND
              season_id = current_season_id()

Результаты объяснения перед автоматическим анализом (как вы видите, более тысячи элементов уже удалены фильтром, и скоро их становится сотни тысяч для каждого запроса) введите здесь описание изображения

Результаты анализа объяснения после автоматического анализа (теперь postgres решает использовать правильный индекс и фильтрация больше не требуется, но проблема в том, что автоматический анализ занимает слишком много времени, отчасти из-за неэффективного выбора индекса на предыдущем изображении) введите здесь описание изображения

ps: Сейчас решаю проблему, просто отключив сервер приложений через 10 секунд после смены сезона, чтобы постгрес получил новые данные и начал автоанализ, а затем включит его, когда автоанализ закончится, но такое решение связано с простоем, который не является желательно и в целом выглядит странно


person Kivan    schedule 18.12.2016    source источник
comment
Добавление DDL и запроса к вашему вопросу поможет.   -  person wildplasser    schedule 19.12.2016
comment
@wildplasser добавил больше информации   -  person Kivan    schedule 19.12.2016
comment
Пожалуйста, отредактируйте свой вопрос и добавьте план выполнения как отформатируйте текст, пожалуйста. Скриншоты плохо читаются.   -  person a_horse_with_no_name    schedule 19.12.2016
comment
@a_horse_with_no_name извините, сейчас это невозможно, потому что я получил их в виде картинок. Вы можете нажать на картинку, чтобы сделать ее больше.   -  person Kivan    schedule 19.12.2016
comment
Я не понимаю, почему на первом снимке экрана показан неэффективный выбор индекса. Запрос занимает меньше миллисекунды с использованием индекса race_results_type_user_id_index - что в этом плохого? План выполнения на второй картинке на самом деле кажется менее эффективным.   -  person a_horse_with_no_name    schedule 19.12.2016
comment
@a_horse_with_no_name это миллисекунды, когда он должен отфильтровать 1000 элементов, но когда дело доходит до 100 000 элементов, он становится намного медленнее (сейчас у меня нет этой картинки), и помните, что этот запрос вызывается всеми пользователями. Среднее время запроса до завершения автоанализа составляет около 20 секунд. После завершения автоанализа оно падает до 0,2 мс.   -  person Kivan    schedule 19.12.2016
comment
Если вам нужен ответ, почему запрос медленный, покажите нам план выполнения (сгенерированный с использованием explain (analyze), а не только explain) для медленного запроса, а не для быстрого запроса.   -  person a_horse_with_no_name    schedule 19.12.2016
comment
@a_horse_with_no_name на первой картинке показан МЕДЛЕННЫЙ план, это означает, что когда речь идет о фильтрации 100 000 элементов, план тот же, но время увеличивается до секунд. Я сделал этот скриншот в рабочей среде некоторое время назад только для внутреннего использования, и я переделываю его сейчас, потому что новый сезон начнется позже.   -  person Kivan    schedule 19.12.2016
comment
season_id = current_season_id() Проблема в том, что планировщик не может угадать значение функции и, следовательно, не может использовать статистику для season_id (который, вероятно, в любом случае является столбцом с низкой кардинальностью). Я предлагаю вам удалить функцию и вместо этого использовать подготовленный запрос. И, возможно, добавить составной индекс, включающий season_id (при втором чтении я вижу, что он у вас уже есть)   -  person joop    schedule 19.12.2016
comment
Меньше миллисекунды — это не медленно — по крайней мере, для меня. Вы должны показать нам настоящий медленный план.   -  person a_horse_with_no_name    schedule 19.12.2016
comment
@joop я так понимаю может, потому что после первого автоанализа все начинает работать как надо   -  person Kivan    schedule 19.12.2016
comment
@a_horse_with_no_name ... ага: P, как я уже говорил, это то же самое, только с другим номером в строках, удаленных фильтром.   -  person Kivan    schedule 19.12.2016
comment
@a_horse_with_no_name - вы просили пример с более чем миллисекундами - вот он, я обновил первое изображение - как видите - уже есть 2 секунды, я не могу увеличить его больше, но поверьте мне, что без моих обходных путей он вырастет до 25 секунд легко. Также в нем говорится, что необходимо проанализировать ~ 17000 элементов, и это число растет и растет очень быстро (SELECT * FROM pg_catalog.pg_stat_all_tables WHERE schemaname = 'public'). Процесс автоанализа запущен, как видно из pg_stat_activity(SELECT * from pg_catalog.pg_stat_activity WHERE state‹›'idle'). И автоанализ может занять час   -  person Kivan    schedule 25.12.2016
comment
Кстати: что такое автоматический анализ?   -  person wildplasser    schedule 26.12.2016
comment
@wildplasser Это процесс, когда postgres автоматически (на основе некоторых предопределенных критериев) выполняет анализ целевой таблицы для обновления статистики для планировщика запросов. Его можно спутать с процедурой автоматической очистки, которая позволяет postgres освобождать память, но это не одно и то же.   -  person Kivan    schedule 26.12.2016
comment
Если вы запрашиваете pg_stat_activity, то я вызываю процессы автоанализа с помощью: 'autovacuum: ANALYZE...' и вызываю процессы автоочистки с помощью: 'autovacuum: VACUUM...'. Иногда postgres также вызывает их обоих: 'autovacuum: VACUUM ANALYZE...'   -  person Kivan    schedule 26.12.2016
comment
какие свойства есть у current_season_id()?   -  person Jasen    schedule 26.12.2016
comment
@Jasen, он просто выбирает последнюю строку из таблицы сезонов, например, SELECT id FROM Seasons ORDER BY number DESC LIMIT 1   -  person Kivan    schedule 27.12.2016
comment
language sql ? Волативность это что: stable?   -  person Jasen    schedule 29.12.2016
comment
@Jasen Да, язык sql и волатильность не установлены, поэтому кажется, что используется какое-то значение по умолчанию.   -  person Kivan    schedule 30.12.2016


Ответы (1)


Наконец я нашел решение. Он не идеален, и я не буду отмечать его как лучший, однако он работает и может кому-то помочь.

Вместо индексов по сезону, типу и идентификатору пользователя/оппонента теперь у меня есть индексы

CREATE INDEX race_results_type_user_id_index ON race_results USING BTREE (user_id,season_id, type);
CREATE INDEX race_results_type_opponent_id_index ON race_results USING BTREE (opponent_id,season_id, type);

Возникла одна проблема - мне все равно нужно было индексировать по сезону в других запросах, но когда я добавляю индекс

CREATE INDEX race_results_season_index ON race_results USING BTREE (season_id);

планировщик пытается использовать его снова вместо тех правильных индексов, и вся ситуация повторяется. Я просто добавил еще одно поле: 'season_id_clone', которое содержит те же данные, что и 'season_id', и сделал для него индекс. Теперь, когда мне нужно что-то отфильтровать по сезону (не включая запросы из первого поста), я использую в запросе Season_id_clone. Я знаю, что это странно, но я не нашел ничего лучше.

person Kivan    schedule 22.01.2017