Перейти к содержанию

Опция FORWARD

Опция FORWARD ограничивает количество сетевых пересылок при исполнении DQL- и DML-запросов. Если для выполнения запроса требуется больше пересылок, чем разрешено, Picodata возвращает ошибку на этапе исполнения — сам запрос при этом не выполняется.

Опция позволяет гарантировать локальность исполнения: например, убедиться, что запрос выполняется на узле-координаторе. Это бывает полезно для «умных» драйверов, которые отправляют запросы напрямую на узлы, хранящие нужные бакеты, чтобы избежать накладных расходов на пересылку по сети.

Синтаксис

Значение опции указывается в части OPTION SQL-запроса:

SELECT * FROM warehouse WHERE id = 1 OPTION (FORWARD = OFF);

Помимо указания в тексте запроса, значение можно задать один раз в connection string — тогда оно будет применяться ко всем запросам в рамках соединения:

postgres://postgres:T0psecret@127.0.0.1:4327?options=forward%3Doff

При одновременном указании опции в connection string и в OPTION запроса приоритет отдается значению из OPTION.

Значения опции

Опция принимает три значения, задающих нарастающую степень локальности:

  • ON (по умолчанию) — бакеты, на которых исполняется запрос, могут принадлежать разным узлам; запрос рассылается лидерам всех затронутых репликасетов, а их результаты собираются на узле-координаторе. Пересылки разрешены без ограничений.
  • RO_TO_RW — все бакеты запроса должны принадлежать одному узлу, но при исполнении допустима одна пересылка с координатора запроса на лидера соответствующего бакетам репликасета. Если бакеты распределены по нескольким узлам — возвращается ошибка.
  • OFF — все бакеты должны принадлежать одному узлу, а клиент обязан сам отправить запрос на лидера соответствующего бакету репликасета. Иначе возвращается ошибка. Это режим максимальной локальности.

Иными словами, степень локальности возрастает от ON к OFF:

  1. ON — локальности нет ни в каком смысле;
  2. RO_TO_RW — все бакеты запроса принадлежат одному узлу;
  3. OFF — истинная локальность: клиент сам отправил запрос на узел, хранящий бакеты.

Примечание

Проверка опции выполняется динамически, во время исполнения запроса. Поэтому успешно выполненный однажды запрос не гарантирует успеха при повторном запуске — состав бакетов может измениться из-за DML-операций над таблицами. Подробнее — в разделе Особенности динамической проверки.

Взаимодействие с READ_PREFERENCE

Для DQL-запросов опция FORWARD взаимодействует с опцией READ_PREFERENCE, которая задаёт стратегию чтения данных. Опция FORWARD имеет приоритет над READ_PREFERENCE. С подробным описанием взаимодействия можно ознакомиться в таблице ниже:

READ_PREFERENCE \ FORWARD on ro_to_rw off
any
leader
replica

Комбинации FORWARD = OFF с READ_PREFERENCE = ANY или REPLICA недопустимы, так как в этих режимах запрос может быть исполнен на реплике, что противоречит требованию FORWARD = OFF об отправке запроса на лидера. При указании такой комбинации возвращается ошибка:

SELECT * FROM warehouse WHERE id = 1
OPTION (FORWARD = OFF, READ_PREFERENCE = REPLICA);
ERROR:  sbroad: invalid OptionSpec: "forward = off" is not compatible with "read_preference = replica"

Исполнение запросов с пересылками (motion)

Сложные запросы могут исполняться в несколько стадий, каждая из которых сопровождается перераспределением данных между узлами — узлом motion в логическом плане запроса (см. фасет LOGICAL). Для каждого motion Picodata сначала вычисляет множество затронутых бакетов, а затем проверяет, удовлетворяет ли оно указанному значению FORWARD. Если условие нарушено хотя бы для одного motion, клиент получает ошибку, а исполнение запроса прекращается.

Несмотря на динамический характер проверки, опция не может привести к ситуации, когда часть DML-запроса выполнилась, а часть нет. Т.е. невозможна ситуация "разорванного" DML.

Особенности динамической проверки

Проверка выполнимости опции FORWARD происходит во время исполнения запроса. Такой подход приводит к отсутствию детерминизма, т.е. если запрос в данный момент выполнился с указанной опцией FORWARD = OFF, то нет никакой гарантии, что он будет успешно выполнен снова после DML-запросов.

Рассмотрим описанное выше на примере. Пусть есть таблицы

CREATE TABLE a (id INT PRIMARY KEY, val INT);
CREATE TABLE b (id INT PRIMARY KEY, val INT);
CREATE TABLE c (id INT PRIMARY KEY, val INT) DISTRIBUTED BY (val);

Данные, лежащие в таблицах b и c:

SELECT bucket_id, * FROM b;
 bucket_id | id | val
-----------+----+-----
      1934 |  1 |   1
      1410 |  2 |   2
SELECT bucket_id, * FROM c;
 bucket_id | id | val
-----------+----+-----
      1410 |  2 |   2

Рассматриваемый запрос:

INSERT INTO a
    SELECT * FROM b
    WHERE b.id IN (SELECT id FROM c WHERE val = 2 ORDER BY id LIMIT 10);

Конфигурация кластера, используемая в примерах:

  • replicaset1 : buckets [1-1500]
  • replicaset2 : buckets [1501-3000]

Репликасет replicaset1 содержит бакеты от 1 до 1500, а репликасет replicaset2 содержит бакеты от 1501 до 3000. Будем считать, что для запроса ниже узлом-координатором будет мастер репликасета replicaset1.

Выполним запрос с опцией forward = off:

-- исполняем на узле, содержащем бакет
INSERT INTO a
    SELECT * FROM b
    WHERE b.id IN (SELECT id FROM c WHERE val = 2 ORDER BY 1 LIMIT 10)
OPTION (FORWARD = OFF);

Он успешно выполнится, т.к. результатом подзапроса будет значение 2, соответствующее значению бакета 1410, который принадлежит текущему узлу.

Вставим еще одну строку в таблицу c:

INSERT INTO c VALUES (1, 2);

Теперь выполнение запроса с опцией forward = off приведет к ошибке

ERROR:  sbroad: invalid option: cannot satisfy "forward = off":
buckets span multiple nodes and are not present on the current node, try using "forward = on" instead

т.к. результатом подзапроса будут значения 1 и 2, соответствующие бакетaм 1934 и 1410, лежащим на разных узлах.

Анализ допустимой опции FORWARD с помощью EXPLAIN

Узнать, с каким значением FORWARD запрос гарантированно будет выполнен, можно без его фактического исполнения — с помощью фасета FORWARD команды EXPLAIN.

Примечание

Указание опции FORWARD в самом запросе не влияет на вывод EXPLAIN. EXPLAIN всегда показывает максимально допустимое значение, определенное статически, — даже если в запросе задано более строгое значение опции. Так, вывод доступен даже для запроса с некорректной опцией FORWARD, поскольку возможность применить EXPLAIN к проблемным запросам — один из принципов реализации EXPLAIN в Picodata. Это позволяет заранее оценить, выполним ли запрос с нужным ограничением.

Подробнее — в описании фасета FORWARD.

Примеры использования

Подготовка тестового окружения

Примеры использования команд включают в себя запросы к тестовым таблицам.

Во всех примерах ниже используется тестовая таблица warehouse, шардированная по id.

INSERT-запросы

Рассмотрим запрос:

-- здесь используется расширенный синтаксис psql для параметров
EXPLAIN (FORWARD)
INSERT INTO warehouse VALUES ($1, $2, $3), ($4, $5, $6)
\bind 6 'boards' 'heavy' 7 'paint' 'light' \g
forward analysis (on > ro_to_rw > off):
  forward = off

Из этого сразу следует, что запрос может быть гарантированно выполнен с опцией FORWARD = OFF:

INSERT INTO warehouse VALUES ($1, $2, $3), ($4, $5, $6)
OPTION (FORWARD = OFF)
\bind 6 'boards' 'heavy' 7 'paint' 'light' \g

Это позволяет убедиться, что:

  1. в драйвере корректно реализован алгоритм вычисления bucket_id;
  2. драйвер корректно выбрал узел для записи порции данных.

SELECT-запросы

Рассмотрим запрос:

EXPLAIN (FORWARD) SELECT * FROM warehouse WHERE id = $1 OR id = $2 \bind 5 2 \g
forward analysis (on > ro_to_rw > off):
  forward = ro_to_rw

Из вывода видно, что запрос будет выполним с опцией RO_TO_RW: все бакеты лежат на одном узле, но запрос отправлен на не содержащий их узел. Таким образом, в конец запроса можно дописать опцию FORWARD:

SELECT * FROM warehouse WHERE id = $1 OR id = $2
OPTION (FORWARD = RO_TO_RW)
\bind 5 2 \g

Аналогично опция применима к запросам UPDATE и DELETE.

См. также