F.83. vops — поддержка векторных операций #
Модуль vops
— экспериментальное расширение Postgres Pro Enterprise, поддерживающее векторные операции, что позволяет ускорить запросы OLAP с фильтрацией и агрегированием более чем в 10 раз. Такое увеличение производительности достигается благодаря реализации вертикальной модели данных, в которой данные группируются по значениям столбцов и хранятся в виде «плиток». Этот формат хранения в некоторых аналитических базах данных также называется «паркетом». Уменьшая издержки хранения неупакованных кортежей, вертикальная модель данных позволяет ускорить выполнение запросов без радикальных изменений в планировщике и исполнителе Postgres Pro.
vops
предоставляет собственный набор векторных функций, которые можно использовать в предикатах и агрегатных выражениях. Чтобы не изучать новый синтаксис, вы можете воспользоваться postgres_fdw в качестве уровня абстракции и выполнять обычные SQL-запросы к векторизованным данным. Для других типов запросов (не фильтрующих и не агрегирующих) vops
может также посредством postgres_fdw
представлять векторизованные данные в виде обычной таблицы со скалярными типами столбцов и обрабатывать их как обычные кортежи. Для упрощения использования vops
также предоставляет автоматический способ формирования векторизованного представления данных и обращения к нему, о чём подробнее рассказывается в Подразделе F.83.5.5.
F.83.1. Ограничения #
Таблицы в виде плиток допускают только операции
INSERT
иAPPEND
. Загруженные в них векторизованные данные изменить нельзя.Векторные операции возможны только для фильтрации и агрегирования. Все остальные типы запросов поддерживаются через
postgres_fdw
.Агрегатные выражения должны быть одинакового типа.
Операции
JOIN
для векторных типов не поддерживаются. Для выполнения таких запросов необходимо преобразовать векторизованные данных в обычные кортежи, используяpostgres_fdw
.Так как
postgres_fdw
не поддерживает выполнение параллельных запросов, такие запросы вы сможете выполнять только применяя непосредственно векторные операции.
F.83.2. Установка и подготовка #
Расширение vops
включено в состав Postgres Pro Enterprise. Установив Postgres Pro Enterprise, выполните следующие действия, чтобы подготовить vops
к работе:
Добавьте
vops
в параметр shared_preload_libraries в файлеpostgresql.conf
:shared_preload_libraries = 'vops'
Примечание
Если библиотека расширения не добавлена в список
shared_preload_libraries
, она будет загружена по требованию при первом вызове функции этого расширения. В этом случае первый векторизованный запрос может выполниться неправильно и выдать некорректный результат.Перезапустите Postgres Pro Enterprise, чтобы изменения вступили в силу.
Создайте расширение
vops
:CREATE EXTENSION vops;
Подготовив vops
к работе, вы можете создавать векторизованные таблицы и запрашивать данные, используя векторные операции.
Если вы предпочитаете использовать обычный синтаксис SQL для векторных типов, вы должны также включить postgres_fdw.
F.83.3. Объяснение #
С нагрузкой OLTP Postgres Pro показывает себя достаточно хорошо в сравнении с другими популярными СУБД. Однако запросы OLAP, охватывающие большие объёмы данных, могут выполняться гораздо быстрее системами, ориентированными на аналитическую обработку. Производительность Postgres Pro ограничена следующими факторами:
Издержки распаковки кортежей. Чтобы прочитать значение столбца, Postgres Pro должен распаковать кортеж. Так как таблицы имеют столбцы разных типов переменной длины, для извлечения N-го столбца необходимо распаковать предшествующие столбцы (N-1). Таким образом преобразование кортежа — довольно дорогая операция, особенно для таблиц с большим количеством атрибутов. Помимо этого, значения могут храниться в сжатом виде или на другой странице (TOAST). В запросах, подобных запросу 6 теста TPC-H, преобразование кортежей занимает около 40% общего времени выполнения.
Издержки интерпретирования. Планировщик и оптимизатор Postgres Pro строят дерево, представляющее план выполнения запроса. Исполнитель запроса рекурсивно вызывает функции, вычисляющие узлы этого дерева. Некоторые узлы могут также содержать условия, выбирающие требуемое действие. Таким образом план запроса не выполняется непосредственно, а интерпретируется исполнителем запросов Postgres Pro. Этот интерпретатор работает примерно в 10 раз медленнее машинного кода. Если исключить издержки интерпретирования, скорость запросов можно увеличить многократно; особенно это касается запросов со сложными предикатами, стоимость вычисления которых высока.
Издержки абстрагирования. Поддержка абстрактных (определяемых пользователем) типов и операций является одной из ключевых особенностей Postgres Pro. Однако ценой этой гибкости является необходимость отдельного вызова функции при каждой операции. Вместо того, чтобы непосредственно сложить два целых числа, исполнитель Postgres Pro вызывает функцию, выполняющую сложение. В этом случае издержки вызова функции оказываются гораздо больше стоимости самой операции. Эти издержки дополнительно увеличиваются из-за того, что Postgres Pro требует передачи значений параметров через память.
Недостатки извлекающей модели. В Postgres Pro операторы извлекают значения операндов. Этот подход упрощает реализацию операторов и исполнителя, но отрицательно сказывается на производительности. В этой модели узлы нижнего уровня, извлекающие кортежи из страниц кучи или индексов, должны проделывать много дополнительной работы по сохранению и восстановлению своего контекста.
Издержки, связанные с MVCC. Postgres Pro обеспечивает многоверсионное управление конкурентным доступом, что позволяет нескольким транзакциям работать с одной и той же записью параллельно, не блокируя друг друга. Это хорошо для часто изменяемых данных (OLTP), но для данных, которые только читаются или только добавляются в сценариях OLAP, впустую расходуется не только место (около 20 дополнительных байт на кортеж), но и ресурсы процессора (для проверки видимости каждого кортежа).
Векторные операции позволяют устранить большинство этих факторов без радикальных изменений в исполнителе Postgres Pro, как описывается в Подразделе F.83.4.
F.83.4. Архитектура #
Расширение vops
реализует вертикальную модель данных для Postgres Pro. В этой модели данные хранятся в вертикальных столбцах, или векторах, формируемых из значений соответствующих атрибутов таблицы.
Вертикальная модель данных, которую реализует vops
, имеет следующие преимущества:
Уменьшение размера считываемых данных: считываются только те столбцы, которые используются в запросе.
Более эффективное сжатие: когда все значения одного атрибута хранятся вместе, их можно сжимать лучше и быстрее. Например, вы можете применить дельта-кодирование.
Минимизация издержек интерпретирования: набор значений может быть обработан одной операцией, выполнять отдельные операции для каждого значения не требуется.
Использование векторных инструкций процессора (SIMD) при обработке данных.
Традиционный исполнитель запросов Postgres Pro имеет дело с одной строкой данных в каждый момент времени. Например, чтобы вычислить выражение (x+y)
, он сначала извлекает значение x
, затем извлекает значение y
, выполняет сложение и, наконец, выдаёт значение результата верхнему узлу. Векторизованный исполнитель, напротив, может обработать множество значений в одной операции. В этом случае x
и y
представляют векторы значений, а не отдельные скаляры. Возвращаемым результатом также будет вектор. В векторной модели выполнения издержки интерпретации и вызовов функций делятся на размер вектора. Стоимость вызова функции остаётся прежней, но так как функция обрабатывает N значений вместо одного, эти накладные расходы становятся не такими значимыми. Чем больше вектор, тем меньше относительные издержки для одной строки.
Однако выполнение операций с целым столбцом тоже может оказаться нерациональным. Обработка больших векторов мешает эффективно использовать разные уровни кеша процессора. Если таблица очень большая, вектор может вовсе не поместиться в память. Во избежание этих проблем vops
разделяет столбцы на относительно небольшие блоки, или плитки. Размер плитки в настоящее время составляет 64 элемента. Это позволяет разместить все операнды векторных операций в кеше, даже для сложных выражений.
Расширение vops
реализует специальные векторные типы, которые должны использоваться вместо скалярных типов для столбцов таблицы. Доступные типы перечислены в Подразделе F.83.6.1. Для применения vops
вы должны создать векторизованные проекции исходных таблиц с атрибутами, использующими эти типы, как описано в Подразделе F.83.5.1.
Исходную таблицу можно воспринимать как хранилище, оптимизированное для записи: если в ней не будет индексов, Postgres Pro сможет обеспечить очень большую скорость добавления данных, сравнимую со скоростью записи непосредственно на диск. Векторизованную проекцию можно воспринимать как хранилище, оптимизированное для чтения, и это наиболее эффективный вариант для запросов OLAP.
Загрузив все данные в плиточную структуру, вы можете выполнять векторизованные запросы к этим данным. vops
предоставляет набор векторных операторов для работы с векторными типами. Применяя эти операторы, можно составлять запросы для фильтрования и агрегирования данных, подобные обычным SQL-запросам. За подробностями обратитесь к Подразделу F.83.5.2.
Для других типов запросов vops
позволяет работать с векторизованными данными через обёртку postgres_fdw
. Вы можете подключить такие данные в ваш кластер баз данных как стороннюю таблицу и обращаться к ней произвольным образом, применяя обычный SQL. Используя неявные приведения типов и код, внедрённый на стадии после разбора ANALYZE
, расширение vops
либо для фильтрации и агрегирования преобразует запрос с целью применения векторных операций, либо для других типов запросов обрабатывает данные, применяя скалярные операторы. За подробностями обратитесь к Подразделу F.83.5.3.
F.83.5. Использование #
F.83.5.1. Преобразование данных в векторизованный формат #
Чтобы начать использовать векторные операции, вы должны преобразовать данные в векторизованный формат, как описано ниже.
Создайте пустую векторизованную таблицу.
Для использования векторных операций вам необходимо загрузить данные в векторизованную таблицу плиточной структуры, которую можно считать проекцией исходной таблицы. В неё могут отображаться все столбцы исходной таблицы или только некоторые, наиболее часто используемые. Для создания векторизованной таблицы можно применять обычный синтаксис CREATE TABLE, но при этом отображаемые столбцы должны иметь векторные типы.
Например, предположим, что у вас есть следующая таблица из теста производительности TPC-H:
CREATE TABLE lineitem( l_orderkey integer, l_partkey integer, l_suppkey integer, l_linenumber integer, l_quantity double precision, l_extendedprice double precision, l_discount double precision, l_tax double precision, l_returnflag "char", l_linestatus "char", l_shipdate date, l_commitdate date, l_receiptdate date, l_shipinstruct char(25), l_shipmode char(10), l_comment char(44));
Чтобы создать векторизованную проекцию, включающую подмножество исходных столбцов, вы можете выполнить:
CREATE TABLE vops_lineitem( l_shipdate vops_date not null, l_quantity vops_float8 not null, l_extendedprice vops_float8 not null, l_discount vops_float8 not null, l_tax vops_float8 not null, l_returnflag vops_char not null, l_linestatus vops_char not null );
Вы также можете сохранить скалярный тип у некоторых столбцов. В этом случае загружаемые в таблицу данные можно будет сортировать по этим столбцам. Например, давайте создадим ещё одну векторизованную проекцию, в которой поля
l_returnflag
иl_linestatus
останутся скалярными:CREATE TABLE vops_lineitem_projection( l_shipdate vops_date not null, l_quantity vops_float8 not null, l_extendedprice vops_float8 not null, l_discount vops_float8 not null, l_tax vops_float8 not null, l_returnflag "char" not null, l_linestatus "char" not null );
В этой таблице поля
l_returnflag
иl_linestatus
— скалярные, тогда как все остальные имеют векторный тип и могут быть использованы в векторных операциях.Загрузите данные в векторизованную таблицу.
Если данные, подлежащие преобразованию, уже находятся в базе данных, воспользуйтесь функцией populate() для предварительной обработки данных и группирования значений атрибутов различных строк по плиткам. Эта функция загружает данные из исходной таблицы в векторизованную проекцию, применяя вместо скалярных типов векторные, определённые в созданной пустой таблице.
Например:
SELECT populate(destination := 'vops_lineitem'::regclass, source := 'lineitem'::regclass);
Наполнив таблицу, вы можете приступить к выполнению запросов с последовательным сканированием.
Также, загружая данные, можно отсортировать их по одному или нескольким скалярным столбцам:
SELECT populate(destination := 'vops_lineitem_projection'::regclass, source := 'lineitem'::regclass, sort := 'l_returnflag,l_linestatus');
Когда исходная таблица сортируется по скалярным столбцам со множеством повторяющихся значений,
vops
может скомпоновать их и эффективно хранить соответствующие значения атрибутов в плитках, что позволяет уменьшить занимаемое пространство, ускорить запросы и упростить создание индексов по этим скалярным столбцам.Если ваши данные ещё не загружены в базу и вы не хотите иметь две копии одних и тех же данных, вы можете импортировать их непосредственно в векторизованную таблицу из файла CSV с помощью функции import(), пропустив этап создания обычной таблицы. Например:
SELECT import(destination := 'vops_lineitem'::regclass, csv_path := '/mnt/data/lineitem.csv', separator := '|');
Подробное описание и синтаксис функций populate()
и import()
вы можете найти в Подразделе F.83.6.4.
Когда ваши данные представлены в плиточной структуре, вы можете обращаться к ним в запросах, используя векторные операторы, как описано в Подразделе F.83.5.2. Если вы создадите стороннюю векторизованную таблицу, к ним можно будет обращаться и в обычных SQL-запросах. Подробнее об этом рассказывается в Подразделе F.83.5.3.
F.83.5.2. Выполнение запросов с векторизованными данными #
vops
реализует собственные векторные функции и операторы для работы с векторизованными данными. Полный список векторных функций и операторов, а также описание их особенностей приведены в Подразделе F.83.6.
F.83.5.2.1. Агрегирование векторизованных данных #
В запросах OLAP часто агрегируются большие объёмы данных. Для выполнения агрегатных операций с векторными типами vops
реализует следующие агрегатные функции: count
, min
, max
, avg
, var_pop
, var_sampl
, variance
, stddev_pop
, stddev_samp
, stddev
. Кроме того, vops
предоставляет агрегатную функцию wavg
, вычисляющую взвешенную по объёму среднюю цену (VWAP, Volume-Weighted Average Price).
Расширение vops
поддерживает следующие типы агрегатных функций:
агрегатные вычисления для всей таблицы
агрегатные вычисления для подмножеств строк таблицы, определённых предложением
GROUP BY
F.83.5.2.1.1. Вычисление общих агрегатных значений #
Для вычисления общих агрегатных значений вы можете использовать предоставленные векторные агрегатные функции так же, как и в обычных SQL-запросах.
Примеры:
Для вычисления взвешенной по объёму средней цены выполните следующий запрос:
SELECT wavg(l_extendedprice,l_quantity) FROM vops_lineitem;
Для вычисления дохода компании за 1996 год по поставленным с определённой скидкой товарам, выполните:
SELECT sum(l_extendedprice*l_discount) AS revenue FROM vops_lineitem WHERE filter(betwixt(l_shipdate, '1996-01-01', '1997-01-01') & betwixt(l_discount, 0.08, 0.1) & (l_quantity < 24));
F.83.5.2.1.2. Вычисление агрегатных значений с группировкой #
Для поддержки агрегатных вычислений с группировкой vops
предоставляет функции map()
и reduce()
:
Функция
map()
собирает агрегатные состояния по всем группам в хеш-таблицу. Она имеет следующий синтаксис:map(
выражение_группировки
,список_агрегатов
,выражение
{,выражение
})Функция
reduce()
проходит по хеш-таблице, сформированной функциейmap
, и возвращает множество. Потребность в функцииreduce
объясняется тем, что в Postgres Pro результат агрегирования не может быть множеством.
Рассмотрим следующий пример:
SELECT reduce(map(l_returnflag||l_linestatus, 'sum,sum,sum,sum,avg,avg,avg', l_quantity, l_extendedprice, l_extendedprice*(1-l_discount), l_extendedprice*(1-l_discount)*(1+l_tax), l_quantity, l_extendedprice, l_discount)) FROM vops_lineitem WHERE filter(l_shipdate <= '1998-12-01'::date);
Оператор конкатенации ||
осуществляет группировку по двум столбцам. Расширение vops
поддерживает группировку только по целочисленным типам. Функция map
принимает параметры агрегирования в виде массива переменной длины, поэтому все элементы этого массива должны иметь один тип. Например, в одной операции нельзя вычислить агрегатные функции по столбцам vops_float4
и vops_int8
.
Строка агрегатов в функции map
должна содержать список агрегатных функций, которые будут вызываться, через запятую. В нём могут указываться стандартные имена в нижнем регистре: count, sum, agg, min, max
. Подсчёт количества выполняется для конкретного столбца: count(x)
. Указывать count(*)
явно нет необходимости, так как функция reduce
в любом случае возвращает число записей в каждой группе.
Функция reduce
возвращает множество элементов типа vops_aggregate
. Каждый элемент содержит три компонента: значение выражения GROUP BY
, число записей в группе и массив чисел с агрегированными значениями. Результаты всех агрегатных функций, включая count
и min/max
, возвращаются в виде чисел с плавающей точкой.
Вы также можете секционировать векторизованную таблицу по полям GROUP BY
для вычисления группируемых агрегатов. В этом случае группирующие ключи хранятся как обычные скалярные значения, тогда как другие поля хранятся в плиточной структуре. Тогда исполнитель Postgres Pro будет применять агрегатные операции vops для каждой группы:
SELECT l_returnflag, l_linestatus, sum(l_quantity) as sum_qty, sum(l_extendedprice) AS sum_base_price, sum(l_extendedprice*(1-l_discount)) AS sum_disc_price, sum(l_extendedprice*(1-l_discount)*(1+l_tax)) AS sum_charge, avg(l_quantity) AS avg_qty, avg(l_extendedprice) AS avg_price, avg(l_discount) AS avg_disc, count(*) AS count_order FROM vops_lineitem_projection WHERE filter(l_shipdate <= '1998-12-01'::date) GROUP BY l_returnflag, l_linestatus ORDER BY l_returnflag, l_linestatus;
В данном примере поля l_returnflag
и l_linestatus
таблицы vops_lineitem_projection
имеют тип char
, тогда как все остальные задействованные поля — векторные. Показанный выше запрос выполняется ещё быстрее, чем запрос с функциями map()
и reduce()
. Основной недостаток этого подхода в том, что вам придётся создавать отдельную проекцию для каждой комбинации ключей GROUP BY
, используемой в запросах.
F.83.5.2.2. Использование оконных векторных функций #
Расширение vops
обеспечивает ограниченную поддержку оконных функций Postgres Pro. Оно реализует функции mcount
, msum
, mmin
, mmax
, mavg
и lag
, соответствующие стандартным оконным функциям count
, sum
, min
, max
, avg
и lag
.
Примите к сведению следующие важные ограничения:
Фильтрацию, группировку и сортировку можно выполнять только по скалярным атрибутам.
Векторные оконные функции поддерживают только рамки окон, определённые предложением
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
.
Примеры:
SELECT vops_unnest(t.*) FROM (SELECT mcount(*) over w,mcount(x) over w,msum(x) over w,mavg(x) over w,mmin(x) over w,mmax(x) over w,x - lag(x) over w FROM v window w AS (rows between unbounded preceding and current row)) t;
F.83.5.2.3. Преобразование результатов в горизонтальный формат #
Запрос из проекции vops
возвращает набор плиток. Функция вывода для типа плитка может отображать содержимое плитки. Однако если вы хотите преобразовать результат в горизонтальный формат, в котором каждый кортеж представляет одну запись, используйте функцию vops-unnest. Например:
SELECT vops_unnest(l.*) FROM vops_lineitem l WHERE filter(l_shipdate >= '1998-12-01'::date);
F.83.5.3. Использование обычного SQL для векторных операций #
Векторные операторы могут эффективно работать только для выполнения фильтрации и агрегирования. Для других задач вы должны выполнять обычные SQL-запросы. Расширение vops
предлагает для этого следующие варианты:
Использовать исходную таблицу, если она есть.
Настроить обёртку сторонних данных (FDW), используя
postgres_fdw
, для представления векторизованной таблицы в Postgres Pro в виде обычной таблицы со столбцами скалярных типов.
Если вы используете postgres_fdw
с расширением vops
, векторизованные данные на самом деле находятся не на другом сервере: они хранятся в той же базе данных, но в альтернативном векторизованном формате. Подключение через FDW позволяет скрыть специфику этого формата и открывает следующие возможности для работы с векторизованными данными:
Извлечение данных из векторизованной таблицы и представление их в горизонтальном формате, чтобы их могли обработать верхние узлы в плане выполнения запроса.
Сведение запросов фильтрации и агрегирования к векторным операциям.
Сбор статистики по нижележащей таблице путём выполнения
ANALYZE
для сторонней таблицы. Собранная статистика может использоваться оптимизатором запросов, так что план выполнения практически не будет отличаться от плана для обычных таблиц.
Таким образом, скрывая векторизованную проекцию за FDW, вы можете использовать стандартный SQL без специфических функций vops
для выполнения последовательного сканирования и агрегирующих запросов, как если бы они обращались явно к таблице vops
, и в то же время выполнять другие запросы к этим данным, включая операции JOIN
, общие табличные выражения (CTE) и т. д. Запросы, которые могут быть эффективно обработаны векторными операциями, оптимизатор запросов Postgres Pro передаст обёртке vops
, и они будут выполнены с применением векторных операций. С другими запросами данные выбираются из векторизованных таблицы как стандартные кортежи. Если определяется, что нужно применить векторные операции, vops
задействует внедряемый на стадии после ANALYZE
код, который заменяет скалярные булевы операции векторными:
Исходное выражение | Преобразованное выражение |
---|---|
NOT filter(o1) | filter(vops_bool_not(o1)) |
filter(o1) AND filter(o2) | filter(vops_bool_and(o1, o2)) |
filter(o1) OR filter(o2) | filter(vops_bool_or(o1, o2)) |
Есть только одно различие между стандартным SQL и его векторизованным расширением — вы в любом случае должны применять явное приведение типа для строковых констант. Например, выражение l_shipdate <= '1998-12-01'
не будет работать со столбцом l_shipdate
векторного типа. В Postgres Pro Enterprise есть две перегруженные версии оператора <= для работы с соответствующим скалярным или векторным типом:
vops_date
<=vops_date
vops_date
<=date
Следующий пример иллюстрирует создание векторизованной таблицы через postgres_fdw
и несколько запросов с ней:
CREATE FOREIGN TABLE lineitem_fdw ( l_suppkey int4 not null, l_orderkey int4 not null, l_partkey int4 not null, l_shipdate date not null, l_quantity float8 not null, l_extendedprice float8 not null, l_discount float8 not null, l_tax float8 not null, l_returnflag "char" not null, l_linestatus "char" not null ) SERVER vops_server OPTIONS (table_name 'vops_lineitem'); EXPLAIN SELECT sum(l_extendedprice*l_discount) AS revenue FROM lineitem_fdw WHERE l_shipdate BETWEEN '1996-01-01' AND '1997-01-01' AND l_discount BETWEEN 0.08 AND 0.1 AND l_quantity < 24; QUERY PLAN --------------------------------------------------------- Foreign Scan (cost=1903.26..1664020.23 rows=1 width=4) (1 row) -- Фильтрация была передана FDW EXPLAIN SELECT n_name, count(*), sum(l_extendedprice * (1-l_discount)) AS revenue FROM customer_fdw JOIN orders_fdw ON c_custkey = o_custkey JOIN lineitem_fdw ON l_orderkey = o_orderkey JOIN supplier_fdw ON l_suppkey = s_suppkey JOIN nation ON c_nationkey = n_nationkey JOIN region ON n_regionkey = r_regionkey WHERE c_nationkey = s_nationkey AND r_name = 'ASIA' AND o_orderdate >= '1996-01-01' AND o_orderdate < '1997-01-01' GROUP BY n_name ORDER BY revenue desc; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------- Sort (cost=2337312.28..2337312.78 rows=200 width=48) Sort Key: (sum((lineitem_fdw.l_extendedprice * ('1'::double precision - lineitem_fdw.l_discount)))) DESC -> GroupAggregate (cost=2336881.54..2337304.64 rows=200 width=48) Group Key: nation.n_name -> Sort (cost=2336881.54..2336951.73 rows=28073 width=40) Sort Key: nation.n_name -> Hash Join (cost=396050.65..2334807.39 rows=28073 width=40) Hash Cond: ((orders_fdw.o_custkey = customer_fdw.c_custkey) AND (nation.n_nationkey = customer_fdw.c_nationkey)) -> Hash Join (cost=335084.53..2247223.46 rows=701672 width=52) Hash Cond: (lineitem_fdw.l_orderkey = orders_fdw.o_orderkey) -> Hash Join (cost=2887.07..1786058.18 rows=4607421 width=52) Hash Cond: (lineitem_fdw.l_suppkey = supplier_fdw.s_suppkey) -> Foreign Scan on lineitem_fdw (cost=0.00..1512151.52 rows=59986176 width=16) -> Hash (cost=2790.80..2790.80 rows=7702 width=44) -> Hash Join (cost=40.97..2790.80 rows=7702 width=44) Hash Cond: (supplier_fdw.s_nationkey = nation.n_nationkey) -> Foreign Scan on supplier_fdw (cost=0.00..2174.64 rows=100032 width=8) -> Hash (cost=40.79..40.79 rows=15 width=36) -> Hash Join (cost=20.05..40.79 rows=15 width=36) Hash Cond: (nation.n_regionkey = region.r_regionkey) -> Seq Scan on nation (cost=0.00..17.70 rows=770 width=40) -> Hash (cost=20.00..20.00 rows=4 width=4) -> Seq Scan on region (cost=0.00..20.00 rows=4 width=4) Filter: ((r_name)::text = 'ASIA'::text) -> Hash (cost=294718.76..294718.76 rows=2284376 width=8) -> Foreign Scan on orders_fdw (cost=0.00..294718.76 rows=2284376 width=8) -> Hash (cost=32605.64..32605.64 rows=1500032 width=8) -> Foreign Scan on customer_fdw (cost=0.00..32605.64 rows=1500032 width=8) -- фильтрация по диапазону значений в orders передана FDW
F.83.5.4. Построение индексов для векторизованных таблиц #
Аналитические запросы обычно выполняются с данными, для которых индексы не определены. В этом случае векторные операции с плиточной структурой наиболее эффективны. Однако и с векторизованными данными можно использовать индексы.
Так как в каждой плитке представлены несколько значений, индекс может использоваться только для предварительной, не точной фильтрации данных. Расширение vops
предоставляет следующие пары функций для получения граничных значений, хранящихся в плитках:
Функции
first()
иlast()
предназначены для отсортированного набора данных. В этом случае первым и последним значением в плитке являются соответственно наименьшее и наибольшее значение в этой плитке.Функции
high()
иlow()
предназначены для несортированных данных. Выполнение этих функций обходится дороже, так как они должны просмотреть все значения в плитке.
Используя возвращённые значения, вы можете построить функциональные индексы для векторизованных данных. Индекс BRIN представляется наиболее подходящим:
CREATE INDEX low_boundary ON trades USING brin(first(day)); -- таблица trades упорядочивается по дням CREATE INDEX high_boundary ON trades USING brin(last(day)); -- таблица table упорядочивается по дням
Теперь вы можете использовать созданные индексы в запросах. Однако вам придётся перепроверять точное условие, так как подобный индекс даёт лишь приблизительный результат:
SELECT sum(price) FROM trades WHERE first(day) >= '2015-01-01' AND last(day) <= '2016-01-01' AND filter(betwixt(day, '2015-01-01', '2016-01-01'));
F.83.5.5. Использование автоматических проекций #
Расширение vops
может автоматизировать создание и поддержание векторизованных проекций данных. Это позволяет не использовать postgres_fdw: vops
может перенаправлять обычные SQL-запросы к таблицам-проекциям автоматически, если выполняются следующие условия:
В запросе отсутствуют соединения.
В запросе выполняется агрегирование векторных столбцов.
Все другие выражения в списке целевых столбцов, а также в предложениях
ORDER BY
иGROUP BY
, обращаются только к скалярным столбцам.
Чтобы включить автоматическое перенаправление запросов к векторизованной проекции данных, выполните следующее:
Создайте векторизованную проекцию таблицы с помощью функции create_projection().
Например, чтобы создать проекцию показанной ранее таблицы
lineitem
, подобнуюvops_lineitem
, выполните:SELECT create_projection('auto_vops_lineitem','lineitem', array['l_shipdate','l_quantity','l_extendedprice','l_discount','l_tax'], array['l_returnflag','l_linestatus']);
В этом примере первые два аргумента задают таблицу-проекцию и исходную таблицу, соответственно; первый массив определяет векторные столбцы, которые должны включаться в проекцию, а второй — скалярные столбцы, по значениям которых векторизованные данные будут группироваться в плитки.
Дополнительно вы можете передать необязательный параметр
order_by
для ускорения последующих операций обновления проекций. Значения столбцов, перечисленных вorder_by
, должны быть уникальными. Например, чтобы записи таблицы сортировались по столбцуl_shipdate
, вы можете создать таблицу-проекцию следующим образом:SELECT create_projection('auto_vops_lineitem_ordered','lineitem', array['l_shipdate','l_quantity','l_extendedprice','l_discount','l_tax'], array['l_returnflag','l_linestatus'], 'l_shipdate');
Установите для параметра vops.auto_substitute_projections значение
on
(вкл.).Теперь запросы, которые
vops
может перенаправить к таблице-проекцииauto_vops_lineitem
, будут выполняться с векторизованными данными.
Проекции не обновляются автоматически, поэтому после изменения данных в исходной таблице неявно перенаправленные запросы могут выдавать неточные данные. Чтобы избежать этого, необходимо обновлять проекции, прежде чем выполнять запросы к векторизованным данным. Для этого vops
предоставляет функции
, где проекция
_refresh()проекция
— имя проекции, которую нужно обновить. Например, чтобы обновить проекцию auto_vops_lineitem
, выполните:
SELECT auto_vops_lineitem_refresh();
Эта функция вызывает метод populate()
, обновляющий все поля, представленные в проекции. Если при создании проекции был указан необязательный параметр order_by
, из исходной таблицы будут импортироваться только новые данные, то есть только те строки, в которых значение столбца order_by
больше максимального значения этого столбца в проекции. В противном случае будет перезаписана вся таблица. Метод populate()
также учитывает группировку столбцов для распределения импортированных данных по плиткам.
Если при создании проекции задаётся атрибут order_by
, vops
также создаёт следующие индексы:
Два функциональных индекса BRIN по функциям
first()
иlast()
с данным атрибутом. Например, если исходный запрос содержит предикаты вида(l_shipdate between '01-01-2017' and '01-01-2018')
,vops
, перенаправляя запрос, добавляет конъюнкцию(first(l_shipdate) >= '01-01-2017' and last(l_shipdate) >= '01-01-2018')
, чтобы оптимизатор Postgres Pro мог быстро найти нужные страницы, используя индексы BRIN.Индекс BRIN для скалярных столбцов, выбранных для группирования данных. Такой индекс позволяет эффективно выбирать группы и выполнять соединения по индексу.
F.83.6. Справка #
F.83.6.1. Векторные типы #
Расширение vops
поддерживает все основные числовые типы Postgres Pro: 1-, 2-, 4-, 8-байтовые целые, а также 4- и 8-байтовые числа с плавающей точкой. Типы date
и timestamp
используют ту же реализацию, что и int4
, и int8
, соответственно. В следующей таблице перечислены имеющиеся векторные типы, в сопоставлении с соответствующими типами SQL и C.
Тип SQL | Тип C | Векторный тип vops |
---|---|---|
bool | bool | vops_bool |
"char" | char | vops_char |
date | DateADT | vops_date |
int2 | int16 | vops_int2 |
int4 | int32 | vops_int4 |
int8 | int64 | vops_int8 |
float4 | float4 | vops_float4 |
float8 | float8 | vops_float8 |
timestamp | Timestamp | vops_timestamp |
char(N ), varchar(N ) | text | vops_text(N ) |
Примечание
По соображениям производительности использовать тип vops_text
для односимвольных строк не рекомендуется. Если у вас идентификаторами являются строки, их можно поместить в словарь и использовать вместо исходных строк числовые идентификаторы.
F.83.6.2. Векторные операторы #
Расширение vops
поддерживает набор операторов для векторных типов.
F.83.6.2.1. Математические операторы #
Для математических операций vops
перегружает обычные операторы SQL, чтобы они принимали операнды и векторного, и скалярного типа.
Оператор | Описание |
---|---|
+ | Сложение |
- | Бинарное вычитание или унарная смена знака |
* | Умножение |
/ | Деление |
|| | Конкатенация. Этот оператор поддерживается только для типов char , int2 и int4 . Он возвращает целочисленное значение удвоенного размера: (char || char) -> int2 (int2 || int2) -> int4 (int4 || int4) -> int8 |
F.83.6.2.2. Операторы сравнения #
Для операций сравнения vops
перегружает обычные операторы SQL, чтобы они принимали операнды и векторного, и скалярного типа.
Оператор | Описание |
---|---|
= | Равно |
<> | Не равно |
< | Меньше |
<= | Меньше или равно |
> | Больше |
>= | Больше или равно |
F.83.6.2.3. Булевы операторы #
Булевы операторы AND
, OR
и NOT
нельзя перегрузить, поэтому vops
предоставляет собственную реализацию булевых операторов для векторных типов:
Оператор | Описание |
---|---|
& | Логическое И (AND) |
| | Логическое ИЛИ (OR) |
! | Логическое НЕ (NOT) |
Векторные булевы операторы имеют следующие особенности:
Приоритеты векторных булевых операторов отличаются от
AND
,OR
иNOT
, поэтому их операнды нужно заключать в скобки. Например:(x=1) | (x=2)
Результат векторных булевых операций имеет тип
vops_bool
. Так как Postgres Pro требует, чтобы выражения предикатов имели обычный булев тип, вы должны привести результат к типуbool
, используя функциюfilter()
. Эта функция, принимающая векторное булево выражение, вычисляет значениеfilter_mask
, которое определяет результат данной булевой операции для каждого элемента вектора. Если все биты маски равны нулю (предикат не выполняется для всех элементов вектора), то функцияfilter()
возвращаетfalse
и Postgres Pro пропускает эту запись. В противном случае возвращаетсяtrue
, и векторные операторы будут проверять значениеfilter_mask
для отдельных элементов вектора в последующих операциях.Применяя векторные булевы операторы, вы должны явно приводить строковые константы к требуемому типу данных.
F.83.6.3. Функции сравнения #
Функция | Описание |
---|---|
bitwixt(x,low,high) | То же, что и предикат x BETWEEN low AND high . |
is_null(x) | То же, что и предикат выражение IS NULL . |
is_not_null(x) | То же, что и предикат выражение IS NOT NULL . |
ifnull(x,subst) | То же, что и функция COALESCE . |
F.83.6.4. Функции преобразования #
-
create_projection(
#projection_name
text
,source_table
regclass
,vector_columns
text[]
,scalar_columns
text[]
DEFAULT null,order_by
text
DEFAULT null) Создаёт таблицу-проекцию с заданным именем и атрибутами, а также вносит в таблицу
vops_projections
информацию об этой проекции, чтобы оптимизатор мог автоматически перенаправлять запросы от исходной таблицы к проекции. При этом также создаётся функция pname_refresh(), позволяющая обновлять проекцию.Аргументы:
projection_name
— имя таблицы-проекции.source_table
— исходная таблица Postgres Pro, из которой будет создаваться векторизованная проекция.vector_columns
— массив имён столбцов, содержимое которых будет сохранено в плиткахvops
.scalar_columns
— массив имён столбцов, тип которых останется скалярным. По значениям этих столбцов строки таблицы будут группироваться в плиткиvops
. По таким столбцамvops
автоматически строит индекс BRIN.order_by
— необязательное указание векторного столбца, по которому могут сортироваться данные в таблице-проекции. Сортировка позволяет инкрементальным образом обновлять в проекции данные, изменённые в исходной странице, с использованием функции pname_refresh(). В этом столбце должны содержаться уникальные значения времени, например, даты продаж. Расширениеvops
автоматически строит индексы BRIN по функциямfirst()
иlast()
со значениями этого столбца.
Примеры:
SELECT create_projection('vops_lineitem','lineitem',array['l_shipdate','l_quantity','l_extendedprice','l_discount','l_tax'],array['l_returnflag','l_linestatus']);
-
#проекция
_refresh() Обновляет таблицу-проекцию с именем
проекция
.Примеры:
Обновление проекции
vops_lineitem
:SELECT vops_lineitem_refresh();
-
drop_projection(
#projection_name
text
) Удаляет указанную проекцию и соответствующую функцию обновления, а также удаляет информацию об этой проекции из таблицы
vops_projections
.Примеры:
Удаление проекции
vops_lineitem
:SELECT drop_projection(vops_lineitem);
-
populate(
#destination
regclass
,source
regclass
,predicate
cstring
DEFAULT null,sort
cstring
DEFAULT null) Копирует данные из существующей таблицы в её векторизованную проекцию и возвращает число импортированных строк (
bigint
).Аргументы:
destination
— целевая таблица, в которую будут копироваться данные.Тип:
regclass
source
— исходная таблица, из которой будут копироваться данные.Тип:
regclass
predicate
— ограничивает множество импортируемых данных (необязательный аргумент). Используя этот аргумент, вы можете импортировать только самые последние записи.Тип:
cstring
sort
— определяет порядок сортировки данных, в котором они будут загружаться (необязательный аргумент).Тип:
cstring
Примеры:
SELECT populate(destination := 'vops_lineitem'::regclass, source := 'lineitem'::regclass);
SELECT populate(destination := 'vops_lineitem_projection'::regclass, source := 'lineitem_projection'::regclass, sort := 'l_returnflag,l_linestatus');
-
import(
#destination
regclass
,csv_path
cstring
,separator
cstring
DEFAULT ','skip
integer
DEFAULT 0) Загружает данные в таблицу
vops
непосредственно из файла CSV, пропуская этап создания обычной таблицы, и возвращает число импортированных строк (bigint
). Используйте эту функцию, чтобы не хранить две копии одних и тех же данных.Аргументы:
destination
— целевая таблица, в которую будут копироваться данные.Тип:
regclass
csv_path
— путь к файлу CSV, из которого будут загружаться данные.Тип:
cstring
separator
— задаёт разделитель полей, используемый в файле CSV (необязательный аргумент). По умолчанию разделителем полей считается запятая.Тип:
cstring
skip
— задаёт количество строк в заголовке CSV, которые не нужно импортировать (необязательный аргумент).По умолчанию: 0
Тип:
cstring
Примеры:
SELECT import(destination := 'vops_lineitem'::regclass, csv_path := '/mnt/data/lineitem.csv', separator := '|');
-
#vops_unnest
(anyelement
) Разбивает записи с типами
vops
на записи со скалярными типами.
F.83.7. Авторы #
Postgres Professional, Москва, Россия