F.82. vops — поддержка векторных операций #
Важно
Расширение vops признано устаревшим. Использовать его не рекомендуется.
Модуль vops — экспериментальное расширение Postgres Pro Enterprise, поддерживающее векторные операции, что позволяет ускорить запросы OLAP с фильтрацией и агрегированием более чем в 10 раз. Такое увеличение производительности достигается благодаря реализации вертикальной модели данных, в которой данные группируются по значениям столбцов и хранятся в виде «плиток». Этот формат хранения в некоторых аналитических базах данных также называется «паркетом». Уменьшая издержки хранения неупакованных кортежей, вертикальная модель данных позволяет ускорить выполнение запросов без радикальных изменений в планировщике и исполнителе Postgres Pro.
vops предоставляет собственный набор векторных функций, которые можно использовать в предикатах и агрегатных выражениях. Чтобы не изучать новый синтаксис, вы можете воспользоваться postgres_fdw в качестве уровня абстракции и выполнять обычные SQL-запросы к векторизованным данным. Для других типов запросов (не фильтрующих и не агрегирующих) vops может также посредством postgres_fdw представлять векторизованные данные в виде обычной таблицы со скалярными типами столбцов и обрабатывать их как обычные кортежи. Для упрощения использования vops также предоставляет автоматический способ формирования векторизованного представления данных и обращения к нему, о чём подробнее рассказывается в Подразделе F.82.5.5.
F.82.1. Ограничения #
Таблицы в виде плиток допускают только операции
INSERTиAPPEND. Загруженные в них векторизованные данные изменить нельзя.Векторные операции возможны только для фильтрации и агрегирования. Все остальные типы запросов поддерживаются через
postgres_fdw.Агрегатные выражения должны быть одинакового типа.
Операции
JOINдля векторных типов не поддерживаются. Для выполнения таких запросов необходимо преобразовать векторизованные данных в обычные кортежи, используяpostgres_fdw.Так как
postgres_fdwне поддерживает выполнение параллельных запросов, такие запросы вы сможете выполнять только применяя непосредственно векторные операции.
F.82.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.82.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.82.4.
F.82.4. Архитектура #
Расширение vops реализует вертикальную модель данных для Postgres Pro. В этой модели данные хранятся в вертикальных столбцах, или векторах, формируемых из значений соответствующих атрибутов таблицы.
Вертикальная модель данных, которую реализует vops, имеет следующие преимущества:
Уменьшение размера считываемых данных: считываются только те столбцы, которые используются в запросе.
Более эффективное сжатие: когда все значения одного атрибута хранятся вместе, их можно сжимать лучше и быстрее. Например, вы можете применить дельта-кодирование.
Минимизация издержек интерпретирования: набор значений может быть обработан одной операцией, выполнять отдельные операции для каждого значения не требуется.
Использование векторных инструкций процессора (SIMD) при обработке данных.
Традиционный исполнитель запросов Postgres Pro имеет дело с одной строкой данных в каждый момент времени. Например, чтобы вычислить выражение (x+y), он сначала извлекает значение x, затем извлекает значение y, выполняет сложение и, наконец, выдаёт значение результата верхнему узлу. Векторизованный исполнитель, напротив, может обработать множество значений в одной операции. В этом случае x и y представляют векторы значений, а не отдельные скаляры. Возвращаемым результатом также будет вектор. В векторной модели выполнения издержки интерпретации и вызовов функций делятся на размер вектора. Стоимость вызова функции остаётся прежней, но так как функция обрабатывает N значений вместо одного, эти накладные расходы становятся не такими значимыми. Чем больше вектор, тем меньше относительные издержки для одной строки.
Однако выполнение операций с целым столбцом тоже может оказаться нерациональным. Обработка больших векторов мешает эффективно использовать разные уровни кеша процессора. Если таблица очень большая, вектор может вовсе не поместиться в память. Во избежание этих проблем vops разделяет столбцы на относительно небольшие блоки, или плитки. Размер плитки в настоящее время составляет 64 элемента. Это позволяет разместить все операнды векторных операций в кеше, даже для сложных выражений.
Расширение vops реализует специальные векторные типы, которые должны использоваться вместо скалярных типов для столбцов таблицы. Доступные типы перечислены в Подразделе F.82.6.1. Для применения vops вы должны создать векторизованные проекции исходных таблиц с атрибутами, использующими эти типы, как описано в Подразделе F.82.5.1.
Исходную таблицу можно воспринимать как хранилище, оптимизированное для записи: если в ней не будет индексов, Postgres Pro сможет обеспечить очень большую скорость добавления данных, сравнимую со скоростью записи непосредственно на диск. Векторизованную проекцию можно воспринимать как хранилище, оптимизированное для чтения, и это наиболее эффективный вариант для запросов OLAP.
Загрузив все данные в плиточную структуру, вы можете выполнять векторизованные запросы к этим данным. vops предоставляет набор векторных операторов для работы с векторными типами. Применяя эти операторы, можно составлять запросы для фильтрования и агрегирования данных, подобные обычным SQL-запросам. За подробностями обратитесь к Подразделу F.82.5.2.
Для других типов запросов vops позволяет работать с векторизованными данными через обёртку postgres_fdw. Вы можете подключить такие данные в ваш кластер баз данных как стороннюю таблицу и обращаться к ней произвольным образом, применяя обычный SQL. Используя неявные приведения типов и код, внедрённый на стадии после разбора ANALYZE, расширение vops либо для фильтрации и агрегирования преобразует запрос с целью применения векторных операций, либо для других типов запросов обрабатывает данные, применяя скалярные операторы. За подробностями обратитесь к Подразделу F.82.5.3.
F.82.5. Использование #
F.82.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.82.6.4.
Когда ваши данные представлены в плиточной структуре, вы можете обращаться к ним в запросах, используя векторные операторы, как описано в Подразделе F.82.5.2. Если вы создадите стороннюю векторизованную таблицу, к ним можно будет обращаться и в обычных SQL-запросах. Подробнее об этом рассказывается в Подразделе F.82.5.3.
F.82.5.2. Выполнение запросов с векторизованными данными #
vops реализует собственные векторные функции и операторы для работы с векторизованными данными. Полный список векторных функций и операторов, а также описание их особенностей приведены в Подразделе F.82.6.
F.82.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.82.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.82.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.82.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.82.5.2.3. Преобразование результатов в горизонтальный формат #
Запрос из проекции vops возвращает набор плиток. Функция вывода для типа плитка может отображать содержимое плитки. Однако если вы хотите преобразовать результат в горизонтальный формат, в котором каждый кортеж представляет одну запись, используйте функцию vops-unnest. Например:
SELECT vops_unnest(l.*) FROM vops_lineitem l WHERE filter(l_shipdate >= '1998-12-01'::date);
F.82.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_datevops_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 передана FDWF.82.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.82.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.82.6. Справка #
F.82.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.82.6.2. Векторные операторы #
Расширение vops поддерживает набор операторов для векторных типов.
F.82.6.2.1. Математические операторы #
Для математических операций vops перегружает обычные операторы SQL, чтобы они принимали операнды и векторного, и скалярного типа.
| Оператор | Описание |
|---|---|
+ | Сложение |
- | Бинарное вычитание или унарная смена знака |
* | Умножение |
/ | Деление |
|| | Конкатенация. Этот оператор поддерживается только для типов char, int2 и int4. Он возвращает целочисленное значение удвоенного размера: (char || char) -> int2 (int2 || int2) -> int4 (int4 || int4) -> int8 |
F.82.6.2.2. Операторы сравнения #
Для операций сравнения vops перегружает обычные операторы SQL, чтобы они принимали операнды и векторного, и скалярного типа.
| Оператор | Описание |
|---|---|
= | Равно |
<> | Не равно |
< | Меньше |
<= | Меньше или равно |
> | Больше |
>= | Больше или равно |
F.82.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.82.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.82.6.4. Функции преобразования #
-
create_projection(#projection_nametext,source_tableregclass,vector_columnstext[],scalar_columnstext[]DEFAULT null,order_bytextDEFAULT 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_nametext) Удаляет указанную проекцию и соответствующую функцию обновления, а также удаляет информацию об этой проекции из таблицы
vops_projections.Примеры:
Удаление проекции
vops_lineitem:SELECT drop_projection(vops_lineitem);
-
populate(#destinationregclass,sourceregclass,predicatecstringDEFAULT null,sortcstringDEFAULT null) Копирует данные из существующей таблицы в её векторизованную проекцию и возвращает число импортированных строк (
bigint).Аргументы:
destination— целевая таблица, в которую будут копироваться данные.Тип:
regclasssource— исходная таблица, из которой будут копироваться данные.Тип:
regclasspredicate— ограничивает множество импортируемых данных (необязательный аргумент). Используя этот аргумент, вы можете импортировать только самые последние записи.Тип:
cstringsort— определяет порядок сортировки данных, в котором они будут загружаться (необязательный аргумент).Тип:
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(#destinationregclass,csv_pathcstring,separatorcstringDEFAULT ','skipintegerDEFAULT 0) Загружает данные в таблицу
vopsнепосредственно из файла CSV, пропуская этап создания обычной таблицы, и возвращает число импортированных строк (bigint). Используйте эту функцию, чтобы не хранить две копии одних и тех же данных.Аргументы:
destination— целевая таблица, в которую будут копироваться данные.Тип:
regclasscsv_path— путь к файлу CSV, из которого будут загружаться данные.Тип:
cstringseparator— задаёт разделитель полей, используемый в файле CSV (необязательный аргумент). По умолчанию разделителем полей считается запятая.Тип:
cstringskip— задаёт количество строк в заголовке CSV, которые не нужно импортировать (необязательный аргумент).По умолчанию: 0
Тип:
cstring
Примеры:
SELECT import(destination := 'vops_lineitem'::regclass, csv_path := '/mnt/data/lineitem.csv', separator := '|');
-
#vops_unnest(anyelement) Разбивает записи с типами
vopsна записи со скалярными типами.
F.82.7. Авторы #
Postgres Professional, Москва, Россия