Модуль aqo представляет собой расширение Postgres Pro Standard для оптимизации запросов по стоимости выполнения. Используя методы машинного обучения, а точнее модификацию алгоритма k-NN, aqo улучшает оценку количества строк, что может способствовать выбору лучшего плана и, как следствие, ускорению запросов.
Модуль aqo может собирать статистику по всем выполняемым запросам, за исключением запросов, обращающихся к системным отношениям. Если запросы различаются только константами, они считаются относящимися к одному классу. Для каждого класса модуль aqo сохраняет для машинного обучения качество оценки количества строк, время планирования, время и статистику выполнения. На основе этих данных aqo строит новый план выполнения и использует его для следующего запроса того же класса. В тестах aqo показал значительное увеличение производительности для сложных запросов.
Модуль aqo сохраняет все данные обучения (aqo_data), запросы (aqo_query_texts), параметры запросов (aqo_queries) и статистику выполнения запросов (aqo_query_stat) в файлах. При запуске aqo эти данные загружаются в разделяемую память. Вы можете обращаться к данным aqo, используя функции и представления.
При включённом параметре aqo.advanced и запуске aqo в режиме intelligent или learn каждому классу запросов для его идентификации и разделения собранной статистики присваивается уникальное хеш-значение, вычисляемое на основе дерева запросов. Если aqo.advanced отключён, статистика для всех неотслеживаемых классов запросов хранится в общем классе запросов с хешем 0.
С каждым классом запросов связано отдельное пространство, называемое пространством признаков, в котором собирается статистика для данного класса запросов. Для идентификации этого пространства признаков используется хеш-значение (fs), называемое основным хеш-значением. Оно является общим для запросов, отличающихся только именами таблиц, что позволяет агрегировать данные обучения по таким запросам. С каждым пространством признаков связаны подпространства признаков, в которых собирается информация об избирательности и количестве строк для каждого узла плана запроса. Для идентификации каждого подпространства также используется хеш-значение (fss).
Параметры оптимизации для каждого запроса хранятся в представлении aqo_queries.
В настоящее время расширение aqo имеет следующие ограничения:
Оптимизация запросов с использованием aqo не поддерживается для запросов, обращающихся только к временным объектам.
Оптимизация запросов с использованием aqo не поддерживается для запросов, содержащих функции IMMUTABLE.
Модуль aqo не собирает статистику по репликам, поскольку они доступны только для чтения. Однако он может использовать статистику выполнения запросов с ведущего сервера при работе с физической репликой.
Режимы learn и intelligent не должны работать на уровне кластера с запросами, имеющими динамически генерируемую структуру, поскольку в этих режимах сохраняются все идентификаторы классов запросов, которые различны для всех таких запросов. Тем не менее могут использоваться динамически генерируемые константы.
Расширение aqo включено в состав Postgres Pro Standard. Установив Postgres Pro Standard, выполните следующие действия, чтобы подготовить aqo к работе:
Добавьте aqo в параметр shared_preload_libraries в файле postgresql.conf:
shared_preload_libraries = 'aqo'
Библиотеку aqo нужно предварительно загрузить при запуске сервера, так как адаптивная оптимизация запросов должна быть включена для всего кластера.
Создайте расширение aqo, выполнив следующий запрос:
CREATE EXTENSION aqo;
Когда расширение будет создано, вы можете приступить к оптимизации запросов.
Чтобы отключить aqo в текущей базе данных, выполните:
DROP EXTENSION aqo;
Чтобы отключить aqo на уровне кластера, выполните следующее:
ALTER SYSTEM SET aqo.enable = off; SELECT pg_reload_conf();
Чтобы удалить все данные из хранилища aqo, включая собранную статистику, вызовите функцию aqo_reset(). Чтобы удалить данные из текущей базы данных, выполните
SELECT aqo_reset();
Чтобы удалить все данные из хранилища aqo, выполните
SELECT aqo_reset(NULL);
Чтобы расширение aqo не загружалось при перезапуске сервера, удалите строку
shared_preload_libraries = 'aqo'
из файла postgresql.conf.
Чтобы избежать ошибок во время физической репликации при переносе данных aqo с ведущего сервера на реплику, убедитесь, что на серверах установлены одинаковые версии модуля. Если установлены разные версии aqo, необходимо задать значение off для параметра aqo.wal_rw на обоих серверах, однако в таком случае репликация выполняться не будет.
Поведение aqo в основном регулируется параметрами конфигурации aqo.enable, aqo.mode и aqo.advanced. Значения этих параметров по умолчанию позволяют начать обучение aqo в базовом режиме, как только для параметра aqo.enable устанавливается значение on.
Чтобы динамически изменить в текущем сеансе любой из этих параметров, например режим, выполните следующую команду:
SET aqo.mode = 'режим';
Здесь режим — название режима работы, который будет использоваться.
По умолчанию параметр aqo.advanced отключён. При этом устанавливается рекомендуемый базовый режим, в котором статистика собирается для узлов плана (определяемых fss), а собранные данные машинного обучения используются для исправления погрешностей оценки количества строк для всех запросов, план которых содержит определённый узел плана. Как только для параметра aqo.enable устанавливается значение on, aqo начинает обучение. Следует несколько раз выполнить запросы, которые необходимо оптимизировать, пока план не станет достаточно хорошим, и изменить значение параметра aqo.mode на frozen. Чтобы применить данные машинного обучения на уровне экземпляра сервера, выполните следующие команды:
ALTER SYSTEM SET aqo.mode = frozen; ALTER SYSTEM SET aqo.enable = on; SELECT pg_reload_conf();
Данные машинного обучения будут применяться не только к запросам, на которых обучался модуль aqo, но также ко всем запросам с планом, содержащим узлы, по которым собиралась статистика. Чтобы данные машинного обучения не влияли на другие запросы, установите для параметра aqo.advanced значение on, и статистика будет собираться для отдельных запросов. За подробностями обратитесь к Подразделу F.2.3.2.
Если часто выполняются однотипные запросы, например, в приложении ограничено число возможных классов запросов, можно включить параметр aqo.advanced и использовать интеллектуальный режим (intelligent) для улучшения планирования таких запросов. В этом режиме aqo анализирует выполнение каждого запроса и собирает статистику. При этом статистика по разным классам запросов собирается отдельно. Если производительность не увеличивается после 50 итераций, расширение aqo отключается, и планирование выполняется стандартным планировщиком запросов.
Можно посмотреть текущий план запроса, воспользовавшись стандартной командой Postgres Pro EXPLAIN с указанием ANALYZE. За подробностями обратитесь к Разделу 14.1.
Так как в режиме intelligent различные классы запросов анализируются отдельно, aqo может не улучшить производительность, если запросы в рабочей нагрузке относятся к нескольким разным классам или постоянно меняются. Для такого профиля нагрузки стоит перевести aqo в режим controlled или попробовать отключить параметр aqo.advanced.
Когда включён параметр aqo.advanced, в режиме controlled расширение aqo не собирает статистику для новых классов запросов, поэтому они не будут оптимизированы, но для известных классов запросов aqo продолжит собирать статистику и использовать оптимизированные алгоритмы планирования. Поэтому следует использовать режим controlled только после обучения aqo в режиме learn или intelligent.
После того, как aqo прошло обучение, рекомендуется использовать режим controlled для рабочей среды. Чтобы aqo работало в этом режиме на уровне всего производственного кластера, выполните
ALTER SYSTEM SET aqo.mode = 'controlled'; SELECT pg_reload_conf();
При включённом параметре aqo.advanced расширение в режиме learn собирает статистику по всем выполненным запросам и обновляет данные для классов запросов. Этот режим аналогичен режиму intelligent, за исключением возможности интеллектуальной настройки. Его не рекомендуется использовать постоянно для всего кластера, поскольку он пытается оптимизировать aqo для каждого класса запросов, даже для тех, которым это не нужно, и может привести к ненужным вычислительным издержкам и снижению производительности.
Для рабочей нагрузки с динамически генерируемой структурой запросов используйте режим learn с отключённым параметром. Общее улучшение производительности не гарантируется. Поскольку в этом режиме отсутствует интеллектуальная настройка, производительность некоторых запросов может даже снизиться, однако он больше подходит для динамически меняющейся нагрузки и потребляет меньше памяти, чем режим intelligent.
Параметр конфигурации aqo.delta_rows может быть полезен в случае с данными, которые могут существенно изменяться между запросами. Этот параметр позволяет aqo делать предсказания на основе оценки количества строк планировщика. Например, планировщик оценивает меньшее число строк при удалении данных из таблицы. Расширение aqo может использовать эту обновлённую информацию, чтобы предсказать меньшее число строк без необходимости дополнительного обучения.
Если вы хотите уменьшить влияние aqo на время планирования и исполнения запросов, вы можете использовать режим frozen. В этом режиме aqo только читает собранную статистику, но новые данные не собираются.
Для обращения к представлениям aqo и изменения расширенных свойств запросов необходимо иметь права суперпользователя.
Все обработанные классы запросов и соответствующие хеш-значения можно увидеть в представлении aqo_query_texts:
SELECT * FROM aqo_query_texts;
Чтобы узнать класс (то есть хеш) запроса и режим aqo, включите переменные среды aqo.show_hash (boolean) и aqo.show_details (boolean) и выполните запрос. Вывод будет примерно следующим:
... Planning Time: 23.538 ms ... Execution Time: 249813.875 ms ... Using aqo: true AQO mode: LEARN AQO advanced: OFF ... Query hash: -2439501042637610315
Разные классы запросов имеют собственные свойства оптимизации. Эти свойства отображаются в представлении aqo_queries:
SELECT * FROM aqo_queries;
Можно вручную изменять эти свойства, чтобы скорректировать оптимизацию для определённого класса запросов. Например:
-- Добавление нового класса запросов в представление aqo_queries: SET aqo.enable='on'; SET aqo.advanced='on'; SET aqo.mode='intelligent'; SELECT * FROM a, b WHERE a.id=b.id; SET aqo.mode='controlled'; -- Отключение автонастройки, включение learn_aqo и use_aqo -- для данного класса запросов: SELECT count(*) FROM aqo_queries, LATERAL aqo_queries_update(fs, NULL, true, true, false) WHERE fs = (SELECT fs FROM aqo_query_texts WHERE query_text LIKE 'SELECT * FROM a, b WHERE a.id=b.id;'); -- Запуск EXPLAIN ANALYZE и наблюдение изменённого плана: EXPLAIN ANALYZE SELECT * FROM a, b WHERE a.id=b.id; EXPLAIN ANALYZE SELECT * FROM a, b WHERE a.id=b.id; -- Отключение обучения для прекращения сбора статистики и -- начала использования оптимизированного плана: SELECT count(*) FROM aqo_queries, LATERAL aqo_queries_update(fs, NULL, false, true, false) WHERE fs = (SELECT fs FROM aqo_query_texts WHERE query_text LIKE 'SELECT * FROM a, b WHERE a.id=b.id;');
Чтобы предотвратить интеллектуальную настройку для определённого класса запросов, отключите свойство auto_tuning:
SELECT count(*) FROM aqo_queries, LATERAL aqo_queries_update(fs, NULL, NULL, NULL, false) WHERE fs = 'hash';
Здесь хеш — это значение хеша для данного класса запросов. В результате aqo не будет автоматически менять свойства learn_aqo и use_aqo.
Чтобы отключить дальнейшее обучение для некоторого класса запросов, выполните следующую команду:
SELECT count(*) FROM aqo_queries, LATERAL aqo_queries_update(fs, NULL, false, NULL, false) WHERE fs = 'hash';
Здесь хеш — это значение хеша для данного класса запросов.
Чтобы полностью отключить aqo для всех запросов и использовать стандартный планировщик Postgres Pro, выполните:
SELECT count(*) FROM aqo_queries, LATERAL aqo_disable_class(fs, NULL) WHERE fs <> 0;
Чтобы временно отключить aqo для всех запросов в текущем сеансе или на уровне всего кластера, но не удалять и не изменять собранную статистику и параметры, отключите параметр aqo.enable, как указано ниже:
SET aqo.enable = 'off';
или
ALTER SYSTEM SET aqo.enable = 'off'
Вы можете экспериментировать с aqo, не затрагивая его основную базу знаний. Для этого выполните команду
SET aqo.sandbox = ON;
Она включит режим «песочницы», в котором aqo будет работать в изолированной среде. Однако если включить aqo.sandbox в разных сеансах SQL, они будут использовать одни и те же данные.
Данные, полученные в режиме «песочницы», не реплицируются. Но режим «песочницы» можно использовать на ведомом сервере. Более того, единственный способ обучить aqo на ведомом сервере — включить режим «песочницы» при включённой репликации, то есть при значении true для aqo.wal_rw. Без режима «песочницы» aqo будет работать на ведомом сервере так, как будто aqo.mode = FROZEN, то есть сможет использовать существующую базу знаний, но не сможет её обновлять или расширять.
aqo.enable (boolean) #Определяет состояние расширения aqo. Если установлено значение off, aqo не работает, за исключением случаев, когда параметр aqo.force_collect_stat = on.
По умолчанию: off (выкл.).
aqo.mode (text) #Устанавливает режим работы aqo и определяет, как расширение будет обрабатывать новые запросы. Возможные значения:
intelligent — расширение сохраняет новые запросы с включённым auto_tuning. За дополнительными сведениями обратитесь к описанию представления aqo_queries. В данном режиме aqo может отключиться для запроса в случае снижения средней производительности. Этот режим работает таким образом, только если параметр aqo.advanced = on, в противном случае работает аналогично режиму learn.
learn — расширение собирает статистику по всем выполненным запросам, обучается и делает предсказания на основе этой статистики.
controlled — расширение только обучается и делает предсказания для известных запросов.
frozen — расширение делает предсказания для известных запросов, но не обучается ни на каких запросах.
По умолчанию: learn.
aqo.advanced (boolean) #Включает расширенную процедуру обучения, которая сохраняет статистику обучения отдельно для каждого класса запроса. Также позволяет настраивать параметры use_aqo и learn_aqo в представлении aqo_queries. После тонкой настройки параметры запроса в представлении aqo_query продолжают работать при выключенном параметре aqo.advanced.
По умолчанию: off (выкл.).
aqo.force_collect_stat (boolean) #Собирать статистику выполнения запросов во всех режимах aqo даже при aqo.enable = off.
По умолчанию: off (выкл.).
aqo.show_details (boolean) #Добавлять некоторые детали в вывод команды EXPLAIN запроса, такие как предсказание или хеш подпространства признаков, и отображать некоторую дополнительную информацию, специфичную для aqo.
По умолчанию: on (вкл.).
aqo.show_hash (boolean) #Показывать хеш-значение, однозначно идентифицирующее класс запросов или класс узлов плана. Расширение aqo использует в качестве идентификатора класса запроса идентификатор самого Postgres Pro, чтобы обеспечить согласованность с другими расширениями, такими как pg_stat_statements. Таким образом, идентификатор запроса можно получить из поля Query hash в выводе команды EXPLAIN ANALYZE.
По умолчанию: on (вкл.).
aqo.join_threshold (integer) #Игнорировать запросы, содержащие количество соединений меньше указанного, то есть статистика для таких запросов не собирается.
По умолчанию: 0 (запросы не игнорируются).
aqo.learn_statement_timeout (boolean) #Обучаться на планах запросов, прерванных по тайм-ауту оператора.
По умолчанию: off (выкл.).
aqo.statement_timeout (integer) #Определяет начальное значение так называемого «умного» тайм-аута операторов в миллисекундах, который необходим для ограничения времени выполнения при ручном обучении aqo на специальных запросах с неудовлетворительным прогнозом количества строк. Расширение aqo может динамически изменять умный тайм-аут операторов во время этого обучения. Когда погрешность оценки количества строк на узлах превышает 0.1, значение aqo.statement_timeout автоматически увеличивается экспоненциально, но не превышает statement_timeout.
По умолчанию: 0.
aqo.min_neighbors_for_predicting (integer) #Определяет, сколько выборок, собранных при предыдущих выполнениях запроса, будет использоваться для прогнозирования оценки количества строк в следующий раз. Если количество выборок меньше заданного значения, aqo не будет делать предсказаний. Слишком большое значение может повлиять на производительность, а слишком маленькое — снизить качество предсказания.
По умолчанию: 3.
aqo.predict_with_few_neighbors (boolean) #Позволяет aqo делать предсказания с меньшим количеством соседей, чем указано в параметре aqo.min_neighbors_for_predicting. Если установлено значение off, aqo обучается, но не делает предсказания до тех пор, пока счётчик выполнений запроса с разными константами не достигнет 3 (по умолчанию для aqo.min_neighbors_for_predicting).
По умолчанию: on (вкл.).
aqo.fs_max_items (integer) #Определяет максимальное количество пространств признаков, с которыми может работать aqo. При превышении этого количества обучение на новых классах запросов прекратится, и они не будут отображаться в представлениях. Задать этот параметр можно только при запуске сервера.
Для ведомого сервера значение этого параметра должно быть равно или больше значения на ведущем сервере. В противном случае на ведомом сервере не гарантируется согласованность данных aqo.
По умолчанию: 10000.
aqo.fss_max_items (integer) #Определяет максимальное количество подпространств признаков, с которыми может работать aqo. При превышении этого количества данные об избирательности и предсказание количества строк для новых узлов плана запроса больше не будут собираться и новые подпространства признаков не будут отображаться в представлении aqo_data. Задать этот параметр можно только при запуске сервера.
Для ведомого сервера значение этого параметра должно быть равно или больше значения на ведущем сервере. В противном случае на ведомом сервере не гарантируется согласованность данных aqo.
По умолчанию: 100000.
aqo.querytext_max_size (integer) #Определяет максимальный размер запроса в представлении aqo_query_texts. Задать этот параметр можно только при запуске сервера.
Для ведомого сервера значение этого параметра должно быть равно или больше значения на ведущем сервере. В противном случае на ведомом сервере не гарантируется согласованность данных aqo.
По умолчанию: 1000.
aqo.dsm_size_max (integer) #Определяет максимальный размер динамической разделяемой памяти в мегабайтах, которую модуль aqo может выделить для хранения данных обучения и текстов запросов. Если установленное значение меньше объёма сохранённых данных aqo, сервер не запустится. Задать этот параметр можно только при запуске сервера.
Для ведомого сервера значение этого параметра должно быть равно или больше значения на ведущем сервере. В противном случае на ведомом сервере не гарантируется согласованность данных aqo.
По умолчанию: 100.
aqo.wal_rw (boolean) #Включает физическую репликацию и обеспечивает полное восстановление данных aqo после сбоя. При значении off на ведущем сервере данные на реплику не передаются. При значении off на реплике данные, передаваемые с ведущего сервера, игнорируются. В таком случае при сбое сервера данные могут быть восстановлены только до последней контрольной точки. Этот параметр можно задать только при запуске сервера.
По умолчанию: on (вкл.).
aqo.sandbox (boolean) #Позволяет резервировать отдельную область в общей памяти для использования ведущим или ведомым узлом, что позволяет собирать и использовать статистику с данными в этой области памяти. Если включён на ведомом узле, расширение использует отдельную область общей памяти, которая не реплицируется на ведомый сервер. Изменение значения этого параметра сбрасывает кеш aqo. Изменить этот параметр могут только суперпользователи.
По умолчанию: off (выкл.).
aqo.delta_rows (boolean) #Включает механизм обучения, при котором aqo корректирует оценку количества строк планировщика своими предсказаниями. Если параметр выключен, aqo использует собственные предсказания.
По умолчанию: off (выкл.).
aqo_query_texts #В представлении aqo_query_texts классифицируются все классы запросов, обрабатываемые aqo. Для каждого класса запросов в представлении отображается текст первого проанализированного запроса этого класса. Количество строк ограничено параметром aqo.fs_max_items.
Таблица F.1. Представление aqo_query_texts
| Имя столбца | Описание |
|---|---|
queryid | Уникальный идентификатор запроса. |
dbid | Идентификатор базы данных. |
fs | Идентификатор пространства признаков. |
query_text | Текст первого проанализированного запроса данного класса. Длина текста запроса ограничивается параметром aqo.querytext_max_size. |
aqo_queries #В представлении aqo_queries отображаются свойства оптимизации для разных классов запросов. Один запрос, выполненный в двух разных базах данных, сохраняется дважды с одинаковым значением fs. Количество строк ограничено параметром aqo.fs_max_items.
Таблица F.2. Представление aqo_queries
| Свойство | Описание |
|---|---|
fs | Идентификатор пространства признаков. |
dbid | Идентификатор базы данных, в которой выполнялся запрос. |
learn_aqo | Показывает, включён ли сбор статистики для данного класса запросов. |
use_aqo | Показывает, включено ли предсказание количества строк средствами aqo для следующего выполнения данного класса запросов. |
auto_tuning |
Показывает, может ли aqo динамически изменять параметры При включённом свойстве Запросы с |
smart_timeout | Значение «умного» тайм-аута операторов для данного класса запросов. Начальное значение такого тайм-аута для любого запроса определяется параметром конфигурации statement_timeout. |
count_increase_timeout | Показывает, сколько раз увеличивался «умный» тайм-аут операторов для данного класса запросов. |
aqo_data #В представлении aqo_data отображаются данные машинного обучения для уточнения оценки количества строк. Количество строк ограничено параметром aqo.fss_max_items. Чтобы сбросить всю собранную статистику для определённого класса запросов, можно удалить из представления aqo_data все строки с соответствующим fs.
Таблица F.3. Представление aqo_data
| Данные | Описание |
|---|---|
fs | Идентификатор (хеш) пространства признаков. |
fss | Идентификатор (хеш) подпространства признаков. |
dbid | Идентификатор базы данных. |
delta_rows | Если true, aqo делает предсказания на основе оценки планировщика; в противном случае false. |
nfeatures | Размер подпространства признаков для узла плана запроса. |
features | Логарифм избирательности, на котором основано предсказание количества строк. |
targets | Логарифм количества строк для узла плана запроса. |
reliability | Уровень достоверности статистики обучения:
|
oids | Список идентификаторов таблиц, которые участвовали в предсказании для этого узла. |
tmpoids | Список идентификаторов временных таблиц, которые участвовали в предсказании для этого узла. |
aqo_query_stat #В представлении aqo_query_stat отображается статистика выполнения запросов, группируемая по классам запросов. Расширение aqo использует эти данные, когда для определённого класса запросов включено свойство auto_tuning.
Таблица F.4. Представление aqo_query_stat
| Данные | Описание |
|---|---|
fs | Идентификатор пространства признаков. |
dbid | Идентификатор базы данных. |
execution_time_with_aqo | Массив значений времени выполнения запросов со включённым aqo. |
execution_time_without_aqo | Массив значений времени выполнения запросов с отключённым aqo. |
planning_time_with_aqo | Массив значений времени планирования запросов со включённым aqo. |
planning_time_without_aqo | Массив значений времени планирования запросов с отключённым aqo. |
cardinality_error_with_aqo | Массив ошибок оценки количества строк со включённым aqo в планах выбранных запросов. |
cardinality_error_without_aqo | Массив ошибок оценки количества строк с отключённым aqo в планах выбранных запросов. |
executions_with_aqo | Число запросов, выполненных со включённым aqo. |
executions_without_aqo | Число запросов, выполненных с отключённым aqo. |
Модуль aqo добавляет несколько функций в каталог Postgres Pro.
Функции aqo_queries_update, aqo_query_texts_update, aqo_query_stat_update, aqo_data_update и aqo_data_delete изменяют файлы данных, на которых основаны соответствующие представления aqo. Поэтому вызывайте эти функции только в том случае, если вы понимаете логику адаптивной оптимизации запросов.
aqo_cleanup() → setof integerУдаляет данные, относящиеся к классам запросов, которые связаны (возможно частично) с удалёнными отношениями. Возвращает количество удалённых пространств признаков (классов) и подпространств признаков. Игнорирует удаление других объектов.
aqo_enable_class (fs bigint, dbid oid) → voidУстанавливает для learn_aqo, use_aqo и auto_tuning (только в режиме intelligent) значение true для класса запросов с указанными fs и dbid. Можно использовать для dbid значение NULL вместо идентификатора текущей базы данных.
aqo_disable_class (fs bigint, dbid oid) → voidУстанавливает для learn_aqo, use_aqo и auto_tuning значение false для класса запросов с указанными fs и dbid. Можно использовать для dbid значение NULL вместо идентификатора текущей базы данных.
aqo_drop_class (fs bigint, dbid oid) → integerУдаляет все данные, относящиеся к заданному классу запросов и базе данных, из хранилища aqo. Для параметра dbid можно задать значение NULL вместо идентификатора текущей базы данных. Возвращает количество записей, удалённых из хранилища aqo.
aqo_reset (dbid oid) → bigintУдаляет записи из указанной базы данных: данные машинного обучения, тексты запросов, статистику и свойства классов запросов. Если параметр dbid не указан, данные удаляются из текущей базы данных. Если dbid имеет значение NULL, удаляются все записи из хранилища aqo. Возвращает количество удалённых записей.
aqo_queries_update (fs bigint, dbid oid, learn_aqo boolean, use_aqo boolean, auto_tuning boolean) → booleanИзменяет или вставляет запись в файл данных, лежащий в основе представления aqo_queries, для указанных fs и dbid. Вместо идентификатора текущей базы данных в dbid можно указать NULL. Значения NULL для остальных параметров означают, что их следует оставить без изменений. Обратите внимание, что записи с нулевым значением fs или dbid не могут быть изменены. Возвращает false в случае ошибки и true в противном случае.
aqo_query_texts_update (fs bigint, dbid oid, query_text text) → booleanИзменяет или вставляет запись в файл данных, лежащий в основе представления aqo_query_texts, для указанных fs и dbid. Можно использовать для dbid значение NULL вместо идентификатора текущей базы данных. Обратите внимание, что записи с нулевым значением fs или dbid не могут быть изменены. Возвращает false в случае ошибки и true в противном случае.
aqo_query_stat_update (fs bigint, dbid oid, execution_time_with_aqo double precision[], execution_time_without_aqo double precision[], planning_time_with_aqo double precision[], planning_time_without_aqo double precision[], cardinality_error_with_aqo double precision[], cardinality_error_without_aqo double precision[], executions_with_aqo bigint[], executions_without_aqo bigint[]) → booleanИзменяет или вставляет запись в файл данных, лежащий в основе представления aqo_query_stat, для указанных fs и dbid. Записи для обобщенного пространства признаков, с нулевым значением fs или dbid, не могут быть изменены. Можно использовать для dbid значение NULL вместо идентификатора текущей базы данных. Возвращает false в случае ошибки и true в противном случае.
aqo_data_update (fs bigint, fss integer, dbid oid, delta_rows boolean, nfeatures integer, features double precision[][], targets double precision[], reliability double precision[], oids oid[], tmpoids oid[]) → booleanИзменяет или вставляет запись в файл данных, лежащий в основе представления aqo_data, для указанных fs, fss и dbid. Можно использовать для dbid значение NULL вместо идентификатора текущей базы данных. Если для delta_rows задать NULL, будет использовано значение параметра конфигурации aqo.delta_rows. Возвращает false в случае ошибки и true в противном случае.
aqo_data_delete (fs bigint, fss integer, dbid oid, delta_rows boolean) → booleanУдаляет запись из файла данных, лежащего в основе представления aqo_data, для указанных fs, fss и dbid. Можно использовать для dbid значение NULL вместо идентификатора текущей базы данных. Если не указать параметр delta_rows или задать для него NULL, будет использовано значение параметра конфигурации aqo.delta_rows. Возвращает false в случае ошибки и true в противном случае.
aqo_memory_usage () → setof recordОтображает выделенные и использованные размеры контекстов памяти и хеш-таблиц aqo. Возвращает таблицу со следующими столбцами:
nameКраткое описание контекста памяти или хеш-таблицы
allocated_sizeОбщий размер выделенной памяти
used_sizeРазмер текущей используемой памяти
aqo_cardinality_error (controlled boolean) → setof recordПоказывает ошибку оценки количества строк для последнего выполнения запроса. Если controlled имеет значение true, показывает запросы, выполненные с включённым aqo. Если controlled имеет значение false, показывает запросы, выполненные с отключённым aqo, но имеющие накопленную статистику. Возвращает таблицу со следующими столбцами:
numПорядковый номер
dbidИдентификатор базы данных
fsИдентификатор пространства признаков. Может содержать ноль или основное хеш-значение.
errorПогрешность aqo, рассчитываемая на узлах планов запросов
nexecsКоличество выполнений запросов, связанных с данным fs
aqo_execution_time (controlled boolean) → setof recordПоказывает время выполнения запросов. Если controlled имеет значение true, показывает время выполнения последнего запроса с включённым aqo. Если controlled имеет значение false, возвращает среднее время выполнения запроса для всех записанных в журнал выполнений с отключённым aqo. Время выполнения без aqo можно собрать, если параметр aqo.mode = intelligent или параметр aqo.force_collect_stat = on. Возвращает таблицу со следующими столбцами:
numПорядковый номер
dbidИдентификатор базы данных
fsИдентификатор пространства признаков. Может содержать ноль или основное хеш-значение.
exec_timeЕсли controlled имеет значение true, показывает время выполнения последнего запроса с включённым aqo, в противном случае — среднее время выполнения запроса для всех записанных в журнал выполнений с отключённым aqo.
nexecsКоличество выполнений запросов, связанных с данным fs
Пример F.1. Обучение на запросе (базовый режим)
Рассмотрим оптимизацию запроса с использованием расширения aqo.
Когда запрос выполняется в первый раз, его нет в таблицах, лежащих в основе представлений aqo. Таким образом, данных для предсказания aqo для каждого узла плана нет, и в выводе EXPLAIN появляются строки «AQO not used»:
demo=# EXPLAIN (ANALYZE, SUMMARY OFF, TIMING OFF)
SELECT bp.*
FROM segments s
JOIN flights f ON f.flight_id = s.flight_id
JOIN boarding_passes bp ON bp.ticket_no = s.ticket_no AND bp.flight_id = s.flight_id
WHERE f.scheduled_departure > '2025-12-1 15:00:00+00';
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
Hash Join (cost=79793.72..237497.86 rows=1201002 width=33) (actual rows=9455.00 loops=1)
AQO not used, fss=4871603661380287993
Hash Cond: ((s.flight_id = f.flight_id) AND (s.ticket_no = bp.ticket_no))
Buffers: shared hit=3713 read=50331, temp read=17210 written=17210
-> Seq Scan on segments s (cost=0.00..72572.70 rows=3941270 width=18) (actual rows=3941249.00 loops=1)
AQO not used, fss=-1745942650988724053
Buffers: shared hit=1853 read=31307
-> Hash (cost=52395.69..52395.69 rows=1201002 width=37) (actual rows=9455.00 loops=1)
Buckets: 131072 Batches: 16 Memory Usage: 1058kB
Buffers: shared hit=1860 read=19024, temp written=45
-> Hash Join (cost=608.55..52395.69 rows=1201002 width=37) (actual rows=9455.00 loops=1)
AQO not used, fss=4705493075117122362
Hash Cond: (bp.flight_id = f.flight_id)
Buffers: shared hit=1860 read=19024
-> Seq Scan on boarding_passes bp (cost=0.00..45318.32 rows=2463832 width=33) (actual rows=2463832.00 loops=1)
AQO not used, fss=1362775811343989307
Buffers: shared hit=1656 read=19024
-> Hash (cost=475.98..475.98 rows=10606 width=4) (actual rows=10594.00 loops=1)
Buckets: 16384 Batches: 1 Memory Usage: 501kB
Buffers: shared hit=204
-> Seq Scan on flights f (cost=0.00..475.98 rows=10606 width=4) (actual rows=10594.00 loops=1)
AQO not used, fss=3484507337497244877
Filter: (scheduled_departure > '2025-12-01 22:00:00+07'::timestamp with time zone)
Rows Removed by Filter: 11164
Buffers: shared hit=204
Planning:
Buffers: shared hit=53
Using aqo: true
AQO mode: AUTO
AQO advanced: OFF
Query hash: 0
JOINS: 2
(32 rows)
Если в представлении aqo_data нет информации об определённом узле, aqo добавит в него соответствующую запись для дальнейшего изучения и предсказания, за исключением узлов с fss=0 в выводе EXPLAIN. Поскольку значения в полях features и targets в представлении aqo_data являются логарифмом по основанию e, чтобы получить фактическое значение, возведите e в соответствующую степень. Например: exp(9.154298981092557):
demo=# select * from aqo_data;
fs | fss | dbid | delta_rows | nfeatures | features | targets | reliability | oids | tmpoids
----+----------------------+-------+------------+-----------+---------------------------------------------+----------------------+-------------+---------------------+---------
0 | 3484507337497244877 | 16556 | f | 1 | {{-0.7185575545175223}} | {9.268043082104471} | {1} | {17452} |
0 | -1745942650988724053 | 16556 | f | 0 | | {15.187008236114766} | {1} | {17488} |
0 | 4705493075117122362 | 16556 | f | 2 | {{-0.7185575545175223,-9.987736784981028}} | {9.154298981092557} | {1} | {17452,17438} |
0 | 1362775811343989307 | 16556 | f | 0 | | {14.71722841949288} | {1} | {17438} |
0 | 4871603661380287993 | 16556 | f | 2 | {{-0.7185575545175223,-14.672062325711414}} | {9.154298981092557} | {1} | {17488,17452,17438} |
(5 rows)
При повторном выполнении запроса aqo распознаёт его и делает предсказание. Обратите внимание на оценку количества строк, предсказанную aqo, и значение погрешности aqo («error=0%»).
demo=# EXPLAIN (ANALYZE, SUMMARY OFF, TIMING OFF)
SELECT bp.*
FROM segments s
JOIN flights f ON f.flight_id = s.flight_id
JOIN boarding_passes bp ON bp.ticket_no = s.ticket_no AND bp.flight_id = s.flight_id
WHERE f.scheduled_departure > '2025-12-1 15:00:00+00';
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
Gather (cost=1608.83..38142.58 rows=9455 width=33) (actual rows=9455.00 loops=1)
Workers Planned: 2
Workers Launched: 2
Buffers: shared hit=27340 read=22325
-> Nested Loop (cost=608.83..36197.08 rows=3940 width=33) (actual rows=3151.67 loops=3)
AQO: rows=9455, error=0%, fss=4871603661380287993
Join Filter: (s.flight_id = f.flight_id)
Buffers: shared hit=27340 read=22325
-> Hash Join (cost=608.40..34249.71 rows=3940 width=37) (actual rows=3151.67 loops=3)
AQO: rows=9455, error=0%, fss=4705493075117122362
Hash Cond: (bp.flight_id = f.flight_id)
Buffers: shared hit=2360 read=18932
-> Parallel Seq Scan on boarding_passes bp (cost=0.00..30945.97 rows=1026597 width=33) (actual rows=821277.33 loops=3)
AQO: rows=2463832, error=0%, fss=1362775811343989307
Buffers: shared hit=1748 read=18932
-> Hash (cost=475.98..475.98 rows=10594 width=4) (actual rows=10594.00 loops=3)
Buckets: 16384 Batches: 1 Memory Usage: 501kB
Buffers: shared hit=612
-> Seq Scan on flights f (cost=0.00..475.98 rows=10594 width=4) (actual rows=10594.00 loops=3)
AQO: rows=10594, error=0%, fss=3484507337497244877
Filter: (scheduled_departure > '2025-12-01 22:00:00+07'::timestamp with time zone)
Rows Removed by Filter: 11164
Buffers: shared hit=612
-> Index Only Scan using segments_pkey on segments s (cost=0.43..0.48 rows=1 width=18) (actual rows=1.00 loops=9455)
AQO not used (early terminated), fss=-5182591529139042748
Index Cond: ((ticket_no = bp.ticket_no) AND (flight_id = bp.flight_id))
Heap Fetches: 0
Index Searches: 9455
Buffers: shared hit=24980 read=3393
Planning:
Buffers: shared hit=53
Using aqo: true
AQO mode: AUTO
AQO advanced: OFF
Query hash: 0
JOINS: 2
(36 rows)
Изменив константу в запросе, можно заметить, что предсказание сделано с ошибкой:
demo=# EXPLAIN (ANALYZE, SUMMARY OFF, TIMING OFF)
SELECT bp.*
FROM segments s
JOIN flights f ON f.flight_id = s.flight_id
JOIN boarding_passes bp ON bp.ticket_no = s.ticket_no AND bp.flight_id = s.flight_id
WHERE f.scheduled_departure > '2025-11-20 15:00:00+00';
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
Gather (cost=1608.83..38142.58 rows=9455 width=33) (actual rows=438899.00 loops=1)
Workers Planned: 2
Workers Launched: 2
Buffers: shared hit=1307156 read=30841
-> Nested Loop (cost=608.83..36197.08 rows=3940 width=33) (actual rows=146299.67 loops=3)
AQO: rows=9455, error=-4542%, fss=4871603661380287993
Join Filter: (s.flight_id = f.flight_id)
Buffers: shared hit=1307156 read=30841
-> Hash Join (cost=608.40..34249.71 rows=3940 width=37) (actual rows=146299.67 loops=3)
AQO: rows=9455, error=-4542%, fss=4705493075117122362
Hash Cond: (bp.flight_id = f.flight_id)
Buffers: shared hit=1521 read=19771
-> Parallel Seq Scan on boarding_passes bp (cost=0.00..30945.97 rows=1026597 width=33) (actual rows=821277.33 loops=3)
AQO: rows=2463832, error=0%, fss=1362775811343989307
Buffers: shared hit=909 read=19771
-> Hash (cost=475.98..475.98 rows=10594 width=4) (actual rows=12593.00 loops=3)
Buckets: 16384 Batches: 1 Memory Usage: 571kB
Buffers: shared hit=612
-> Seq Scan on flights f (cost=0.00..475.98 rows=10594 width=4) (actual rows=12593.00 loops=3)
AQO: rows=10594, error=-19%, fss=3484507337497244877
Filter: (scheduled_departure > '2025-11-20 22:00:00+07'::timestamp with time zone)
Rows Removed by Filter: 9165
Buffers: shared hit=612
-> Index Only Scan using segments_pkey on segments s (cost=0.43..0.48 rows=1 width=18) (actual rows=1.00 loops=438899)
AQO not used (early terminated), fss=-5182591529139042748
Index Cond: ((ticket_no = bp.ticket_no) AND (flight_id = bp.flight_id))
Heap Fetches: 0
Index Searches: 438899
Buffers: shared hit=1305635 read=11070
Planning:
Buffers: shared hit=53
Using aqo: true
AQO mode: AUTO
AQO advanced: OFF
Query hash: 0
JOINS: 2
(36 rows)
Однако вместо пересчёта полей features и targets, aqo добавил новые значения избирательности и оценки количества строк для этого запроса в aqo_data:
demo=# SELECT * FROM aqo_data;
fs | fss | dbid | delta_rows | nfeatures | features | targets | reliability | oids | tmpoids
----+----------------------+-------+------------+-----------+---------------------------------------------------------------------------------------+---------------------------------------+-------------+---------------------+---------
0 | 3484507337497244877 | 16556 | f | 1 | {{-0.7185575545175223},{-0.5463556163769266}} | {9.268043082104471,9.440896383005846} | {1,1} | {17452} |
0 | -1745942650988724053 | 16556 | f | 0 | | {15.187008236114766} | {1} | {17488} |
0 | 4705493075117122362 | 16556 | f | 2 | {{-0.7185575545175223,-9.987736784981028},{-0.5463556163769266,-9.987736784981028}} | {9.154298981092557,12.9920245972504} | {1,1} | {17452,17438} |
0 | 1362775811343989307 | 16556 | f | 0 | | {14.71722841949288} | {1} | {17438} |
0 | 4871603661380287993 | 16556 | f | 2 | {{-0.7185575545175223,-14.672062325711414},{-0.5463556163769266,-14.672062325711414}} | {9.154298981092557,12.9920245972504} | {1,1} | {17488,17452,17438} |
(5 rows)
Теперь в предсказании есть небольшая погрешность примерно в 2%, которая может объясняться погрешностью вычислений:
demo=# EXPLAIN (ANALYZE, SUMMARY OFF, TIMING OFF)
SELECT bp.*
FROM segments s
JOIN flights f ON f.flight_id = s.flight_id
JOIN boarding_passes bp ON bp.ticket_no = s.ticket_no AND bp.flight_id = s.flight_id
WHERE f.scheduled_departure > '2025-11-20 15:00:00+00';
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------
Gather (cost=39355.89..164831.92 rows=429336 width=33) (actual rows=438899.00 loops=1)
Workers Planned: 2
Workers Launched: 2
Buffers: shared hit=1619 read=52833, temp read=21086 written=21152
-> Parallel Hash Join (cost=38355.89..120898.32 rows=178890 width=33) (actual rows=146299.67 loops=3)
AQO: rows=429336, error=-2%, fss=4871603661380287993
Hash Cond: ((s.flight_id = f.flight_id) AND (s.ticket_no = bp.ticket_no))
Buffers: shared hit=1619 read=52833, temp read=21086 written=21152
-> Parallel Seq Scan on segments s (cost=0.00..49581.96 rows=1642187 width=18) (actual rows=1313749.67 loops=3)
AQO: rows=3941249, error=0%, fss=-1745942650988724053
Buffers: shared read=33160
-> Parallel Hash (cost=34274.54..34274.54 rows=178890 width=37) (actual rows=146299.67 loops=3)
Buckets: 131072 Batches: 8 Memory Usage: 4928kB
Buffers: shared hit=1619 read=19673, temp written=2812
-> Hash Join (cost=633.24..34274.54 rows=178890 width=37) (actual rows=146299.67 loops=3)
AQO: rows=429336, error=-2%, fss=4705493075117122362
Hash Cond: (bp.flight_id = f.flight_id)
Buffers: shared hit=1619 read=19673
-> Parallel Seq Scan on boarding_passes bp (cost=0.00..30945.97 rows=1026597 width=33) (actual rows=821277.33 loops=3)
AQO: rows=2463832, error=0%, fss=1362775811343989307
Buffers: shared hit=1007 read=19673
-> Hash (cost=475.98..475.98 rows=12581 width=4) (actual rows=12593.00 loops=3)
Buckets: 16384 Batches: 1 Memory Usage: 571kB
Buffers: shared hit=612
-> Seq Scan on flights f (cost=0.00..475.98 rows=12581 width=4) (actual rows=12593.00 loops=3)
AQO: rows=12581, error=-0%, fss=3484507337497244877
Filter: (scheduled_departure > '2025-11-20 22:00:00+07'::timestamp with time zone)
Rows Removed by Filter: 9165
Buffers: shared hit=612
Planning:
Buffers: shared hit=53
Using aqo: true
AQO mode: AUTO
AQO advanced: OFF
Query hash: 0
JOINS: 2
(36 rows)
Можно изменить запрос, добавив некоторую таблицу в список JOIN. В этом случае aqo будет прогнозировать оценку количества строк на узлах, использовавшихся для обучения.
demo=# EXPLAIN (ANALYZE, SUMMARY OFF, TIMING OFF)
SELECT bp.*
FROM segments s
JOIN flights f ON f.flight_id = s.flight_id
JOIN boarding_passes bp ON bp.ticket_no = s.ticket_no AND bp.flight_id = s.flight_id
JOIN tickets t ON t.ticket_no = s.ticket_no
WHERE f.scheduled_departure > '2025-12-1 15:00:00+00';
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------
Gather (cost=1609.40..40084.93 rows=9666 width=33) (actual rows=9455.00 loops=1)
Workers Planned: 2
Workers Launched: 2
Buffers: shared hit=53810 read=24225 written=1
-> Nested Loop (cost=609.40..38118.33 rows=4028 width=33) (actual rows=3151.67 loops=3)
AQO not used, fss=3232027643707566962
Buffers: shared hit=53810 read=24225 written=1
-> Nested Loop (cost=608.97..36240.71 rows=4028 width=47) (actual rows=3151.67 loops=3)
AQO: rows=9666, error=2%, fss=4871603661380287993
Join Filter: (s.flight_id = f.flight_id)
Buffers: shared hit=28230 read=21435
-> Hash Join (cost=608.54..34249.84 rows=4028 width=37) (actual rows=3151.67 loops=3)
AQO: rows=9666, error=2%, fss=4705493075117122362
Hash Cond: (bp.flight_id = f.flight_id)
Buffers: shared hit=1006 read=20286
-> Parallel Seq Scan on boarding_passes bp (cost=0.00..30945.97 rows=1026597 width=33) (actual rows=821277.33 loops=3)
AQO: rows=2463832, error=0%, fss=1362775811343989307
Buffers: shared hit=394 read=20286
-> Hash (cost=475.98..475.98 rows=10605 width=4) (actual rows=10594.00 loops=3)
Buckets: 16384 Batches: 1 Memory Usage: 501kB
Buffers: shared hit=612
-> Seq Scan on flights f (cost=0.00..475.98 rows=10605 width=4) (actual rows=10594.00 loops=3)
AQO: rows=10605, error=0%, fss=3484507337497244877
Filter: (scheduled_departure > '2025-12-01 22:00:00+07'::timestamp with time zone)
Rows Removed by Filter: 11164
Buffers: shared hit=612
-> Index Only Scan using segments_pkey on segments s (cost=0.43..0.48 rows=1 width=18) (actual rows=1.00 loops=9455)
AQO not used (early terminated), fss=-5182591529139042748
Index Cond: ((ticket_no = bp.ticket_no) AND (flight_id = bp.flight_id))
Heap Fetches: 0
Index Searches: 9455
Buffers: shared hit=27224 read=1149
-> Index Only Scan using tickets_pkey on tickets t (cost=0.43..0.47 rows=1 width=14) (actual rows=1.00 loops=9455)
AQO not used (early terminated), fss=1810536986390200978
Index Cond: (ticket_no = bp.ticket_no)
Heap Fetches: 0
Index Searches: 9455
Buffers: shared hit=25580 read=2790 written=1
Planning:
Buffers: shared hit=121 read=11 dirtied=1
Using aqo: true
AQO mode: AUTO
AQO advanced: OFF
Query hash: 0
JOINS: 3
(45 rows)
Пример F.2. Использование представления aqo_query_stat
В представлении aqo_query_stat отображается статистика времени планирования запросов, времени выполнения запросов и ошибок оценки количества строк. На основании этих данных вы можете принимать решения об использовании предсказаний aqo для различных классов запросов.
Обратимся к представлению aqo_query_stats:
demo=# SELECT * FROM aqo_query_stat \gx
-[ RECORD 1 ]-----------------+----------------------------------------------------------------
fs | 0
dbid | 16556
execution_time_with_aqo | {1.194791831,0.497019753,0.372696583,0.071416851}
execution_time_without_aqo | {1.194049191,1.003504607}
planning_time_with_aqo | {0.004099525,0.000548588,0.000518923,0.000545041}
planning_time_without_aqo | {0.000568455,0.000472447}
cardinality_error_with_aqo | {0.47163214679982596,0,1.5696609066434117,0.009035905503851183}
cardinality_error_without_aqo | {0.47163214679982596,1.9379745948665572}
executions_with_aqo | 4
executions_without_aqo | 2 Полученные данные относятся к запросу, рассмотренному в примере Пример F.1. Этот запрос выполнялся с каждым из параметров f.scheduled_departure > '2025-11-20 15:00:00+00' и f.scheduled_departure > '2025-12-1 15:00:00+00' по одному разу без aqo и по два раза с aqo. Видно, что с aqo погрешность оценки количества строк уменьшается до 0,009, а минимальная погрешность оценки количества строк без aqo составляет 0,471. Кроме того, время выполнения с aqo меньше, чем без него. Таким образом, можно сделать вывод, что aqo хорошо обучается на этом запросе и предсказание можно использовать для этого класса запросов.
Пример F.3. Использование aqo в расширенном режиме
Расширенный режим позволяет более гибко управлять расширением aqo. При включении данного режима, то есть
demo=# SET aqo.advanced = on;
aqo будет собирать данные машинного обучения отдельно для каждого выполняемого запроса. Например:
demo=# EXPLAIN (ANALYZE, SUMMARY OFF, TIMING OFF)
SELECT bp.*
FROM segments s
JOIN flights f ON f.flight_id = s.flight_id
JOIN boarding_passes bp ON bp.ticket_no = s.ticket_no AND bp.flight_id = s.flight_id
WHERE f.scheduled_departure > '2025-12-1 15:00:00+00';
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
Hash Join (cost=79793.72..237497.86 rows=1201002 width=33) (actual rows=9455.00 loops=1)
AQO not used, fss=4871603661380287993
Hash Cond: ((s.flight_id = f.flight_id) AND (s.ticket_no = bp.ticket_no))
Buffers: shared hit=4958 read=49086, temp read=17210 written=17210
-> Seq Scan on segments s (cost=0.00..72572.70 rows=3941270 width=18) (actual rows=3941249.00 loops=1)
AQO not used, fss=-1745942650988724053
Buffers: shared hit=2116 read=31044
-> Hash (cost=52395.69..52395.69 rows=1201002 width=37) (actual rows=9455.00 loops=1)
Buckets: 131072 Batches: 16 Memory Usage: 1058kB
Buffers: shared hit=2842 read=18042, temp written=45
-> Hash Join (cost=608.55..52395.69 rows=1201002 width=37) (actual rows=9455.00 loops=1)
AQO not used, fss=4705493075117122362
Hash Cond: (bp.flight_id = f.flight_id)
Buffers: shared hit=2842 read=18042
-> Seq Scan on boarding_passes bp (cost=0.00..45318.32 rows=2463832 width=33) (actual rows=2463832.00 loops=1)
AQO not used, fss=1362775811343989307
Buffers: shared hit=2638 read=18042
-> Hash (cost=475.98..475.98 rows=10606 width=4) (actual rows=10594.00 loops=1)
Buckets: 16384 Batches: 1 Memory Usage: 501kB
Buffers: shared hit=204
-> Seq Scan on flights f (cost=0.00..475.98 rows=10606 width=4) (actual rows=10594.00 loops=1)
AQO not used, fss=3484507337497244877
Filter: (scheduled_departure > '2025-12-01 22:00:00+07'::timestamp with time zone)
Rows Removed by Filter: 11164
Buffers: shared hit=204
Planning:
Buffers: shared hit=463
Using aqo: true
AQO mode: AUTO
AQO advanced: ON
Query hash: 6166891552805381787
JOINS: 2
(32 rows)
Теперь этот запрос хранится в aqo_data с ненулевым fs (по умолчанию fs равен хешу запроса):
demo=# SELECT * FROM aqo_data;
fs | fss | dbid | delta_rows | nfeatures | features | targets | reliability | oids | tmpoids
---------------------+----------------------+-------+------------+-----------+---------------------------------------------+----------------------+-------------+---------------------+---------
6166891552805381787 | 4705493075117122362 | 16556 | f | 2 | {{-0.7185575545175223,-9.987736784981028}} | {9.154298981092557} | {1} | {17452,17438} |
6166891552805381787 | 4871603661380287993 | 16556 | f | 2 | {{-0.7185575545175223,-14.672062325711414}} | {9.154298981092557} | {1} | {17488,17452,17438} |
6166891552805381787 | 1362775811343989307 | 16556 | f | 0 | | {14.71722841949288} | {1} | {17438} |
6166891552805381787 | 3484507337497244877 | 16556 | f | 1 | {{-0.7185575545175223}} | {9.268043082104471} | {1} | {17452} |
6166891552805381787 | -1745942650988724053 | 16556 | f | 0 | | {15.187008236114766} | {1} | {17488} |
(5 rows)
We can make a few settings individually for this query. These are values of learn_aqo, use_aqo and auto_tuning in the aqo_queries view:
demo=# SELECT * FROM aqo_queries;
fs | dbid | learn_aqo | use_aqo | auto_tuning | smart_timeout | count_increase_timeout
---------------------+-------+-----------+---------+-------------+---------------+------------------------
6166891552805381787 | 16556 | t | t | f | 0 | 0
0 | 0 | f | f | f | 0 | 0
(2 rows)
Зададим для use_aqo значение false:
demo=# SELECT aqo_queries_update(6166891552805381787, NULL, NULL, false, NULL); aqo_queries_update -------------------- t (1 row)
Теперь изменим константу в запросе:
demo=# EXPLAIN (ANALYZE, SUMMARY OFF, TIMING OFF)
SELECT bp.*
FROM segments s
JOIN flights f ON f.flight_id = s.flight_id
JOIN boarding_passes bp ON bp.ticket_no = s.ticket_no AND bp.flight_id = s.flight_id
WHERE f.scheduled_departure > '2025-11-20 15:00:00+00';
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
Hash Join (cost=84966.87..244434.20 rows=1426685 width=33) (actual rows=438899.00 loops=1)
AQO not used, fss=0
Hash Cond: ((s.flight_id = f.flight_id) AND (s.ticket_no = bp.ticket_no))
Buffers: shared hit=5142 read=48902, temp read=20132 written=20132
-> Seq Scan on segments s (cost=0.00..72572.70 rows=3941270 width=18) (actual rows=3941249.00 loops=1)
AQO not used, fss=0
Buffers: shared hit=2208 read=30952
-> Hash (cost=52420.60..52420.60 rows=1426685 width=37) (actual rows=438899.00 loops=1)
Buckets: 131072 Batches: 16 Memory Usage: 2925kB
Buffers: shared hit=2934 read=17950, temp written=2967
-> Hash Join (cost=633.46..52420.60 rows=1426685 width=37) (actual rows=438899.00 loops=1)
AQO not used, fss=0
Hash Cond: (bp.flight_id = f.flight_id)
Buffers: shared hit=2934 read=17950
-> Seq Scan on boarding_passes bp (cost=0.00..45318.32 rows=2463832 width=33) (actual rows=2463832.00 loops=1)
AQO not used, fss=0
Buffers: shared hit=2730 read=17950
-> Hash (cost=475.98..475.98 rows=12599 width=4) (actual rows=12593.00 loops=1)
Buckets: 16384 Batches: 1 Memory Usage: 571kB
Buffers: shared hit=204
-> Seq Scan on flights f (cost=0.00..475.98 rows=12599 width=4) (actual rows=12593.00 loops=1)
AQO not used, fss=0
Filter: (scheduled_departure > '2025-11-20 22:00:00+07'::timestamp with time zone)
Rows Removed by Filter: 9165
Buffers: shared hit=204
Planning:
Buffers: shared hit=53
Using aqo: false
AQO mode: AUTO
AQO advanced: ON
Query hash: 6166891552805381787
JOINS: 2
(32 rows)
aqo не использовался для этого запроса, но в представлении aqo_data появились новые данные:
demo=# SELECT * FROM aqo_data;
fs | fss | dbid | delta_rows | nfeatures | features | targets | reliability | oids | tmpoids
---------------------+----------------------+-------+------------+-----------+---------------------------------------------------------------------------------------+---------------------------------------+-------------+---------------------+---------
6166891552805381787 | 4705493075117122362 | 16556 | f | 2 | {{-0.7185575545175223,-9.987736784981028},{-0.5463556163769266,-9.987736784981028}} | {9.154298981092557,12.9920245972504} | {1,1} | {17452,17438} |
6166891552805381787 | 4871603661380287993 | 16556 | f | 2 | {{-0.7185575545175223,-14.672062325711414},{-0.5463556163769266,-14.672062325711414}} | {9.154298981092557,12.9920245972504} | {1,1} | {17488,17452,17438} |
6166891552805381787 | 1362775811343989307 | 16556 | f | 0 | | {14.71722841949288} | {1} | {17438} |
6166891552805381787 | 3484507337497244877 | 16556 | f | 1 | {{-0.7185575545175223},{-0.5463556163769266}} | {9.268043082104471,9.440896383005846} | {1,1} | {17452} |
6166891552805381787 | -1745942650988724053 | 16556 | f | 0 | | {15.187008236114766} | {1} | {17488} |
(5 rows)
Установленное значение параметра use_aqo не относится к другим запросам. После выполнения другого запроса дважды видно, что aqo обучается на нём и делает для него предсказание:
demo=# EXPLAIN (ANALYZE, SUMMARY OFF, TIMING OFF)
SELECT bp.*
FROM segments s
JOIN flights f ON f.flight_id = s.flight_id
JOIN boarding_passes bp ON bp.ticket_no = s.ticket_no AND bp.flight_id = s.flight_id
JOIN tickets t ON t.ticket_no = s.ticket_no
WHERE f.scheduled_departure > '2025-12-1 15:00:00+00';
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------
Gather (cost=68355.43..129435.66 rows=9455 width=33) (actual rows=9455.00 loops=1)
Workers Planned: 2
Workers Launched: 2
Buffers: shared hit=34424 read=48398, temp read=25496 written=25656
-> Nested Loop (cost=67355.43..127490.16 rows=3940 width=33) (actual rows=3151.67 loops=3)
AQO: rows=9455, error=0%, fss=3232027643707566962
Buffers: shared hit=34424 read=48398, temp read=25496 written=25656
-> Parallel Hash Join (cost=67355.00..125653.57 rows=3940 width=47) (actual rows=3151.67 loops=3)
AQO: rows=9455, error=0%, fss=4871603661380287993
Hash Cond: ((bp.flight_id = f.flight_id) AND (bp.ticket_no = s.ticket_no))
Buffers: shared hit=6286 read=48166, temp read=25496 written=25656
-> Parallel Seq Scan on boarding_passes bp (cost=0.00..30945.97 rows=1026597 width=33) (actual rows=821277.33 loops=3)
AQO: rows=2463832, error=0%, fss=1362775811343989307
Buffers: shared hit=3098 read=17582
-> Parallel Hash (cost=54501.93..54501.93 rows=616138 width=22) (actual rows=492910.33 loops=3)
Buckets: 131072 Batches: 16 Memory Usage: 6144kB
Buffers: shared hit=3188 read=30584, temp written=7556
-> Hash Join (cost=608.40..54501.93 rows=616138 width=22) (actual rows=492910.33 loops=3)
AQO: rows=1478731, error=0%, fss=4547398436029445256
Hash Cond: (s.flight_id = f.flight_id)
Buffers: shared hit=3188 read=30584
-> Parallel Seq Scan on segments s (cost=0.00..49581.96 rows=1642187 width=18) (actual rows=1313749.67 loops=3)
AQO: rows=3941249, error=0%, fss=-1745942650988724053
Buffers: shared hit=2576 read=30584
-> Hash (cost=475.98..475.98 rows=10594 width=4) (actual rows=10594.00 loops=3)
Buckets: 16384 Batches: 1 Memory Usage: 501kB
Buffers: shared hit=612
-> Seq Scan on flights f (cost=0.00..475.98 rows=10594 width=4) (actual rows=10594.00 loops=3)
AQO: rows=10594, error=0%, fss=3484507337497244877
Filter: (scheduled_departure > '2025-12-01 22:00:00+07'::timestamp with time zone)
Rows Removed by Filter: 11164
Buffers: shared hit=612
-> Index Only Scan using tickets_pkey on tickets t (cost=0.43..0.47 rows=1 width=14) (actual rows=1.00 loops=9455)
AQO not used (early terminated), fss=1810536986390200978
Index Cond: (ticket_no = bp.ticket_no)
Heap Fetches: 0
Index Searches: 9455
Buffers: shared hit=28138 read=232
Planning:
Buffers: shared hit=85
Using aqo: true
AQO mode: AUTO
AQO advanced: ON
Query hash: 5639045936347396923
JOINS: 3
(45 rows)
Пример F.4. Использование режима «песочницы»
SET aqo.sandbox = ON; SET aqo.enable = ON; SET aqo.advanced = OFF; -- Очистка базы знаний песочницы, не затрагивающая основные данные SELECT aqo_reset(); EXPLAIN (ANALYZE, SUMMARY OFF, TIMING OFF) SELECT bp.* FROM segments s JOIN flights f ON f.flight_id = s.flight_id JOIN boarding_passes bp ON bp.ticket_no = s.ticket_no AND bp.flight_id = s.flight_id JOIN tickets t ON t.ticket_no = s.ticket_no WHERE f.scheduled_departure > '2025-12-1 15:00:00+00'; -- Выполнение предыдущего запроса, пока планы не стабилизируются ... -- Копирование данных, полученных из песочницы с aqo.advanced = OFF CREATE TABLE aqo_data_sandbox AS SELECT * FROM aqo_data; SET aqo.sandbox = OFF; SELECT aqo_data_update (fs, fss, dbid, delta_rows, nfeatures, features, targets, reliability, oids, tmpoids) FROM aqo_data_sandbox WHERE fs = 0; DROP TABLE aqo_data_sandbox;
Олег Иванов