Data model¶
В этой главе подробно описана структура модели данных TDG, а также перечислены доступные способы работы с ней – в файле и в веб-интерфейсе. В главе рассмотрен пример создания модели данных и её загрузки в TDG. Для выполнения примера требуется настроенный кластер TDG.
Язык модели данных¶
Модель данных включает в себя:
структуру объектов для заданной предметной области;
описание связей между объектами;
ограничения, которые накладываются на объекты.
В TDG для описания модели данных используются язык Avro Schema, а также расширения, разработанные специально для системы TDG.
Схема в стандарте Avro Schema – это JSON-файл с расширением .avsc
, содержащий описание типов данных для объектов и для их полей.
Приложение использует схему в качестве формата и понимает ее, как массив типов объектов:
[
{"name": "TypeA", "type": "record", ...},
{"name": "TypeB", "type": "record", ...}
]
Все типы объектов соответствуют стандарту Avro Schema, за исключением расширений для системы TDG. Расширения обратно совместимы, а модель, описанная с их помощью, успешно преобразуется стандартными парсерами (синтаксическими анализаторами).
Определение модели данных¶
Для начала зададим модель данных с двумя типами объектов – Country
(страна) и City
(город).
В качестве примера возьмем следующую диаграмму объектов:
Теперь представим модель с диаграммы в виде схемы данных Avro:
[
{
"name": "Country",
"type": "record",
"doc": "Страна",
"fields": [
{"name": "title", "type": "string"},
{"name": "phone_code", "type": ["null", "string"]}
],
"indexes": ["title"],
"relations": [
{ "name": "city_relation", "to": "City", "count": "many", "from_fields": "title", "to_fields": "country" }
]
},
{
"name": "City",
"type": "record",
"doc": "Город",
"fields": [
{"name": "title", "type": "string"},
{"name": "country", "type": "string"},
{"name": "population", "type": "int"},
{"name": "capital", "type": "boolean"},
{"name": "postcodes", "type": {"type":"array", "items":"int"}}
],
"indexes": [
{"name":"primary", "parts":["title", "country"]},
"title",
"country",
"population",
"postcodes"
]
}
]
где
name
– название типа объекта;type
– вид схемы;doc
– текстовое описание для типа объекта;fields
– поля для типа объекта вместе с соответствующими им типами данных.name
– название поля;type
– тип данных для поля.
Note
Если в модели данных есть опциональное поле, его тип данных описывают с помощью объединяющего массива
union
. Такой массив содержит основной тип данных для этого поля и типnull
. Пример:{"name": "phone_code", "type": ["null", "string"]}
indexes
– индексы, которые используются при выполнении операций. Первый по счету индекс в списке является первичным. Полеindexes
– расширение TDG для задания ключей;relations
– вид связи между объектами. В этой модели данных типы объектовCountry
иCity
имеют связь один ко многим, так как в одной стране обычно расположено много городов. Полеrelations
– расширение TDG для задания отношений между объектами.
Описания всех доступных расширений TDG приведены в разделе Расширения модели данных.
Загрузка модели данных¶
Чтобы применить модель данных и делать запросы к данным, нужно загрузить модель данных в TDG. Основные способы загрузки данных:
через веб-интерфейс на вкладке Model;
в файле
.avsc
, который будет запакован в архив вместе с файлом конфигурации.
Загрузка через веб-интерфейс¶
Чтобы загрузить модель данных через веб-интерфейс TDG, выполните следующие шаги:
Откройте веб-интерфейс на экземпляре, входящем в набор реплик с кластерной ролью
runner
. Если вы используете уже развернутый TDG-кластер, URL экземпляра будет следующий: http://172.19.0.2:8082.В веб-интерфейсе выберите вкладку Model.
Вставьте созданную модель данных в поле Request.
Нажмите Submit. Если модель данных загружена успешно, в окне Response появится ответ
OK
.
Загрузка в составе zip-архива¶
Загрузить модель данных можно также в составе zip-архива вместе с файлом конфигурации. Для этого:
Упакуйте в zip-архив:
модель данных в файле с расширением
.avsc
, например,model.avsc
;файл конфигурации
config.yml
.
Загрузите архив в TDG согласно инструкции.
Расширения модели данных¶
Расширения для системы TDG дополняют спецификацию Avro Schema. Эти расширения позволяют:
Задание отношений между объектами¶
Явно задавать связи между объектами нужно:
для валидации внешних ключей при вставке объектов;
для запросов связанных объектов через GraphQL.
Чтобы указать такую связь, используется поле relations
в теле описания типа объекта.
Это поле игнорируется существующими парсерами Avro Schema.
Связываемые поля (внешние ключи в терминологии SQL) должны быть объявлены на уровне типов объектов.
Например, чтобы задать связь между страной и ее городами, требуются следующие поля:
title
(Country
) – первичный ключ доступен через обращение к типу объектаCountry.title
;country
(City
) – внешний ключ доступен через обращение к типу объектаCity.country
.
Пример поля relations
из заданной ранее модели данных:
{
"relations": [
{
"name": "city_relation",
"to": "City",
"count": "many",
"from_fields": "title",
"to_fields": "country"
},
...
]
}
где:
name
– название поля, через которое можно получить связанные объекты в GraphQL-запросах;to
– тип объекта, с которым устанавливается связь;count
– вид связи. Возможные значения:one
– связь один к одному;many
– связь один ко многим;
from_fields
– название поля или индекса, содержащего первичный ключ. Возможные значения:название индекса (
"from_fields": "title"
);название поля (
"from_fields": "field_name"
);список названий полей (
"from_fields": ["field_name1", "field_name2"]
);
to_fields
– название поля или индекса, содержащего внешний ключ. Возможные значения:название индекса (
"to_fields": "country"
);название поля (
"to_fields": "field_name"
);список названий полей (
"to_fields": ["field_name1", "field_name2"]
).
Все параметры поля обязательные.
Поле relations
можно задать как с одной стороны отношения, так и с обеих.
Поле указывается только в одном типе объекта, когда запрашивать данные в обратном направлении не требуется.
Дополним модель данных еще одним полем relations
(City
), чтобы можно было запрашивать данные в обоих направлениях.
Полный пример может выглядеть так:
[
{
"name": "Country",
"type": "record",
"doc": "Страна",
"fields": [
{"name": "title", "type": "string"},
{"name": "phone_code", "type": ["null", "string"]}
],
"indexes": ["title"],
"relations": [
{"name": "city_relation", "to": "City", "count": "many", "from_fields": "title", "to_fields": "country"}
]
},
{
"name": "City",
"type": "record",
"doc": "Город",
"fields": [
{"name": "title", "type": "string"},
{"name": "country", "type": "string"},
{"name": "population", "type": "int"},
{"name": "capital", "type": "boolean"},
{"name": "postcodes", "type": {"type":"array", "items":"int"}}
],
"indexes": [
{"name":"primary", "parts":["title", "country"]},
"title",
"country",
"population",
"postcodes"
],
"relations": [
{"name": "country_relation", "to": "Country", "count": "one", "from_fields": "country", "to_fields": "title"}
]
}
]
Задание индексов (ключей)¶
Для задания индексов используется поле indexes
в описании типа объекта.
Поле не меняет поведение, регулируемое стандартом Avro Schema, а добавляет дополнительные
ограничения на хранение и запросы.
Если для типа объекта указаны индексы, TDG создает спейсы под этот тип.
Note
Если поле indexes
содержит список индексов, первичным индексом считается первый индекс в списке.
Синтаксис поля indexes
:
{
"indexes": ["<index1>", <"index2">, <"index3">, ...]
}
Каждый индекс в поле indexes
может быть задан в виде:
строки с названием поля, по которому будет построен индекс (
"indexes": ["title"]
);словаря, на основе которого строятся составной индекс или индекс по полю вложенного объекта. Такой индекс указывается в следующем формате:
{ "name": "<index_name>", "parts": [ {"path": "<field_name>", "collation": "<collation_type>"}, {"path": "<field_name>", "collation": "<collation_type>"}, ... ] }
где:
name
– название индекса, не совпадающее с именами существующих полей;parts
– части индекса. Включает в себя:path
– названия полей, по которым строится индекс;collation
(опционально) – способ сравнения строк. Возможные значения:binary
(по умолчанию) – бинарное сравнение ('A' < 'B' < 'a'
);case_sensitivity
– сравнение, зависящее от регистра ('a' < 'A' < 'B'
);case_insensitivity
– сравнение, не зависящее от регистра (('a' = 'A') < 'B'
и'a' = 'A' = 'á' = 'Á'
).
Note
Существуют ограничения при использовании в поле indexes
мультиключевого индекса – индекса по полю, содержащему массив.
Подробная информация об этих ограничениях приведена в разделе Запросы по мультиключевому индексу.
Пример составного индекса
{
"name": "City",
"type": "record",
"fields": [
{"name": "title", "type": "string"},
{"name": "country", "type": "string"},
{"name": "population", "type": "int"},
{"name": "capital", "type": "boolean"},
{"name": "postcodes", "type": {"type":"array", "items":"int"}}
],
"indexes": [
{"name":"primary", "parts":["title", "country"]},
"title",
"country",
"population",
"postcodes"
]
}
Пример индексации по полю вложенного объекта
Иногда требуется построить индекс по полю не из самого объекта, а из его вложенного объекта. Вложенный объект при этом создается, чтобы сгруппировать логически набор стандартных полей.
Note
Тип объекта можно использовать как вложенный только в случае, если у этого типа не задано поле indexes
.
Например, представьте, что каждая страна (Country
) в примере содержит дополнительный блок информации для туристов.
Создадим новый тип объекта Info
и сложный индекс в типе объекта Country
.
Тип объекта Info
включается здесь непосредственно в тип объекта Country
, поэтому индекс может сослаться на поле из Info
:
[
{
"name": "Info",
"type": "record",
"doc": "Информация для туристов",
"fields": [
{"name": "id", "type": "long"},
{"name": "info_text", "type": "string"}
]
},
{
"name": "Country",
"type": "record",
"doc": "Страна",
"fields": [
{"name": "title", "type": "string"},
{"name": "info", "type": "Info"},
{"name": "phone_code", "type": ["null", "string"]}
],
"indexes": [
"title",
{"name": "info_id", "parts": ["info.id"]}
]
}
]
Распределение объектов по хранилищам¶
В случае распределенного хранилища данных объекты распределяются с
использованием хеш-функции от первичного ключа объекта.
Указать такой индекс можно в поле affinity
.
Note
Поле affinity
может содержать только те поля, которые входят в первичный ключ (в том числе составной).
Пример
Перед загрузкой модели с новым полем affinity
удалите старую модель данных.
Кроме того, проверьте список спейсов во вкладке Settings > Unlinked spaces в веб-интерфейсе TDG.
Если в списке есть спейсы с типом объекта City
, удалите их, чтобы избежать ошибки при загрузке новой модели.
Теперь дополните модель данных новым полем и загрузите модель в TDG:
{
"name": "City",
"type": "record",
"doc": "Город",
"fields": [
{"name": "title", "type": "string"},
{"name": "country", "type": "string"},
{"name": "population", "type": "int"},
{"name": "capital", "type": "boolean"},
{"name": "postcodes", "type": {"type":"array", "items":"int"}}
],
"indexes": [
{"name":"primary", "parts":["title", "country"]},
"title",
"country",
"population",
"postcodes"
],
"affinity": ["country"]
}
В примере города одной и той же страны размещены физически на одном хранилище.
Поле affinity
при этом содержит индекс из составного первичного ключа.
Атрибуты полей¶
Расширение для TDG дополняет список стандартных атрибутов полей Avro Schema:
default_function
(function) – задает для поля динамическое значение по умолчанию. Значение атрибута – это функция, файл с которой хранится в директорииsrc
в корне проекта. При вставке в спейс новой записи вызывается указанная функция, и результат функции становится значением для данного поля. Атрибут не стоит путать со стандартным атрибутомdefault
, который задает для поля статическое значение;auto_increment
(boolean) – значениеtrue
делает поле автоинкрементным. По умолчанию:false
. Может использоваться в качестве числового идентификатора, уникального для объектов данного типа, даже при шардировании базы данных. Автоинкрементное поле обязательно должно иметь типlong
. Атрибут несовместим с атрибутамиdefault
иdefault_function
.
Пример
Расширьте тип объекта City
этими атрибутами:
задайте по умолчанию для поля
population
динамическое значение;добавьте поле с числовым идентификатором города.
Обновленные поля объекта могут выглядеть так:
{
"name": "City",
"type": "record",
"fields": [
{"name": "city_id", "type": "long", "auto_increment": true},
{"name": "title", "type": "string"},
{"name": "country", "type": "string"},
{"name": "population", "type": "int", "default" : "count_population.call"}},
...
],
...
}
Логические типы данных¶
Логический тип – это простой или составной тип данных Avro, содержащий дополнительные атрибуты для представления
нового типа данных для поля.
Чтобы объявить в модели поле с логическим типом, используется атрибут logicalType
.
Поля этих типов можно использовать при построении любых индексов,
кроме мультиключевых.
По умолчанию для логических типов задается строковый тип данных.
TDG поддерживает следующие логические типы:
Date
– дата. Хранится в видеint
. Формат записи:YYYY-MM-DDZ
;Time
– время. Хранится в видеint
. Формат записи:HH:MM:SSZ
;Datetime
– дата и время. Хранится в видеint
. Форматы записи:дата и время с миллисекундами:
YYYY-MM-DDTHH:MM:SS.sssZ
;дата и время с микросекундами:
YYYY-MM-DDTHH:MM:SS.ssssssZ
;дата и время с наносекундами:
YYYY-MM-DDTHH:MM:SS.sssssssssZ
.
Note
Начиная с версии TDG 2.7, тип
DateTime
объявлен устаревшим, используйте вместо него типTimestamp
.Timestamp
– тип для работы с датой и временем на основе Tarantool-модуля datetime;Decimal
– вычисления с точными числами. Пример записи:10.001
. Тип использует Tarantool-модуль decimal;UUID
– уникальный идентификатор. Формат записи:00000000-0000-0000-0000-000000000000
. Тип использует Tarantool-модуль uuid.
Примеры объявления полей с логическими типами:
[
{
"name": "LogicalTypes",
"type": "record",
"fields": [
{"name": "id", "type": {"type": "string", "logicalType": "Timestamp"}},
{"name": "datetime", "type": ["null", {"type": "string", "logicalType": "DateTime"}]},
{"name": "time", "type": ["null", {"type": "string", "logicalType": "Time"}]},
{"name": "date", "type": ["null", {"type": "string", "logicalType": "Date"}]},
{"name": "decimal", "type": ["null", {"type": "string", "logicalType": "Decimal"}]},
{"name": "uuid", "type": ["null", {"type": "string", "logicalType": "UUID"}]}
],
"indexes": ["id", "datetime", "time", "date", "decimal", "uuid"]
}
]