Опция 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:
ON— локальности нет ни в каком смысле;RO_TO_RW— все бакеты запроса принадлежат одному узлу;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
Это позволяет убедиться, что:
- в драйвере корректно реализован алгоритм вычисления
bucket_id; - драйвер корректно выбрал узел для записи порции данных.
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.