Чтение данных¶
Для обращения к данным в хранилище Tarantool Column Store используются SQL-запросы.
TCS поддерживает следующие способы обращения к данным:
SQL-запросы
SELECTчерез SQL и HTTP API – наиболее простой способ, подходящий для работы без нагрузки, например, для тестирования и отладки или единичного выполнения.Аналитические расчеты – хранимые в TCS именованные процедуры, в которых доступен широкий спектр операций над данными с использованием всех поддерживаемых функций и операторов SQL.
Представления для чтения (read views) – снимки хранилища данных в конкретный момент времени.
Нематериализованные SQL-представления (non-materialized views in SQL) – удобный способ обращаться к большим запросам, как к таблице.
Поддерживаемые операторы и функции SQL описаны в Справочнике по SQL.
SELECT-запросы через HTTP¶
Для выполнения запроса SELECT воспользуйтесь одним из двух интерфейсов: SQL или HTTP.
SQL:
Отправьте SQL-запрос через SQL-драйвер.
Пример запроса на чтение всех объектов таблицы:
SELECT * FROM users
HTTP:
Отправьте POST-запрос на HTTP-адрес
/sql.Пример запроса на чтение всех объектов таблицы:
curl -u tcs:tcs -d 'SELECT * FROM users' http://localhost:7777/sql
Пример запроса на чтение с фильтром:
curl -u tcs:tcs -d 'SELECT * FROM users WHERE id=1' http://localhost:7777/sql
Пример чтения из схемы по умолчанию
tcs.schema:curl -u tcs:tcs -d 'SELECT * FROM users WHERE id=1' http://localhost:7777/sql
Аналитические расчеты¶
TCS позволяет выполнять аналитические расчеты над хранимыми данными.
Аналитический расчет – это процедура, которая содержит агрегирующие запросы к таблицам и колонкам и возвращает результат выполнения запросов. В аналитических расчетах можно использовать поддерживаемые операторы и функции SQL, описанные в Справочнике по SQL.
TCS поддерживает два вида аналитических расчетов:
перманентные (хранятся в базе данных)
временные (работают только в пределах сессии)
Перманентные аналитические расчеты¶
Перманентные аналитические расчеты являются хранимыми объектами: чтобы выполнить расчет, нужно сначала его создать и загрузить в TCS. После этого он станет доступен для вызова через HTTP API по имени. Это позволяет уменьшить накладные расходы, возникающие при отправке SQL-запросов через SQL/HTTP API. Среди таких расходов: сериализация и десериализация, передача по сети, разбор и составление плана запроса. Таким образом, аналитические расчеты – оптимальный механизм для ресурсоемких запросов, повторяющихся многократно.
Перманентные аналитические расчеты специфичны для TCS.
Они создаются и вызываются только через SQL. Для этого используется код запуска
вида PREPARE my_func(INT) AS <query> и последующий вызов через EXECUTE.
Примечание
При работе с TCS по протоколу HTTP доступен только этот вид аналитических расчетов.
Подробнее о работе с этим видом расчетов см. ниже.
Временные аналитические расчеты¶
Временные аналитические расчеты представляют собой переменную-объект в языке программирования.
Они создаются и вызываются только через библиотечные API с помощью кода вызова
вида my_db.Prepare(sql).
Подробнее о работе с этим видом расчетов см. Работа с аналитическими расчетами через JDBC/ADBC.
Ограничения при работе с аналитическими расчетами¶
Аналитические расчеты разных видов можно комбинировать, но с ограничениями:
Из временного расчета можно выполнить перманентный расчет, вызвав его по имени.
Вызвать временный расчет из перманентного нельзя, потому что у временных расчетов нет имени для привязки.
В качестве аналитического расчета используется один подготовленный оператор SQL (prepared statement).
Созданные аналитические расчеты нельзя изменить. Вместо этого удалите существующий расчет и создайте заново.
Создание аналитических расчетов¶
Чтобы создать аналитический расчет, воспользуйтесь одним из двух интерфейсов: SQL или HTTP.
SQL:
Отправьте SQL-запрос через SQL-драйвер.
Запрос должен содержать SQL-оператор
PREPARE, создающий подготовленное SQL-выражение (prepared statement)Пример: расчет количества записей в таблице.
PREPARE plan(INT) AS SELECT count(*) FROM users WHERE age > $1
HTTP:
Отправьте POST-запрос на HTTP-адрес
/sql. В качестве значения используйте SQL-операторPREPARE, создающий подготовленное SQL-выражение (prepared statement).Пример: расчет количества записей в таблице.
curl -u tcs:tcs -d 'PREPARE plan(INT) AS SELECT count(*) FROM users WHERE age > $1' http://localhost:7777/sql
В качестве параметров для подготовленных SQL-выражений используйте типы данных PostgreSQL.
Выполнение аналитических расчетов¶
Чтобы выполнить существующий в TCS расчет, воспользуйтесь одним из двух интерфейсов: SQL или HTTP.
SQL:
Отправьте SQL-запрос с
EXECUTEчерез SQL-драйвер.Пример:
EXECUTE plan(0)
HTTP:
Отправьте POST-запрос на HTTP-адрес
/sql. JSON-тело запроса должно содержать инструкцию EXECUTE, имя и входные параметры расчета.Пример:
curl -u tcs:tcs -d 'EXECUTE plan(0)' http://localhost:7777/sql
В ответе TCS отправляет результаты выполнения в следующих секциях:
success– результаты успешно выполненных запросов;fail– информация о запросах, которые не удалось выполнить;timings– время, потраченное на разные этапы обработки запроса;plans– вывод EXPLAIN ANALYZE для планов, указанных в запросе.
(queries-computation-manage)
Управление аналитическими расчетами¶
Чтобы получить список аналитических расчетов и их статусов,
отправьте запрос SELECT, содержащий функцию tcs_prepareds.
Пример запроса:
SELECT * FROM tcs_prepareds()
В ответе придёт список, содержащий имена и тела запросов, их зависимости и статусы.
Пример ответа:
name query depends_on status
0 my_prepared PREPARE my_prepared A S SELECT 1 None Ready
Статус Ready указывает на то, что аналитический расчет готов. Если аналитический расчет
не готов (планируется к выполнению), его статус будет Planning.
Чтобы просмотреть текст конкретного аналитического расчета, отправьте запрос SELECT,
содержащий функцию tcs_prepareds и имя расчета.
Пример: просмотр текста существующего аналитического расчета my_prepared.
SELECT * FROM tcs_prepareds() WHERE name = my_prepared
Представления для чтения (read views)¶
TCS предполагает работу под постоянной транзакционной нагрузкой на запись. Для консистентности чтения данных в таких условиях TCS использует представления для чтения (read view) – снимки хранилища данных в конкретный момент времени.
В каждый момент времени в системе существует одно актуальное представление на чтение.
Оно обновляется раз в rv_update_ms миллисекунд.
Также есть очередь из используемых в данный момент представлений, из которых еще выполняется чтение в проходящих обработку запросах SQL и прочих.
Когда запрос на чтение обработан, и при этом уже есть более актуальное представление,
то представление, которые использовалось для этого запроса, помещается в очередь.
При следующей итерации цикла сборщика мусора (раз в rv_update_ms миллисекунд)
все представления из очереди удаляются.
Подробнее о представлениях на чтение читайте в документации Tarantool EE.
Нематериализованные SQL-представления (non-materialized views in SQL)¶
Нематериализованные SQL-представления удобны тем, что позволяют обращаться к большим запросам, как к таблице. Поскольку создаваемое представление не имеет физической формы, указанный запрос будет выполняться каждый раз при обращении к этому представлению.
Создание нематериализованных SQL-представлений¶
Чтобы создать нематериализованное SQL-представление, отправьте SQL-запрос, содержащий SQL-оператор CREATE VIEW.
Пример: SQL-представление, содержащее список имен всех совершеннолетних лиц в таблице.
CREATE VIEW adults AS SELECT name FROM users WHERE age >= 18;
Примечание
Созданные SQL-представления существуют только на мастер-узле хранилища Tarantool и не сохраняются между перезагрузками.
Использование нематериализованных SQL-представлений¶
После создания нематериализованное SQL-представление становится доступно:
для запросов на чтение,
для использования в других нематериализованных SQL-представлениях; это означает, что в TCS можно создавать вложенные (иерархические) нематериализованные SQL-представления.
Пример: обращение к SQL-представлению adults из SELECT-запроса.
SELECT salary FROM users WHERE name IS IN (SELECT name FROM adults)
Управление нематериализованными SQL-представлениями¶
Чтобы просмотреть все доступные нематериализованные SQL-представления, отправьте запрос SELECT,
содержащий функцию tcs_views:
SELECT * FROM tcs_views()
Чтобы просмотреть текст конкретного SQL-представления, отправьте запрос SELECT,
содержащий функцию tcs_views и имя представления.
Пример: просмотр текста существующего SQL-представления adults.
SELECT * FROM tcs_views() WHERE name = adults
Чтобы заместить существующее SQL-представление, отправьте SQL-запрос, содержащий SQL-оператор CREATE OR REPLACE VIEW.
Пример: замещение существующего SQL-представления adults.
CREATE OR REPLACE VIEW adults AS SELECT name FROM users WHERE age >= 16
Чтобы удалить SQL-представление, отправьте SQL-запрос, содержащий SQL-оператор DROP VIEW и имя представления.
Пример: удаление существующего SQL-представления adults.
DROP VIEW adults
Оптимизация запросов на чтение шардированных данных¶
В TCS предусмотрены правила оптимизации для запросов на чтение шардированных данных:
RemotePushDownLocalizedPrepared
Правило RemotePushDown¶
Правило оптимизации RemotePushDown позволяет исполнить всю возможную часть запроса
на одном шарде, для того чтобы по сети пересылался минимум данных. Без этой оптимизации
все данные из таблицы, необходимые для выполнения запроса, пересылались бы по сети
на экземпляр Scheduler, где дальше исполнялся бы сам запрос.
Далее рассмотрим на примерах, как это правило оптимизации влияет на обработку шардированных
запросов на чтение. Предположим, что у нас есть шардированная таблица t(a, b) и 3 шарда.
Простое полное сканирование (full scan)¶
Рассмотрим простой запрос:
SELECT * FROM t
Запросив EXPLAIN на экземпляре Scheduler, можно увидеть физический план следующего вида:
RemoteExec: table=datafusion.public.t, partitioning=Hash([a@0], 3), filter=None
UnionExec
TarantoolExec: table=datafusion.public.t fields=[a, b], ...
BufExec: table=datafusion.public.t fields=[a, b], ...
В этом плане есть узел RemoteExec, который отправляет нижележащий план на шарды
для последующего исполнения. Тот план, который он отправляет, назовем удаленным планом.
В нашем примере удаленный план – это план с корневым узлом UnionExec.
В описании RemoteExec мы видим следующее:
есть соответствие между этим узлом и таблицей
t(table=);партицирование данных производится по хэшу колонки
a(согласно схеме шардирования);используется пустой фильтр.
В нашем примере таблица будет просканирована на 3 шардах, а все записи отправлены на экземпляр Scheduler и там объединены.
Одношардовый перебор¶
Рассмотрим более сложный запрос, где для выполнения не обязательно читать данные со всех шардов:
SELECT * FROM t WHERE a = 1
Здесь мы получаем физический план следующего вида:
RemoteExec: table=datafusion.public.t, partitioning=Hash([a@0], 1), filter=datafusion.public.t.a=1
CoalesceBatchesExec: target_batch_size=8192
FilterExec: a@0 = 1
UnionExec
TarantoolExec: table=datafusion.public.t fields=[a, b], ...
BufExec: table=datafusion.public.t fields=[a, b], ...
В описании RemoteExec мы видим следующее:
весь план проталкивается внутрь (push down) через
RemoteExec;фильтрация выполняется полностью в рамках одного шарда, а по сети пересылаются уже отфильтрованные данные.
Здесь правило RemotePushDown пытается поднять RemoteExec как можно выше.
Для того чтобы посмотреть, как отработало то или иное правило оптимизации, можно
использовать запрос EXPLAIN VERBOSE. Он возвращает состояния плана после каждой
изменившей его оптимизации. Например, в нашем случае можно видеть, что первоначальный
физический план имеет такой вид:
FilterExec: a@0 = 1
RemoteExec: table=datafusion.public.t, partitioning=Hash([a@0], 1), filter=datafusion.public.t.a=1
UnionExec
TarantoolExec: table=datafusion.public.t fields=[a, b], ...
BufExec: table=datafusion.public.t fields=[a, b], ...
Здесь фильтрация осуществляется после сбора данных с шардов, а после прохода
правила оптимизации RemotePushDown фильтр проталкивается вниз через RemoteExec,
вследствие чего запрос становится лучше локализован в рамках шарда.
Исполнение частичной агрегации на шардах¶
Правило RemotePushDown также может обеспечить выполнение частичной агрегации на шардах.
Рассмотрим запрос:
SELECT count(*) FROM t
Он имеет следующий физический план:
AggregateExec: mode=Final, gby=[], aggr=[count(*)]
CoalescePartitionsExec
RemoteExec: partitioning=UnknownPartitioning(3), filter=None
AggregateExec: mode=Partial, gby=[], aggr=[count(*)]
UnionExec
TarantoolExec: table=datafusion.public.t fields=[], ...
BufExec: table=datafusion.public.t fields=[], ...
Как мы видим, частичная агрегация была перенесена на шард. Заметим, что в этом примере
для RemoteExec уже не выводится table, а также используется UnknownPartitioning
вместо Hash. Это происходит из-за того, что после исполнения агрегации изменяется
выходная схема: поле a в ней больше не присутствует, но появляется поле count(*).
То, какие узлы можно проталкивать внутрь для исполнения на одном шарде, определяется следующими факторами:
Если в запросе указан фильтр, который читает лишь одно значение ключа шардирования (например,
a = $1), то запрос может быть полностью локализован и план будет полностью протолкнут подRemoteExec.В ином случае проталкивание производится до тех пор, пока шард может исполнить соответствующий узел самостоятельно (то есть пока для сбора данных шарду не нужны данные с других шардов). Например,
CoalescePartitionsExecне может быть исполнен локально, поскольку для него необходимо собрать данные со всех шардов, тогда какFilterExecможет быть исполнен локально.
Правило LocalizedPrepared¶
Правило оптимизации LocalizedPrepared позволяет полностью предотвратить передачу
физического плана с экземпляра Scheduler на экземпляр Storage при исполнении
обоих следующих условий:
Исполняется аналитический запрос (prepared statement).
План был полностью локализован.
В таком случае при исполнении аналитического запроса будет использоваться план следующего вида:
PreparedStatementExec: name=query, mode=local {from_cache: true}
RemoteExec: table=datafusion.public.sharded_by_indexed_string, partitioning=Hash([a@0], 1), filter=None
PreparedStatementExec: name=query, mode=remote, metrics=[]\n
Данный план следует читать так:
«Исполняется аналитический запрос, локальный для экземпляра Scheduler.
Его план состоит из однопартицированного RemoteExec, который отправляет
удаленный план на экземпляр Storage для исполнения аналитического запроса.»
В этом случае вместо тяжелого плана будет отослан всего один узел (PreparedStatementExec).
Далее экземпляр Storage сам запланирует аналитический запрос с этим названием
(либо возьмет план для исполнения из кэша) и исполнит его.