Updated at 2023-06-07 03:30:12.888830
1. Руководство по разработке приложений
Систем-источников сложных бизнес-объектов много, и они разрабатываются разными
командами. Даже если системы работают с одними и теми же сущностями, их
представление и структура, как правило, отличаются от системы к системе.
Чтобы унифицированно строить запросы по объектам из разных источников,
система TDG приводит такие объекты к одному каноническому формату.
Для задания формата используется специализированный высокоуровневый язык.
Он позволяет описывать структуру объектов, связи между ними и накладываемые на них ограничения.
Чтобы создать приложение для TDG, необходимо описать, разработать
и организовать:
Доменную модель (канонический формат объектов), используя
соответствующий язык;
Само приложение, взаимодействующее с компонентами TDG и использующее
программный интерфейс репозитория для управления
объектами модели;
Версионирование модели (согласно ее представлению) и всех
необходимых компонентов приложения для поддержания обратной совместимости;
Тесты для проверки логики модулей TDG и приложения на
соответствие требованиям.
Данное руководство описывает каждый из данных шагов и содержит сведения, необходимые для их
выполнения.
1.1. Разработка доменной модели
Чтобы приступить к описанию доменной модели, ознакомьтесь со следующими
понятиями по порядку и изучите приведенный пример:
В терминологии системы TDG язык описания является языком доменной модели,
при этом домен — синоним понятия предметная область.
Язык доменной модели состоит из двух элементов:
В качестве такого языка используется Avro Schema.
Он не сложный и достаточно распространен в сообществе и разрабатываемых им
сторонних приложениях.
В стандарте Avro Schema есть два «контейнера», содержащих описания
типов: протокол и схема. Оба — JSON-файлы с расширением .avsc
.
Приложение использует схему в качестве формата и понимает ее в виде массива
типов:
[
{"name": "TypeA", "type": "record", ...},
{"name": "TypeB", "type": "record", ...}
]
Каждый тип соответствует стандарту Avro Schema,
за исключением расширений для системы
TDG. Расширения обратно совместимы, а модель, описанная с их
помощью, должна успешно преобразовываться стандартными парсерами
(синтаксическими анализаторами).
В дополнение к типичной для UML (Unified Model Language) терминологии
«агрегация и композиция»,
в TDG есть три дополнительных понятия:
Агрегат (Aggregate) — самостоятельный объект, имеющий идентичность;
Сущность (Entity) — несамостоятельный объект, имеющий идентичность;
Значение (Value Object) — несамостоятельный объект, не имеющий
идентичности.
Примечание
Значение здесь — отдельная единица моделирования (значение-объект),
а не атрибут класса объекта.
Эти понятия — часть словаря Domain Driven Design.
Они описывают классы объектов с точки зрения идентичности и принадлежности.
Идентичность определяет, есть ли у объекта жизненный цикл во времени.
Объект имеет идентичность, если остается тем же самым объектом, даже
когда его свойства меняются.
Примером класса, объекты которого имеют идентичность, является
класс клиент (бизнеса). Изменение имени клиента или его адреса
не делает его другим клиентом — это тот же человек.
С другой стороны, его адрес не имеет собственной
идентичности, потому что смена улицы и дома сделает его другим адресом.
Принадлежность определяет, может ли объект существовать отдельно от
другого. Объект самостоятелен, если его создание не требует существования
других объектов или если можно удалить тот объект, который на него ссылается,
в то время как самостоятельный объект будет жив.
Примером сущности, которая не может существовать отдельно, является
паспорт. Паспорта выдаются только существующим людям, и, даже
если они были выданы «мертвым душам», полное удаление информации о «душах»
потребует удаления информации об их паспортах.
Примером сущности, которая может существовать отдельно, является как сам
клиент, так и его договоры. Если договоры или другие объекты,
ссылающиеся на пользователя, будут удалены, существование объекта
пользователя все еще будет иметь смысл. С другой стороны, если клиент пропал
и был удален из системы, например, при компактизации данных или при их
повреждении, существование его договоров тоже все еще будет иметь смысл,
например, для построения финансовой отчетности.
Является ли объект агрегатом, сущностью или значением, важно не только для
контроля жизненного цикла, но и для указания границ транзакционности и
возможности ссылаться на объекты.
В пределах одного агрегата все его подчиненные сущности и значения можно
обновить транзакционно и атомарно. Любая логика, работающая на уровне
агрегата, может на это рассчитывать. Между агрегатами таких гарантий нет,
то есть логика, обновляющая несколько агрегатов, является всегда
согласованной в конечном счете (eventually consistent).
Также объекты могут ссылаться либо на объекты-сущности внутри своего
агрегата-родителя, либо на другие агрегаты. Ссылки на сущности других
агрегатов запрещены, так как сущности являются деталью реализации
агрегата, и их данные всегда запрашиваются через объект агрегата.
Предположим, в системе требуется хранить информацию о клиентах (Client
),
их паспортах (Passport
), адресах (Address
), договорах (Contract
),
счетах (Account
) и операциях (Operation
) по ним.
Рассмотрим следующую диаграмму объектов в качестве примера:
В примере:
Агрегатами являются Client
, Contract
, Account
и Operation
. Информацию о каждом из них следует хранить вне
зависимости от наличия других объектов, чтобы построить финансовую
отчетность для любого временного среза;
Сущностью — Passport
. Информацию о паспортах хранить отдельно
от клиентов не нужно, паспорта не существуют сами по себе. У каждого клиента может
быть несколько паспортов и их состояние может меняться, например, может истечь срок действия.
Поэтому каждый паспорт — отдельная сущность, зависимая от агрегата клиента;
Значением — Address
, так как он не обладает идентичностью.
Система автоматически версионирует объекты, поэтому создавать массив для новых
адресов не нужно, достаточно одного объекта-значения.
Из схемы также видны отношения:
Между Client
и Address
, а также множеством
экземпляров Passport
есть отношение владения;
Объекты Client
, Contract
, Account
и Operation
существуют отдельно. Cвязи между ними — ссылочного типа.
Одному и тому же агрегату разрешено находиться в отношениях
агрегации с другими объектами.
Опишем данную структуру на языке доменной модели:
[
{
"name": "Passport",
"type": "record",
"logicalType": "Entity",
"fields": [
{"name": "id", "type": "long"},
{"name": "passport_series", "type": "string"},
{"name": "passport_number", "type": "string"},
{"name": "expired_flag", "type": "boolean"}
],
"indexes": [
"id",
{
"name": "passport",
"parts": ["passport_series", "passport_number"]
}
]
},
{
"name": "Address",
"type": "record",
"logicalType": "ValueObject",
"fields": [
{"name": "country", "type": "string"},
{"name": "city", "type": "string"},
{"name": "street", "type": "string"},
{"name": "building", "type": "int"}
]
},
{
"name": "Client",
"type": "record",
"logicalType": "Aggregate",
"doc": "Клиент",
"fields": [
{"name": "id", "type": "long"},
{"name": "first_name", "type": "string"},
{"name": "last_name", "type": "string"},
{"name": "passports", "type": {"type": "array", "items": "Passport"}},
{"name": "address", "type": "Address"}
],
"indexes": ["id"],
"relations": [
{"name": "contracts", "to": "Contract", "count": "many",
"from_fields": "id", "to_fields": "client_id"}
]
},
{
"name": "Contract",
"type": "record",
"logicalType": "Aggregate",
"doc": "Договор",
"fields": [
{"name": "id", "type": "long"},
{"name": "client_id", "type": "long"},
{"name": "header", "type": "string"},
{"name": "body", "type": "string"},
{"name": "date", "type": {"type": "string", "logicalType": "Date"}}
],
"indexes": [
"id",
"client_id"
],
"relations": [
{"name": "accounts", "to": "Account", "count": "many",
"from_fields": "id", "to_fields": "contract_id"}
]
},
{
"name": "Account",
"type": "record",
"logicalType": "Aggregate",
"doc": "Счет",
"fields": [
{"name": "id", "type": "long"},
{"name": "contract_id", "type": "long"},
{"name": "date", "type": {"type": "string", "logicalType": "Date"}}
],
"indexes": [
"id",
"contract_id"
],
"relations": [
{"name": "operations", "to": "Operation", "count": "many",
"from_fields": "id", "to_fields": "account_id"}
]
},
{
"name": "Operation",
"type": "record",
"logicalType": "Aggregate",
"doc": "Операция",
"fields": [
{"name": "id", "type": "long"},
{"name": "account_id", "type": "long"},
{"name": "amount", "type": "double"},
{"name": "type", "type": "string"},
{"name": "timestamp", "type": {"type": "string", "logicalType": "DateTime"}}
],
"indexes": [
"id",
"account_id"
]
}
]
Примечание
Если какое-то поле является опциональным, в доменной модели его тип описывают с помощью
union — массива, содержащего основной тип для этого поля и тип null
.
Например:
{"name": "amount", "type": ["null","double"]}
При описании мы использовали расширения для TDG.
Следующий раздел подробно описывает каждое расширение.
Расширения дополняют спецификацию Avro Schema и позволяют
воспользоваться функциональностью приложения.
TDG понимает следующие расширения:
Для указания признака агрегата, сущности или значения используется
логический тип Avro Schema (Logical Type
). Тип указывает приложению
трактовать себя специальным образом.
В документации Avro Schema сказано,
что логические типы можно использовать, например, для представления даты и времени.
Наше приложение использует для оперирования датами и временем
объекты DateTime, но на уровне сериализации и хранения они имеют строковый
тип.
Поэтому даты, например, задаются так:
{ "type": "string", "logicalType": "Date"}
Стандарт Avro Schema не предписывает ничего по поводу допустимых
значений logicalType
, поэтому мы можем его использовать, чтобы
придать типам дополнительный смысл.
TDG понимает следующие допустимые значения logicalType
для типов
модели:
Aggregate
(агрегат);
Entity
(сущность);
ValueObject
(значение).
В коде модели мы задали типы всем классам объектов.
Например, клиенту:
{
"name": "Client",
"type": "record",
"logicalType": "Aggregate",
"fields": [...]
}
Если logicalType
не указан, по умолчанию подразумевается ValueObject
.
Явное задание отношений между объектами нужно для двух целей:
Для задания связи используется поле relations
в теле описания
класса объекта. Это поле не является стандартным, но игнорируется
существующими парсерами Avro Schema. Связь — логическая конструкция.
Связываемые поля (внешние ключи в терминологии SQL) должны быть
объявлены на уровне классов.
Например, для связи между клиентом (Client
) и его контрактом (Contract
)
требуются следующие поля:
{
"name": "Client",
"type": "record",
"logicalType": "Aggregate",
"fields": [
{"name": "id", "type": "long"},
{"name": "first_name", "type": "string"},
{"name": "last_name", "type": "string"},
{"name": "passports", "type": {"type": "array", "items": "Passport"}},
{"name": "address", "type": "Address"}
],
"indexes": ["id"],
// Здесь должно быть поле "relations", формат которого описан ниже.
},
{
"name": "Contract",
"type": "record",
"logicalType": "Aggregate",
"doc": "Договор",
"fields": [
{"name": "id", "type": "long"},
{"name": "client_id", "type": "long"},
{"name": "header", "type": "string"},
{"name": "body", "type": "string"},
{"name": "date", "type": {"type": "string", "logicalType": "Date"}}
],
"indexes": [
"id",
"client_id"
],
}
Здесь:
Чтобы сделать связь явной, определим поле relations
в
следующем формате:
"relations": [
{
// Все параметры обязательные.
"name": "<имя_отношения>",
"to": "<класс_объекта>",
"count": <"one"|"many">, // один к одному или один к многим
"from_fields": <спецификация_первичного_ключа>,
"to_fields": <спецификация_внешнего_ключа>
},
...
]
где:
name
— имя виртуального поля, через которое можно будет получить
связанные объекты в graphql-запросах;
to
— имя класса, с которым устанавливается связь;
count
— вид связи: один к одному или один ко многим;
from_fields
— спецификация поля, которое содержит первичный ключ;
to_fields
— спецификация поля, которое содержит внешний ключ.
Спецификация обоих ключей (from_fields
и to_fields
) должна быть задана
в следующем формате:
"index_name" | "field_name" | ["field_name", "field_name", "field_name", ...]
То есть в полях from_fields
и to_fields
можно указывать имя индекса,
имя поля (если оно одно) или список имен полей (если их больше).
Поле relations
можно указать как с одной стороны отношения, так
и с обеих. Односторонние отношения имеют смысл тогда, когда
запрашивать данные в «обратном направлении» не требуется.
Полный пример задания отношения один ко многим между Client
и
Contract
с возможностью запроса данных в обоих направлениях:
[
{
"name": "Client",
"type": "record",
"logicalType": "Aggregate",
"fields": [
{"name": "id", "type": "long"},
...
],
"indexes": ["id"],
"relations": [
{"name": "contracts", "to": "Contract", "count": "many",
"from_fields": "id", "to_fields": "client_id"}
]
},
{
"name": "Contract",
"type": "record",
"logicalType": "Aggregate",
"fields": [
{"name": "id", "type": "long"},
{"name": "client_id", "type": "long"},
...
],
"indexes": [
"id",
"client_id"
],
"relations": [
{"name": "client", "to": "Client", "count": "one",
"from_fields": "client_id", "to_fields": "id"}
]
},
]
Для задания ключей используется поле indexes
в описании класса. Так же
как и relations
, indexes
не является частью спецификации, но не
меняет поведения, регулируемого стандартом, а добавляет дополнительные
ограничения на хранение и запросы.
Ключи описываются в следующем формате:
[<index1>, <index2>, <index3>, ...]
Где каждый ключ может быть:
В виде строки:
где field_name
— имя поля, по которому будет сделан ключ.
Либо в виде словаря (для составных ключей):
{
"name": "<index_name>",
"parts": ["<field1_name>", "<field2_name>", ...]
[, "collation"="binary"|"case_sensitivity"|"case_insensitivity"]
}
где:
index_name
— имя составного ключа, которое не должно совпадать с
именами существующих полей;
field<X>_name
— имя одного из полей, по которому строится индекс;
collation
— способ сравнения строк. По умолчанию способ binary
—
бинарный 'A' < 'B' < 'a'
.
Значение case_sensitivity
включит регистрозависимое сравнение
'a' < 'A' < 'B'
.
Значение case_insensitivity
включит регистронезависимое сравнение
'a' = 'A' < 'B'
и 'a' = 'A' = 'á' = 'Á'
.
Для указания первичности ключа нет отдельного явного признака. Первичным ключом
признается первый ключ в списке.
Примечание
Нужно отметить исключения для так называемого мультиключевого индекса
(multikey index), т.е. индекса по полю, содержащему массив:
мультиключевой индекс не может быть первичным;
мультиключевой индекс нельзя строить по полю, содержащему сложные типы,
такие как DateTime
, Date
, Time
, Decimal
и др.
Запрос к данным по мультиключевому индексу также имеет специфику
(см. подробнее).
Полный пример задания ключей:
{
"name": "Passport",
"type": "record",
"logicalType": "Entity",
"fields": [
{"name": "id", "type": "long"},
{"name": "passport_series", "type": "string"},
{"name": "passport_number", "type": "string"},
{"name": "expired_flag", "type": "boolean"}
],
"indexes": [
"id",
{
"name": "passport",
"parts": ["passport_series", "passport_number"]
}
]
}
Более сложный случай индексации — когда индекс делается по полю,
присутствующему не в самом объекте, а в одном из его подобъектов.
Такое возможно, если подобъект создается для логической
группировки набора стандартных полей.
Например, представим, что операция по счету (Operation
) в нашем примере
производится по протоколу, требующему определенный заголовок. Создадим
соответствующий объект-значение для него и сложный индекс в агрегате
операции:
[
{
"name": "Header",
"type": "record",
"doc": "Заголовок операции",
"logicalType": "ValueObject",
"fields": [
{"name": "id", "type": "long"},
{"name": "header_body", "type": "string"}
]
},
{
"name": "Operation",
"type": "record",
"logicalType": "Aggregate",
"doc": "Операция",
"fields": [
{"name": "id", "type": "long"},
{"name": "header", "type": "Header"},
{"name": "account_id", "type": "long"},
{"name": "amount", "type": "double"},
{"name": "type", "type": "string"},
{"name": "timestamp", "type": {"type": "string", "logicalType": "DateTime"}}
],
"indexes": [
"id",
"account_id",
{"name": "header_id", "parts": ["header.id"]}
]
}
]
Header
как объект-значение включается непосредственно в агрегат
Operation
, поэтому индекс может сослаться на поле из Header
.
Если бы Header
был сущностью или агрегатом, модель не прошла бы валидацию.
В случае распределенного хранилища данных агрегаты распределяются с
использованием хеш-функции от первичного ключа объекта.
Для явного указания ключей для этой функции используется поле affinity
с форматом:
"affinity": <index_name>[, "affinity": index_name, ...]
Директива может содержать только ключи, входящие в первичный ключ.
Например, для распределенного хранения операций по счетам укажем:
{
"name": "Operation",
"type": "record",
"logicalType": "Aggregate",
"doc": "Операция",
"fields": [
{"name": "id", "type": "long"},
{"name": "account_id", "type": "long"},
{"name": "amount", "type": "double"},
{"name": "type", "type": "string"},
{"name": "timestamp", "type": {"type": "string", "logicalType": "DateTime"}}
],
"indexes": [
{"name":"pkey", "parts": ["id", "account_id"]},
"account_id",
],
"affinity": "account_id"
}
Таким образом, операции одного и того же счета будут размещены физически на одном и том же хранилище.
Помимо стандартных атрибутов полей, определенных в спецификации Avro Schema,
в TDG есть несколько дополнительных атрибутов:
default_function
— используется для задания динамического значения по
умолчанию (в отличие от стандартного атрибута default
, задающего
статическое значение). В качестве значения атрибута указывается функция,
определенная в секции functions
в файле конфигурации системы config.yml
. При вставке в спейс новой записи
будет вызываться указанная функция, и результат ее работы будет записан как
значение для данного поля;
auto_increment
— позволяет сделать поле автоинкрементным
(auto-incremental). Может использоваться для задания числового
идентификатора, который будет уникальным для сущностей данного типа, даже при
шардировании базы данных.
Атрибут является флагом; значение true
включает автоинкремент.
Пример:
{
"name": "Contract",
"type": "record",
"logicalType": "Aggregate",
"fields": [
{"name": "id", "type": "long", "auto_increment": true},
...
],
...
Внутри TDG для представления даты/времени
используется строковый формат ISO 8601.
Этот формат позволяет делать поля даты/времени индексируемыми,
и при этом имеющими правильный порядок сравнения.
Все даты, поступающие в приложение, должны быть приведены к UTC.
Допустимые форматы записи:
дата: YYYY-MM-DDZ
;
время: HH:MM:SSZ
;
дата/время с миллисекундами: YYYY-MM-DDTHH:MM:SS.sssZ
;
дата/время с микросекундами: YYYY-MM-DDTHH:MM:SS.ssssssZ
;
дата/время с наносекундами: YYYY-MM-DDTHH:MM:SS.sssssssssZ
.
Например: 2018-03-24T10:20:48Z
.
Чтобы объявить поле типа Date
, Time
или DateTime
,
используйте механизм логических типов (logicalType
)
Avro Schema. Базовый же тип для этих полей — всегда строковый.
Пример объявления поля даты/времени:
{"name": "timestamp", "type": {"type": "string", "logicalType": "DateTime"}}
1.2. Безопасная среда исполнения
Пользовательский код в TDG исполняется отдельно от собственного кода
TDG — в безопасной изолированной среде, называемой песочницей (или
sandbox). При этом используется JIT-компилятор LuaJIT.
Для доступа к данным, хранящимся в TDG, используйте функции
программного интерфейса репозитория.
Все остальные функции и возможности, которые можно использовать при написании
пользовательского кода обработки в песочнице, описаны в данном разделе.
Все функции можно разделить на следующие категории:
Также имеется возможность подключить к безопасной среде исполнения
новые произвольные функции. Как это сделать, описано в разделе
Подключение новых функций.
Важно
Подключать новые функции к безопасной среде исполнения категорически
запрещено для сертифицированной версии TDG.
Перед подключением новых функций в обычной версии TDG
рекомендуется самостоятельно проверять их на уязвимости.
1.2.1. Стандартные модули и функции
Общедоступные Lua-модули и функции:
assert
— вызывает исключение, если первый аргумент является nil
или false
;
math
— интерфейс стандартной математической библиотеки C;
next
— возвращает следующий элемент таблицы по индексу;
pairs
— позволяет выполнять итерации по парам ключ-значение (key-value) таблицы;
pcall
— защищенный вызов с заданными аргументами (позволяет обработать исключения);
print
— простой вывод в stdout
;
select
— если первый аргумент функции имеет числовое значение, возвращаются все аргументы,
следующие за аргументом с этим номером. Если первый аргумент — строка ’#’,
возвращается общее число полученных аргументов;
string
— модуль для работы со строками;
table
— модуль для работы с таблицами. Расширен двумя собственными функциями
(см. подробнее);
tonumber
— преобразование в число;
tostring
— преобразование в строку;
type
— возвращает тип переданного аргумента в виде строки;
unpack
— возвращает элементы таблицы;
xpcall
— подобна pcall
, но устанавливает новый обработчик ошибок;
error
— вызов исключения.
Модули Tarantool:
ipairs
—
подобно pairs
, но использует числовые ключи;
fun
—
модуль Luafun для Tarantool;
uuid
—
модуль для работы с UUID (Universally unique identifier);
digest
—
модуль кодирования и хэширования;
utf8
—
модуль для работы со строками в кодировке UTF-8;
decimal
—
модуль для точных вычислений с числами.
А также отдельные функции из модулей Tarantool:
2 функции из модуля json>
:
1 функция из модуля yaml
:
1 функция из модуля fiber
:
1 функция из модуля box
:
3 функции из модуля metrics
:
metrics.counter
— монотонно возрастающий счетчик;
metrics.gauge
— метрика для числовых значений;
metrics.histogram
— метрика для оценки интенсивности потока во времени.
Также доступны нестандартные функции собственной разработки, описание которых
приведено далее.
1.2.2. Функции доступа к данным
Основные функции для доступа к данным в TDG входят в
программный интерфейс репозитория.
Кроме них, также доступны:
model_accessor.find(type_name, filter, options, context)
— функция,
возвращающая объекты, соответствующие заданным условиям. По умолчанию
возвращаются первые 10 результатов. Пагинация осуществляется аналогично
операциям
программного интерфейса репозитория. Не рекомендуется к использованию —
используйте более высокоуровневую функцию find
из программного интерфейса репозитория;
shared_storage.new(namespace)
— создаёт новое общее хранилище;
connector.send(output_name, obj, output_options)
— направляет объект в секцию
output
для отправки в смежную систему;
2 функции ODBC:
odbc.execute(connection_name, statement, params)
— выполнение запроса
через ODBC;
odbc.prepare(connection_name, query)
— подготовка запроса через ODBC.
Возвращает объект подготовленного запроса.
1.2.3. Функции управления обработкой
commands
— позволяет выполнить произвольные команды, в том числе модифицируя
объект или порядок его обработки. Для этого достаточно вернуть из клиентского
кода не объект, а соответствующую команду или команды. В случае, если команд
несколько, необходимо вернуть таблицу, содержащую все команды. Всего возможны
четыре варианта команд:
commands.make_delete(routing_key, filter, options)
— удаляет объекты,
соответствующие условиям фильтра. См.
Синтаксис поля filter;
commands.make_update(routing_key, filter, updaters, options)
— обновляет
данные объектов, соответствующих условиям. См. синтаксис поля updaters
в
разделе про Запрос на обновление данных;
commands.make_insert(obj)
— указанный объект возвращается в конвейер
обработки данных (pipeline). Передав несколько команд, можно вернуть несколько
объектов;
request_context.get — возвращает контекст запроса.
this_storage.snapshot — аналог функции box.snapshot
.
get_function — возвращает ссылку на функцию по её имени, если она доступна в
песочнице.
spawn(pipeline, func_name, args, options) — запускает один или несколько
файберов
для выполнения функции func_name
. Количество запускаемых файберов
определяется количеством args
;
spawn_n(pipeline, func_name, func_num, options)
— запускает func_num
количество файберов для выполнения функции func_name
без аргументов.
1.2.4. Функции преобразования данных
lom.get_by_path (lom, path)
— функция возвращает объект из Lua-таблицы в нотации
Lua Object Model.
Выполняется разделение path
на отдельные части с разделителем в виде точки.
Затем в переданном lom
выполняется поиск объекта с полем tag
, равным первой
части path
. Далее операция поиска повторяется для каждой части path
в результатах поиска предыдущего этапа.
3 функции для работы со значениями внутри объектов:
mapping_tools.get_by_path(obj, path, delimeter)
— получение значения из
объекта по пути;
mapping_tools.set_by_path(obj, path, value, delimeter)
— задание значения
в объекте по пути;
mapping_tools.just_get_and_set(source_object, source_path, target_object,
target_path)
— получить значение из одного объекта и записать в другой;
2 функции, расширяющие модуль table
:
table.cmpdeeply(got, expected)
— глубокое сравнение двух таблиц.
Возвращает результат сравнения, истину (true) или ложь (false);
table.append_table(where, from)
— добавление одной таблицы к другой. При
этом используется неглубокое (shallow) копирование.
2 функции модуля soap
, который позволяет преобразовывать SOAP-запрос в формате XML в объекты Lua и обратно:
soap.decode(doc)
— получает на вход строку, содержащую XML-документ. Возвращает объекты Lua, полученные в результате парсинга XML:
строка, содержащая указатель на пространство имен (namespace
);
строка с именем метода, переданного в SOAP-запросе (method
);
Lua-таблица, которая содержит значения, переданные в тэгах SOAP-запроса (entries
);
soap.encode(data)
— получает на вход Lua-таблицу. Возвращает строку, содержащую XML-документ.
1.2.5. Функции логирования
1.2.6. Функции работы с датами и временем
1.2.7. Функции работы с последовательностями
sequence
— генератор уникальных упорядоченных целых чисел.
Уникальность чисел гарантируется в пределах отдельной последовательности с
заданным именем, даже при вызове из разных файберов
или на разных экземплярах. Для обеспечения уникальности при вызовах из разных
экземпляров используется роль sequence_generator
, которая выделяет доступные
диапазоны чисел. При первом обращении или при исчерпании выданного ранее диапазона
происходит выделение нового незанятого ранее диапазона уникальных чисел.
Примечание
По умолчанию файберам или экземплярам выделяются диапазоны по 10 номеров.
Пример использования: если на двух разных экземплярах вызвать 1 раз пайплайн,
который заполняет поле объекта уникальным номером с помощью метода next
,
то номера у объектов будут 1 и 11 соответственно. Если вызвать еще по 9 раз
пайплайны, то номера объектов будут 2-10 и 12-20 на соответствующих экземплярах.
Дальше, если запустить пайплайн еще раз на первом экземпляре, то будет получен
номер 21. Если мы продолжим вызывать пайплайн на одном из экземпляров, то номера
будут идти по порядку (22, 23, …).
1.2.8. Подключение новых функций
Важно
Подключать новые функции к безопасной среде исполнения категорически
запрещено для сертифицированной версии TDG.
Перед подключением новых функций в обычной версии TDG
рекомендуется самостоятельно проверять их на уязвимости.
Для подключения к безопасной среде исполнения (песочнице) новых функций
необходимо выполнить следующие действия:
1.2.8.1. Создание модуля с подключаемыми функциями
Новые подключаемые функции необходимо разместить в специальном файле
модуля. Данный файл должен иметь специальный формат, продемонстрированный в
примере далее.
local exported_function = nil
local check_custom_function = os.getenv('CUSTOM_FUNCTION')
if check_custom_function ~= nil and check_custom_function ~= "" then
local function custom_function()
local magic_constant = 42
return magic_constant
end
exported_function = custom_function
end
return {
name = 'new_awesome_function',
exports = exported_function
}
В примере продемонстрированы следующие возможности:
реализация произвольной логики проверок перед подключением новой функции;
задание логики функции непосредственно в файле модуля;
задание произвольного имени для вызова функции внутри песочницы
(new_awesome_function
).
Из файла должен возвращаться результат в виде имени и кода новой функции.
Имя новой функции, подключаемой к безопасной среде исполнения, указывается в виде
строки name
.
Код новой функции приводится в поле exports
в одном из следующих форматов:
Ссылка на функцию, описанную в этом же файле (как в примере выше);
Ссылка на подключенный при помощи директивы require()
модуль Tarantool.
Примечание
Код модуля с подключаемыми функциями выполняется за пределами песочницы.
Поэтому в коде модуля нельзя воспользоваться функциями программного интерфейса репозитория
и функциями, доступными из песочницы (описаны в данной главе выше).
1.2.8.2. Размещение файла модуля в файловой структуре
Возможны два варианта размещения файла модуля с подключаемыми функциями.
Оба варианта равноценны с функциональной точки зрения.
Для того, чтобы новая функция была доступна в песочнице, необходимо
разместить файл модуля подключаемой функции в специальной директории.
Возможно, эту директорию потребуется создать, если она не существовала ранее.
Также нужно проследить, чтобы у пользователей были необходимые права для чтения данной директории.
Файл размещается по адресу ../extensions/sandbox
, где ..
— это
директория, в которой размещена директория с файлами TDG,
обозначенная на следующей схеме как tdg
.
.
├── tdg
├── extensions
└── sandbox
└── custom_function.lua <-- файл модуля с подключаемыми функциями
Альтернативно, файл модуля можно поместить в архив .zip
, посредством которого загружается конфигурация TDG.
В архиве файл модуля необходимо разместить по такому же пути — ../extensions/sandbox
.
1.2.8.3. Применение конфигурации
После создания файла модуля с новой функцией для безопасной среды исполнения
и размещения данного файла в корректной директории, новая функция еще не будет
доступна. Считывание файлов из данной директории выполняется на этапе применения
конфигурации экземпляра.
Поэтому для добавления функции понадобится вызвать процесс применения
конфигурации. Это можно сделать разными способами, например:
Примечание
При применении к кластеру TDG новой конфигурации она проходит
валидацию.
Попытка применения той же самой конфигурации, что уже используется,
закончится неудачей.
При этом, если конфигурация является валидной (корректной), то в ответ на
запрос не будет выведено ошибок. Сообщение о том, что конфигурация совпадает
с имеющейся и не будет применена, будет доступно только в системном журнале
(на вкладке logger данное сообщение будет отсутствовать).
1.3. Программный интерфейс репозитория
Пользовательский код в TDG исполняется в изолированной среде — так называемой
«песочнице» (sandbox). При этом для доступа к данным должны использоваться
функции программного интерфейса репозитория (repository API).
Все запросы к данным в TDG от внешних информационных систем, включая
GraphQL-запросы (а также запросы, выполняемые при помощи вкладки GraphQL),
также реализованы на основе функций программного интерфейса репозитория.
Основные функции программного интерфейса репозитория включают в себя выборки разной
сложности, изменение и обработку данных, а также добавление отложенных работ:
Синтаксис функций:
repository.find(type_name, filter, options, context)
repository.get(type_name, index_name, value, options, context)
repository.put(type_name, object, options, context)
repository.update(type_name, filter, updaters, options, context)
repository.delete(type_name, filter, options, context)
repository.call_on_storage(type_name, index_name, value, func_name, func_args, options, context)
repository.push_job(name, arguments, options, context)
repository.map_reduce(type_name, filter, version, map_fn, combine_fn, reduce_fn, opts)
Функции get
, find
, update
, delete
и map-reduce
поддерживают порядковую нумерацию страниц (пагинацию) с помощью параметров first
и after
.
Чтобы фильтровать объекты, запросы используют условия-предикаты (filter
) — булевы выражения,
синтаксис которых описан ниже.
1.3.1. Синтаксис предикатов
В запросах предикаты (filter
) записываются в виде:
{{left, comparator, right}, ...}
где:
left
и right
— это левая и правая части выражения.
Правая часть выражения (right
) может содержать:
либо полный путь к полю объекта ($foo.bar
), где имя поля начинается
со знака $
;
либо строковое или численное значение.
Левая часть (left
) может содержать только полный путь к полю.
comparator
— оператор сравнения: ">"
, ">="
, "=="
,
"<="
или "<"
.
Если в предикате несколько условий, по умолчанию они объединяются
логической операцией and
(конъюнкцией).
Примеры предикатов:
{{"$id", ">", 10}}
{{"$id", ">", 10}, {"$id", "<", 100}}
{{"$name", "==", "foo"}, {"$birth_year", "==", 1990}}
{{"$name", "==", "foo"}, {"$reg_date", "==", {1990, 04, 23}}}
1.3.2. Общее хранилище
Для передачи объектов между функциями и экземплярами TDG используйте
общее хранилище (shared storage). При помощи следующей команды подключите существующее
или создайте новое (если хранилище с таким именем ещё не существует) общее хранилище:
local shared_storage_object = shared_storage.new('some_namespace')
Переменная shared_storage_object
в данном примере содержит указатель на созданное
общее хранилище.
Примечание
При создании общего хранилища создаётся спейс,
который хранится на одном из наборов реплик с ролью storage
. Однако персистентность
данных в общем хранилище не гарантируется — данные из него могут быть потеряны,
например, при перезапуске кластера.
Данные в общее хранилище помещаются в формате key, value
, например, следующей
командой:
shared_storage_object:set('abc', 123)
где 'abc'
— это ключ (key),
а 123
— значение (value).
Для получения данных из общего хранилища выполните следующую команду:
shared_storage_object:get('abc')
1.3.3. Исторические данные
Запросы позволяют получить или обработать объекты, которые предшествуют или равны
определённой версии. За это отвечает параметр version
в options
.
Примечание
Пример: Допустим, существуют версии 1, 3 и 5 объекта, а мы запросили версию 4.
В таком случае, мы получим версию либо равную запрошенной, либо ближайшую
предшествующую, в данном случае версию 3.
При получении данных параметр version
определяет версию получаемого объекта.
Если параметр version
не задан, то будет получена последняя версия объекта.
При обновлении данных параметр version
определяет изменяемую версию объекта.
Если задано значение параметра version
, то запрос получит указанную версию объекта
(или ближайшую предшествующую, см. пример выше), выполнит обновление и сохранит
результат с той же версией.
Если параметр version
не задан, то запрос получит последнюю версию объекта,
обновит её и сохранит с новой версией, значение которой будет взято по умолчанию
(большое целое монотонно возрастающее число).
При вставке (добавлении или замене) объекта параметр version
определяет
версию объекта, который будет добавлен или заменён.
Если параметр version
не задан, то используется значение по умолчанию (см. выше).
При удалении данных параметр version
определяет удаляемую версию объекта.
Если параметр version
не задан, то будет удалена последняя версия объекта.
Для запросов на выборку или удаление объектов (find
или delete
), при
установленном флаге all_versions
(значение true
) обработка выполняется
для всех версий объекта. Если задан параметр version
, то обрабатываются все
версии меньше или равные заданной.
1.3.4. Оптимистичные блокировки
В случае конкурентной модификации объекта, когда изменения в один объект могут вноситься
параллельно из разных обработчиков, что может вызывать конфликты, необходимо использовать
механизм оптимистичных блокировок, основанный на версиях объектов.
Для этого в options
запроса задайте значение параметра only_if_version
, и запрос выполнится
только в случае, если данная версия объекта является последней и совпадает с
заданной в запросе.
Пример:
repository.put("users", {id = "100500", name = "Kirito777"}, {version = 6, only_if_version = 5})
Этот запрос попытается выполнить поиск по первичному ключу (в данном случае — id
).
Если запись с таким значением первичного ключа будет найдена, то она будет
обновлена только в том случае, если версия объекта в системе на момент исполнения
совпадёт со значением, переданным в параметре only_if_version
(в данном примере
это 5
).
После успешного исполнения запроса последняя версия объекта станет равна 6
.
Если запись с переданным значением первичного ключа отсутствует, то новая запись
добавлена не будет, так как версия несуществующей записи не может быть равна 5
.
При вставке новых объектов можно использовать параметр if_not_exists
, который также задается в options
запроса.
Параметр имеет тип boolean
(значения true
или false
).
Может использоваться только с функцией repository.put()
.
Если задано значение true
, система проверит, существует ли уже такой объект,
и только при условии его отсутствия новый объект будет добавлен в хранилище.
Если параметр не задан, это равносильно значению false
.
Пример:
repository.put("users", {id = "100500", name = "Kirito777"}, if_not_exists = true)
Примечание
Параметры only_if_version
и if_not_exists
взаимоисключающие и не могут использоваться вместе в одном запросе.
В этом случае система выдаст ошибку запроса.
1.3.5. Функции программного интерфейса репозитория
1.3.5.1. Выборка по полям объектов одного типа (find)
Синтаксис
repository.find(type_name, filter, options, context)
где:
type_name
— тип объекта;
filter
— список условий-предикатов для выбора
(фильтрации) объектов указанного типа;
options
— параметры для управления запросом:
first
— количество элементов;
after
— курсор пагинации на первый элемент;
version
— версия объекта;
all_versions
— указатель для поиска по всем версиям объекта, если задано значение true
;
mode
— определяет целевой экземпляр для выполнения запроса. Возможные значения: read
и write
.
Если задано write
, целью будет мастер;
prefer_replica
— определяет целевой экземпляр для выполнения запроса.
Возможные значения: true
и false
. Если задано true
, то предпочитаемая цель — одна из реплик.
Если доступной реплики нет, то целью будет мастер.
Опция полезна для ресурсозатратных функций, чтобы избежать замедления работы мастера;
balance
— управление балансировкой нагрузки.
Возможные значения: true
и false
. Если задано true
, добавится балансировка нагрузки
— запросы на чтение распределяются по всем узлам набора реплик по кругу.
Если при этом параметр prefer_replica
определен как true
, предпочтение отдается репликам;
context
— контекст выполнения запроса:
Пример
repository.find(
"Client",
{{"$id", "==", 42}}
)
1.3.5.2. Выборка по индексу (get)
Синтаксис
repository.get(type_name, index_name, value, options, context)
где:
type_name
— тип объекта;
index_name
— имя индекса;
value
— значение ключа поиска;
options
— параметры для управления запросом:
first
— количество элементов;
after
— курсор пагинации на первый элемент;
version
— версия объекта;
all_versions
— указатель для поиска по всем версиям объекта, если задано значение true
;
mode
— определяет целевой экземпляр для выполнения запроса.
Возможные значения: read
и write
. Если задано write
, целью будет мастер;
prefer_replica
— определяет целевой экземпляр для выполнения запроса.
Возможные значения: true
и false
. Если задано true
, то предпочитаемая цель — одна из реплик.
Если доступной реплики нет, то целью будет мастер. Опция полезна для ресурсозатратных функций, чтобы избежать замедления работы мастера;
balance
— управление балансировкой нагрузки.
Возможные значения: true
и false
.
Если задано true
, добавится балансировка нагрузки — запросы на чтение распределяются по всем узлам набора реплик по кругу.
Если при этом параметр prefer_replica
определен как true
, предпочтение отдается репликам;
context
— контекст выполнения запроса:
Пример
repository.get("Client", "id", 42)
1.3.5.3. Запрос на добавление данных (put)
Запрос put
позволяет вставить новые данные или заменить существующие.
Он поддерживает версионирование
и оптимистичные блокировки.
Если отсутствует параметр version
, то используется значение по умолчанию
(большое целое монотонно возрастающее число).
При заданном параметре version
запрос выполняет добавление или замену
объекта с заданной версией (обычно используется целое число).
При указанном параметре only_if_version
запрос добавляет или заменяет объект
только в том случае, если последняя версия объекта совпадает с указанной.
Синтаксис
Запрос на вставку объекта выглядит следующим образом:
repository.put(type_name, object, options, context)
где:
type_name
— тип объекта;
object
— объект для вставки;
options
— параметры для управления запросом:
context
— контекст выполнения запроса:
Пример
repository.put("users", {id = "100500", name = "Kirito777"})
Данный запрос из примера добавит новый объект с первичным ключом id
, равным 100500,
если таковой ещё не существует. При этом значение параметра version
будет равным
значению по умолчанию (большое целое монотонно возрастающее число). Если объект
с таким первичным ключом уже есть, то будет добавлена новая версия объекта со
значением параметра version
, равным значению по умолчанию.
1.3.5.4. Запрос на обновление данных (update)
Запрос на обновление данных (update
) поддерживает версионирование
и выполняется в две стадии:
исполнение запроса на каждом хранилище, которое может содержать объекты по условию,
передаваемому аргументом filter
;
сбор результатов на роутере.
Синтаксис
Запрос на обновление объекта выглядит следующим образом:
repository.update(type_name, filter, updaters, options, context)
где:
type_name
— тип объекта;
filter
— список условий-предикатов для выбора
(фильтрации) объектов указанного типа;
updaters
— список обновлений для объекта, состоящий
из списка мутаторов {{mutator, path, new_value}, ...}
, где:
mutator
— имя мутатора, например:
set
— устанавливает значение;
add
— увеличивает значение на указанное число;
sub
— уменьшает значение на указанное число;
path
— строковый путь до поля объекта с точкой-разделителем (.
).
Путь до объекта(ов) массива должен включать индекс массива или
символ *
для захвата всех подобъектов;
new_value`
— новое значение;
options
— параметры для управления запросом:
first
— количество элементов для обновления;
after
— курсор пагинации на первый элемент;
version
— версия объекта;
only_if_version
— проверка имеющейся версии перед вставкой;
dont_skip_deleted
— использовать в поиске удаленные версии объектов;
context
— контекст выполнения запроса:
Примеры
Для модели, рассмотренной в примере:
Чтобы обновить имя клиента с идентификатором 42, используйте
следующий запрос:
repository.update(
"Client",
{{"$id", "==", 42}},
{{"set", "first_name", "John"},
{"set", "last_name", "Doe"}})
Если у того же клиента истек срок действия первого паспорта и необходимо обновить
соответствующее поле expired_flag
, используйте следующий запрос:
repository.update(
"Client",
{{"$id", "==", 42}},
{{"set", "passports.1.expired_flag", "true"}}
где .1.
— индекс массива, содержащего экземпляры сущности
Passport
агрегата Client
, т.е. первый паспорт клиента.
1.3.5.5. Запрос на удаление объектов (delete)
Чтобы прозрачно и удобно исключить объект из потенциальных результатов запросов,
используйте запрос на удаление (delete
). Он помещает объект или его версию в
«удаленное состояние» (добавляя флаг delete:true
), что исключает его из выборки.
Запрос на удаление поддерживает версионирование
и оптимистичные блокировки.
Синтаксис
Чтобы удалить объект, используйте следующий запрос:
repository.delete(type_name, filter, options, context)
где:
type_name
— имя типа объекта для удаления;
filter
— список условий-предикатов для выбора
(фильтрации) объектов указанного типа;
options
— параметры для управления запросом:
first
— количество элементов для удаления;
after
— курсор пагинации на первый элемент;
version
— версия объекта для копирования в удаленное состояние;
only_if_version
— проверка имеющейся версии перед вставкой;
all-versions
— этот флаг для функции удаления (delete
) нельзя использовать
без флага permanent_delete
;
permanent_delete
— при установленном флаге (значение True
) происходит
физическое удаление информации об объекте из TDG. Используется вместе
с флагом all-versions
;
context
— контекст выполнения запроса:
Пример
Чтобы удалить клиентов с именем «QWERTY» для модели из
примера, используйте следующий запрос:
repository.delete(
"Client",
{{"$first_name", "==", "QWERTY"}})
В результате такого запроса все объекты, удовлетворяющие условию (first name
равное QWERTY
), будут дополнены новой версией (значение версии по умолчанию)
с флагом delete:true
.
1.3.5.6. Вызов функции на экземпляре с ролью storage (call_on_storage)
Синтаксис
repository.call_on_storage(type_name, index_name, value, func_name, func_args, options, context)
где:
type_name
— тип объекта;
index_name
— имя индекса;
value
— значение ключа поиска;
func_name
— имя вызываемой функции;
func_args
— аргументы, которые передаются в вызываемую функцию;
options
— параметры для управления запросом:
timeout
— время ожидания выполнения запроса, секунды.
Значение не должно быть больше, чем значение параметра конфигурации vshard_timeout;
mode
— определяет целевой экземпляр для выполнения запроса.
Возможные значения: read
и write
. Если задано write
, целью будет мастер;
prefer_replica
— определяет целевой экземпляр для выполнения запроса.
Возможные значения: true
и false
. Если задано true
, то предпочитаемая цель — одна из реплик.
Если доступной реплики нет, то целью будет мастер.
Опция полезна для ресурсозатратных функций, чтобы избежать замедления работы мастера;
balance
— управление балансировкой нагрузки.
Возможные значения: true
и false
.
Если задано true
, добавится балансировка нагрузки — запросы на чтение распределяются
по всем узлам набора реплик по кругу. Если при этом параметр prefer_replica
определен как true
, предпочтение отдается репликам;
context
— контекст выполнения запроса:
1.3.5.7. Асинхронный запуск задач/функций (push_job)
Синтаксис
repository.push_job(name, arguments, options, context)
где:
name
— имя функции/пайплайна, определенное в файле конфигурации;
arguments
— аргументы функции;
options
— параметры для управления запросом (не используется);
context
— контекст выполнения запроса (не используется).
1.4. Запрос на сложную обработку кластером
Если требуется выполнить обработку всех данных в кластере, то её необходимо
выполнять на всех экземплярах типа storage
. Для этого можно использовать
функцию map_reduce
.
Сложная кластерная обработка по модели MapReduce в TDG состоит из трех этапов:
Map — выполняется на экземплярах с ролью storage
;
Combine — выполняется на экземплярах с ролью storage
;
Reduce — выполняется на экземпляре, где была вызвана функция map_reduce
.
1.4.1. Синтаксис запроса
Функция
map_reduce(type_name, filter, version, map_fn, combine_fn, reduce_fn, opts)
имеет следующие аргументы:
type_name — имя агрегата;
filter — булевы выражения / предикаты;
version — номер версии;
map_fn — указатель на функцию для этапа Map;
combine_fn — указатель на функцию для этапа Combine;
reduce_fn — указатель на функцию для этапа Reduce;
opts — необязательные аргументы:
opts.map_args — дополнительные аргументы для этапа Map;
opts.combine_args — дополнительные аргументы для этапа Combine;
opts.combine_initial_state — исходное состояние для этапа Combine;
opts.reduce_args — дополнительные аргументы для этапа Reduce;
opts.reduce_initial_state — исходное состояние для этапа Reduce.
1.4.2. Этап Map
Сначала на каждом экземпляре c ролью storage
будет вызвана функция map_fn
,
столько раз, сколько на этом экземпляре будет найдено подходящих объектов, соответствующих типу
type_name
и условиям filter
.
Функция map_fn
применяется к каждому найденному кортежу.
local map_res, err = map_fn(tuple, opts.map_args)
Эта функция может вернуть любые данные или nil.
Если функция вернет данные, то на следующем этапе эти данные будут использованы
для запуска combine_fn
. combine_fn
будет вызвана столько раз, сколько раз
вызовы map
вернут данные.
1.4.3. Этап Combine
Эта стадия опциональна и нужна для того, чтобы по возможности уменьшить
количество данных, полученных на стадии Map, перед их передачей по сети и
дальнейшей обработкой.
Результат каждого вызова map_fn
передаётся в combine_fn
.
Каждый вызов combine_fn
осуществляется с передачей исходного состояния,
равного результату предыдущего вызова combine_fn
. Для первого вызова исходное
состояние равно opts.combine_initial_state
, либо равно пустой таблице, если этот параметр не задан.
local combine_res = opts.combine_initial_state or {}
local combine_res, err = combine_fn(combine_res, map_res, opts.combine_args)
Функция combine_fn
также выполняется на роли storage
.
Результат выполнения combine_fn
аккумулируется в переменной combine_res
и передается на ту роль, откуда был вызван map_reduce
— в функцию reduce_fn
.
1.4.4. Этап Reduce
Данный этап предназначен для завершения обработки данных со всего кластера.
Данные передаются по сети на экземпляр, откуда была вызвана функция map_reduce
.
Там будет вызвана функция reduce_fn
. Эта функция будет вызвана столько
раз, сколько экземпляров типа storage
в кластере найдет подходящие объекты и,
соответственно, вернет результат в combine_res
.
local reduce_res, err = reduce_fn(combine_res, opts.reduce_initial_state, opts.reduce_args)
Результат reduce_fn
возвращается как результат всего map_reduce
.
1.5. Представление модели
При эксплуатации TDG объекты могут поступить в систему постфактум,
а запросы к системе могут быть сделаны по функциональному срезу для определенной
бизнес-даты.
Поэтому, чтобы поддерживать обратную совместимость при разработке приложения
для TDG, нужно версионировать всё, что непосредственно влияет на логику его
работы:
Avro-схему модели (единый формат);
конвейеры обработки входящих объектов;
код Lua-функций, составляющих конвейеры и методы объектов модели;
(потенциально) фабрики объектов;
(потенциально) триггеры;
(потенциально) описание сервисов.
Чтобы поддерживать историчность и эволюцию модели,
TDG представляет ее в едином формате — Lua-структуре, состоящей
из примитивных типов. Ее легко преобразовать в JSON и обратно для передачи по сети.
Чтобы версионировать собственную структуру, система использует следующие форматы
конфигурации для разных целей:
1.5.1. Внутренний формат конфигурации
Узлы кластера передают конфигурацию по сети и хранят ее в следующем формате:
{
"functions": {
"function_name": "function_code"
...
},
"pipelines": {
"pipeline_name": ["function1_name", "function2_name", ...],
...
},
"classifiers": {
"classifier_name": "pipeline_name/function_name",
...
},
"routing": {
"routing_key": "pipeline_name/function_name",
...
},
"storage": {
"routing_key": "type_name"
},
"schema": <avro_schema>
}
где:
functions
— набор функций, которые используются в классификаторах, маршрутизаторах и объектах модели;
pipelines
— конвейеры, в которых функции применяются к объекту по цепочке;
classifiers
— позволяют получить ключ маршрутизации для объекта;
routing
— функции, которые позволяют менять ключ маршрутизации объекта;
storage
— хранилище для объектов с соответствующими ключами маршрутизации;
function_name
— имя (идентификатор) Lua-функции;
function_code
— код Lua-функции (исполняемый в «песочнице»);
pipeline_name
— имя конвейера;
classifier_name
— имя классификатора;
routing_key
— строковый ключ маршрутизации.
Также в формате присутствует поле schema
, формат которого описан в разделе
о доменной модели. В этом поле хранится структурное
(а не строковое) представление доменной модели.
1.5.2. Внешний (экспортный) формат
Экспортный формат используется для загрузки начальной модели при первичном
старте системы, а также для загрузки конфигурации в тестах.
Экспортный формат соответствует внутреннему, за исключением следующих полей:
Вместо function_code
— function_file_name
, файл, в котором находится
код Lua-функции;
Вместо avro_schema
— avro_file_name
, файл, в котором сериализована
Avro-схема модели.
Корневой объект сохраняется в файле config.yml
, код функций — рядом, в соответствующих
файлах с расширением .lua
, а схема — в файле schema.avsc
.
1.6. Разработка тестов
Тестирование приложений для системы TDG преследует две основные цели:
Юнит-тесты обычно синтетические и предназначены для
исчерпывающего покрытия критических частей кода и последующей автоматической
проверки на регрессии. Самая большая польза от юнит-тестов —
выявление ситуаций, в которых модуль может получить разнообразный
неверный «ввод» от пользователя. В таких случаях юнит-тесты помогают
убедиться, что модуль реагирует на неверный ввод корректно.
Если система приняла неверные по форме/содержанию данные или, наоборот,
не приняла верные данные (посчитав их неверными), то юнит-тесты —
идеальное средство проверки подобных сценариев.
Интеграционные тесты предназначены для проверки
функциональности сложных элементов и для проверки на соответствие требованиям
заказчика. Если поступает новое требование в формате пользовательской истории,
то оно обязательно должно быть проверено интеграционным тестом, в котором
должна быть сделана ссылка на требование.
Прежде чем разрабатывать собственные тесты, ознакомьтесь с существующими и
запустите их.
1.6.1. Запуск тестов
Чтобы запустить существующие юнит-тесты, в корне проекта:
Установите все зависимости при помощи скрипта:
Он установит в папку .rocks
все модули, нужные для запуска.
Запустите юнит-тесты:
Команда выполнит по очереди все юнит-тесты из папки test/unit
.
Чтобы запустить существующие интеграционные тесты:
Установите python3, pip и несколько python-библиотек. Весь список зависимостей
приведен в файле requirements.txt
в корне проекта. Чтобы установить их все
глобально, используйте pip. Например:
sudo pip3 install -r requirements.txt
Примечание
Как альтернативу можно настроить pipenv и
сделать для прогона тестов отдельную среду.
Запустите интеграционные тесты:
1.6.2. Разработка юнит-тестов
Юнит-тесты сгруппированы по подсистемам в подпапках test/unit
.
Чтобы тест был включен в общий прогон, имя файла должно начинаться с
test_
и иметь расширение .lua
, например, test_ddl.lua
.
Команда tarantool unit.lua
найдет в упомянутой папке файлы с
такими именами и автоматически исполнит их.
Юнит-тесты используют модуль Tarantool tap
. Чтобы написать новый тест, используйте следующий код:
#!/usr/bin/env tarantool
local tap = require('tap')
local test = tap.test("validation")
test:plan(5) --- замените цифру на количество тестов в модуле
--- здесь будут тесты
os.exit(test:check() and 0 or 1)
Чтобы написать сами тесты, достаточно вызывать у объекта test
функции проверки:
test:isnil()
test:isstring()
test:isnumber()
test:istable()
test:isboolean()
test:isudata()
test:iscdata()
Если в эти функции будут переданы данные, не соответствующие
ожидаемым, тест будет прерван с ошибкой и поясняющим сообщением.
Примечание
Чтобы быстро понять, как писать юнит-тесты, лучше всего посмотреть в
реализацию существующих.
Если в тестах требуется использовать box
-функции, используйте
следующий код:
local tarantool = require('test.unit.tarantool').new()
tarantool:start()
--- здесь будут тесты
local success = test:check()
tarantool:stop()
os.exit(success and 0 or 1)
1.6.3. Разработка интеграционных тестов
Интеграционные тесты основаны на pytest
и «заточены» под тестирование логики приложения с возможным подключением
логики кластера.
Для запуска каждого файла с тестами:
Сначала поднимается «с нуля» TDG или кластер из
узлов с разными ролями.
Затем ко всем возможным узлам применяется соответствующая конфигурация.
Наконец, по очереди запускаются тест-кейсы.
Интеграционные тесты находятся в подпапках test/integration
.
В отличие от юнит-тестов, они сгруппированы не по подсистеме, а по
логической «близости» тестов друг к другу. Основной критерий группировки
— тесты в одной подпапке имеют одну и ту же стартовую конфигурацию.
Чтобы создать новую группу тестов:
Добавьте папку в test/integration
и в ней сделайте
подпапки config
и data
.
В config
поместите все конфигурационные файлы для TDG
и connector
. При исполнении этой группы тестов, такие файлы будут
автоматически загружены в качестве начальной конфигурации.
Дополнительно к папке config
можно создать рядом папку data
и положить
туда тестовые данные, например, большие XML или JSON-объекты. Это поможет не
прописывать данные в коде теста в явном виде.
Создайте файл с именем, начинающимся с test_
и расширением .py
.
Такие файлы автоматически включаются в прогон.
В файле используйте следующий код:
#!/usr/bin/env python3
import pytest
import os
def test_something(server, datadir):
# тут можно писать логику теста
assert something == something_else
# тут можно добавить еще тестов
Здесь функция-тест test_something
имеет параметры server
и datadir
.
Эти параметры называются «фикстуры», и они должны быть у каждой функции-теста.
Фикстуры — это способ удобно использовать в тестах библиотечную
функциональность. О них можно прочитать в
документации по pytest.
При указании в параметрах datadir
, можно получить полный путь
к папке, в которой лежат тестовые данные текущей группы тестов.
Когда pytest видит в параметрах тестовой функции server
:
Он автоматически стартует TDG.
Применяет к нему конфигурацию.
Передает объект-обертку («враппер»), позволяющую
удобно делать запросы (HTTP, SOAP, GraphQL и другие) к TDG.
1.6.3.1. API объекта-обертки
Объект-обертка сервера («враппер») поддерживает запросы через
HTTP, SOAP и GraphQL:
server.post(path, data, json)
— посылает post
-запрос по пути path
.
Можно указать либо строку data
для текстовых запросов, либо json
для
JSON-запросов;
server.soap(data)
— послает SOAP-запрос, где data
— текст запроса;
server.graphql(query)
— послает GraphQL-запрос, где query
— текст
запроса. В ответ функция либо бросает исключение при ошибке, либо возвращает
объект с результатом. graphql для TDG разделён на схемы. Для доступа к
данным schema = 'default'
или не указывается. Для доступа к функциям
администрирования schema = 'admin'
. Например, запрос данных:
server.graphql("""
query {
User(country:"USA") {
fullname
}
}
""")
Пример вызова администрирования TDG, изменение модели:
obj = server.post('/graphql', json={
"query": "mutation set_model($model:String!) { model(model: $model) }",
"variables": {
"model": json.dumps(model)
},
"schema": "admin"
})
server.cluster_graphql(query)
— посылает GraphQL-запрос для управления
кластером. Например,
obj = server.cluster_graphql("""
{
servers {
uri
replicaset { roles }
}
}
""")
2. Руководство по эксплуатации
Данное руководство описывает работу с системой Tarantool Data Grid (TDG).
с точки зрения администратора.
Информация в руководстве сгруппирована по основным задачам,
которые выполняет администратор:
установка системы;
настройка кластера;
администрирование кластера;
управление пользователями, настройка прав и уведомлений;
управление бизнес-объектами;
администрирование ремонтной очереди;
администрирование системных задач;
работа с логами;
сбор и настройка метрик;
резервное копирование данных;
тестирование и отладка.
Задачи по администрированию могут выполняться с помощью различных средств
(что также указано в соответствующих разделах руководства):
через web-интерфейс администратора;
другими средствами/утилитами системы TDG через консоль;
через настройку конфигурационных файлов;
средствами операционной системы.
2.1. Установка TDG
В этом разделе рассмотрена процедура установки TDG версии 1.5.0
или выше.
Программный комплекс TDG поддерживает установку на операционные
системы Red Hat Enterprise Linux и CentOS версий 7.5 и выше.
Примечание
TDG может быть запущен на других дистрибутивах Linux, основанных на
systemd
, но не тестируется на них и может не соответствовать заявленной
функциональности.
TDG разработан на основе фреймворка
Tarantool Cartridge,
в котором предусмотрена упаковка создаваемых приложений в дистрибутивы разных
форматов, и, соответственно, различные процедуры установки из этих дистрибутивов.
В данном руководстве приводится один из вариантов установки TDG —
из дистрибутива в формате архива tar.gz
.
Установка выполняется при помощи скрипта tdgctl.py
с локальной машины на удаленные серверы.
Рассмотрим далее:
2.1.1. Подготовка к установке
2.1.1.1. Дистрибутив установки
Для установки вам понадобится дистрибутив TDG в формате
архива tar.gz
.
Запросить доступ к дистрибутивам TDG вы можете, обратившись через
форму обратной связи на сайте Tarantool в разделе Tarantool Data Grid
или по адресу электронной почты sales@tarantool.io.
Для упрощения процедур тестового развертывания в составе дистрибутива находится
директория /deploy
, содержащая:
скрипт для развертывания и управления кластером tdgctl.py
;
примеры конфигураций кластера;
файл README.md
с краткими инструкциями по установке;
файл Vagrantfile
для автоматизации создания тестового окружения для
развертывания кластера;
файл VAGRANT.md
с краткими инструкциями по сборке дистрибутива,
развертыванию виртуального окружения и установке кластера TDG.
2.1.1.2. Предварительная настройка сервера
Основные требования к серверу, на котором будет производиться установка
TDG (если установка выполняется распределенно на несколько серверов,
требования ниже относятся к каждому из них):
Пользователь операционной системы сервера, от имени которого будет происходить
установка, должен иметь привилегии суперпользователя. Также в файле
/etc/sudoers
на сервере нужно отключить для этого пользователя запрос
пароля:
<server_user_name> ALL=(ALL:ALL) NOPASSWD: ALL
На стороне сервера должен быть включен SSH-сервер, который слушает запросы
клиентских соединений на стандартном порте 22
;
На сервер должен быть загружен публичный SSH-ключ пользователя локальной
машины, из-под которого будет запускаться на локальной машине скрипт
tdgctl.py
;
На локальной машине, откуда будет запускаться tdgctl.py
, для
работы скрипта требуется наличие Python 3 и библиотек fabric
и
requests
. Проверить наличие библиотек можно командой:
Если библиотеки отсутствуют, установите их:
pip3 install fabric requests
2.1.1.3. Подготовка конфигурации кластера
Для установки TDG на серверы также потребуется подготовить файл
конфигурации кластера в формате JSON.
Рассмотрим структуру и параметры файла на примере:
{
"general":
{
"cluster_cookie": "ilikerandompasswords"
},
"servers":
[
{
"address": "172.19.0.2",
"username": "vagrant",
"instances":
[
{
"name": "core_1",
"binary_port": 3000,
"http_port": 8080,
"memory_mb": 128,
"env": {"CUSTOM_ENV": "some_value"}
},
{
"name": "runner",
"binary_port": 3001,
"http_port": 8081,
"memory_mb": 128
},
{
"name": "storage_1",
"binary_port": 3002,
"http_port": 8082,
"memory_mb": 1024
},
{
"name": "storage_2",
"binary_port": 3003,
"http_port": 8083,
"memory_mb": 1024
}
]
},
{
"address": "172.19.0.3",
"username": "vagrant",
"instances":
[
{
"name": "core_2",
"binary_port": 3004,
"http_port": 8084,
"memory_mb": 128
},
{
"name": "storage_1_replica",
"binary_port": 3005,
"http_port": 8085,
"memory_mb": 1024
},
{
"name": "storage_2_replica",
"binary_port": 3006,
"http_port": 8086,
"memory_mb": 1024
}
]
}
]
}
Параметры конфигурации:
general
— параметры, относящиеся ко всем экземплярам (инстансам, instances):
servers
— список (массив) серверов, на которые будет происходить установка.
Для каждого сервера задается:
username
— имя пользователя ОС, из-под которого будет происходить установка;
address
— адрес сервера;
instances
— список (массив) устанавливаемых экземпляров. Для каждого
экземпляра задается:
name
— имя экземпляра. При установке на сервера Linux это будет
имя юнита systemd
;
binary_port
— порт, который должен быть открыт на сервере для
взаимодействия экземпляров по бинарному протоколу Tarantool. Требует протоколы
TCP и UDP;
http_port
— порт, который должен быть открыт на сервере для
доступа к экземпляру по HTTP. Требует протокола TCP;
memory_mb
— ограничение потребления памяти Tarantool внутри экземпляра,
мегабайты;
env
— (опционально) переменные окружения, которые будут доступны
для данного экземпляра и могут быть использованы в пользовательском коде.
2.1.2. Установка
Для установки системы используется скрипт tdgctl.py
. Скрипт находится в
дистрибутиве установки в директории /deploy
.
При запуске скрипта нужно указать команду deploy
и передать аргументами путь
к файлу с конфигурацией кластера и файлу дистрибутива установки:
./tdgctl.py -c deploy.json deploy tdg-<version>.tar.gz
где
Подробнее о скрипте tdgctl.py
и формате его команд см. здесь.
Cкрипт подключается к серверам, указанным в конфигурации, загружает на них
дистрибутив, распаковывает исполняемые файлы TDG, создает
systemd-юниты вида <instance_name>.service
для каждого экземпляра, указанного
в конфигурации кластера, и запускает созданные
сервисы.
Запуск сервисов производится от имени системного пользователя nobody
.
Успешность установки можно определить по логу работы скрипта в
консоли. Пример лога успешной установки (в соответствии с конфигурацией кластера
из примера выше):
$ ./tdgctl.py -c deploy.json deploy tdg-1.6.5-10-g9fc081e.tar.gz
Deploying package...
Uploading package...
[####################] 100%
[ ] 0% Created symlink from /etc/systemd/system/core_1 to /etc/systemd/system/core_1.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/core_1.service to /etc/systemd/system/core_1.service.
[## ] 14% Created symlink from /etc/systemd/system/runner to /etc/systemd/system/runner.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/runner.service to /etc/systemd/system/runner.service.
[##### ] 28% Created symlink from /etc/systemd/system/storage_1 to /etc/systemd/system/storage_1.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/storage_1.service to /etc/systemd/system/storage_1.service.
[######## ] 42% Created symlink from /etc/systemd/system/storage_2 to /etc/systemd/system/storage_2.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/storage_2.service to /etc/systemd/system/storage_2.service.
[########### ] 57% Created symlink from /etc/systemd/system/core_2 to /etc/systemd/system/core_2.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/core_2.service to /etc/systemd/system/core_2.service.
[############## ] 71% Created symlink from /etc/systemd/system/storage_1_replica to /etc/systemd/system/storage_1_replica.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/storage_1_replica.service to /etc/systemd/system/storage_1_replica.service.
[################# ] 85% Created symlink from /etc/systemd/system/storage_2_replica to /etc/systemd/system/storage_2_replica.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/storage_2_replica.service to /etc/systemd/system/storage_2_replica.service.
[####################] 100%
7 instances created
172.19.0.2 core_1
172.19.0.2 runner
172.19.0.2 storage_1
172.19.0.2 storage_2
172.19.0.3 core_2
172.19.0.3 storage_1_replica
172.19.0.3 storage_2_replica
0 instances updated
0 instances kept
Done!
Далее необходимо подключиться к web-интерфейсу системы,
убедиться, что все экземпляры запущены, и перейти к следующему этапу —
настройке кластера и запуску системы в работу.
2.1.2.1. Директории по умолчанию
В процессе установки TDG на сервере создаются по умолчанию следующие
директории:
/var/lib/tarantool/<instance_name>
— рабочая директория ({workdir}
)
каждого из экземпляров, в которой хранятся его данные и конфигурация:
файлы снимков данных .snap
и WAL-файлы .xlog
;
поддиректории /config
и /config.backup
, в которых хранятся файлы
конфигурации экземпляра и их резервные копии соответственно;
файл .tarantool.cookie
, содержащий токен кластера;
/usr/share/tarantool/<instance_name>
— директория, в которой хранятся
исполняемые файлы TDG. Количество таких директорий соответствует
количеству развернутых экземпляров.
<instance_name>
— имя экземпляра, определенное в
файле конфигурации кластера.
2.2. Web-интерфейс
Web-интерфейс TDG предоставляет возможности для настройки и
администрирования системы.
2.2.1. Авторизация
Для подключения к web-интерфейсу надо зайти в браузере на любой
сервер по доступному на нем http-порту http://<address>:<http_port>
(указаны в файле конфигурации кластера).
В рассматриваемом в конфигурации примере это может быть http://172.19.0.2:8080
.
При первом подключении к web-интерфейсу после развертывания системы разрешен
неавторизованный анонимный доступ. После включения режима обязательной аутентификации
при подключении к web-интерфейсу пользователь будет попадать на страницу
авторизации (к этому моменту должны быть созданы
профили пользователей).
В форме авторизации необходимо ввести идентификатор пользователя
(Login) и пароль (Password) и нажать Login. Идентификатор
пользователя автоматически генерируется при создании
профиля пользователя (параметр LOGIN
).
При успешной авторизации пользователь попадает на основную страницу web-интерфейса.
Имя пользователя, авторизованного в системе, отображается в web-интерфейсе в
правом верхнем углу. Также там отображается меню системных уведомлений.
При ошибке авторизации система выдает сообщение об ошибке
«Authentication failed»:
2.2.2. Общее описание web-интерфейса
В web-интерфейсе можно выделить следующие основные области:
Панель вкладок — отображает список вкладок для навигации по
функциональным разделам web-интерфейса.
Рабочая область вкладок — отображает содержание активной вкладки.
2.2.2.1. Панель вкладок
В зависимости от роли пользователя набор доступных вкладок
будет разный. Пользователи с ролями «admin» и «supervisor» видят все вкладки.
Пользователю с ролью «user» доступен
ограниченный набор.
Кнопка Collapse menu внизу панели переключает ее отображение в компактный
режим и обратно.
Примечание
Возможность гибкой настройки системы позволяет подключать или отключать
определенные кластерные роли в зависимости от задач, решаемых на проекте.
Если какие-то кластерные роли отключены, соответствующая вкладка тем не менее
будет отображаться на панели вкладок, но функциональные элементы в рабочей
области будут отсутствовать.
Ниже перечислены все вкладки с кратким описанием их назначения и ссылками на
подробное описание в соответствующих разделах документа (подробное описание
вкладки Cluster и меню Settings дано далее в этой главе).
Как упоминалось выше, для пользователя с ролью «user» доступ к определенным
функциям системы ограничен, поэтому от него скрыты следующие вкладки:
Cluster
Configuration files
Model
Audit Log
Settings
2.2.2.2. Вкладка Cluster
Вкладка Cluster отображает текущий статус кластера экземпляров TDG
и дает возможность его администрировать. В интерфейсе можно выделить несколько
групп элементов для управления кластером.
[1] Replica sets
основная статистика по наборам реплик (replica sets): общее количество наборов
реплик в кластере (total) | наборы реплик в статусе «unhealthy» | общее
количество экземпляров (servers);
фильтр узлов кластера по различным критериям: URI, UUID, роль набора реплик,
имя (alias) узла.
[2] Виджет набора реплик, который содержит следующую информацию и
функциональные элементы:
имя и роли набора реплик;
текущий статус:
для набора реплик с ролью «storage» — значения параметров «Vshard group» и
«Replica set weight»;
виждеты экземпляров, входящих в данный набор реплик (см. далее [3]);
кнопка Edit — открывает диалоговое окно, в котором можно редактировать
параметры набора реплик.
[3] Виджет экземпляра:
имя (alias) экземпляра;
URI экземпляра для доступа по бинарному порту (задается в
конфигурации кластера — параметр advertise_uri
);
статус:
индикатор, является ли экземпляр лидером (leader) в наборе реплик;
индикатор используемой памяти (Memory usage): фактически используемая
память / лимит памяти, заданный для данного экземпляра;
если экземпляр входит в набор реплик с ролью «storage» — индикатор количества
виртуальных сегментов (Buckets) на данном экземпляре;
кнопка […] — меню со следующими функциями:
Server details — открывает дополнительную страницу с информацией о параметрах экземпляра;
Disable server — временно отключает экземпляр. Позже его можно снова подключить к кластеру;
Expel server — исключает экземпляр из кластера. См. подробнее.
[4] Кнопки для работы со следующими функциями:
Для каждого экземпляра можно просмотреть детальную информацию о его
параметрах в режиме read-only. Для этого на вкладке Cluster для нужного
экземпляра нажмите … > Server details:
Отображается всплывающее окно, содержащее подробную информацией о параметрах
экземпляра:
Для удобства параметры сгруппированы на дополнительных вкладках:
General — общая информация об экземпляре;
Cartridge — информация о версии фреймворка Tarantool Cartridge;
Replication — параметры репликации;
Storage — параметры базы данных;
Network — параметры, относящиеся к работе с сетью;
Membership — параметры модуля membership
;
Vshard-Router — параметры встроенной роли vshard-router
;
Vshard-Storage — параметры встроенной роли vshard-storage
;
Issues — информация об ошибках, возникающих в работе экземпляра.
Также см. подробнее:
2.2.2.3. Меню Settings
Меню Settings содержит вкладки, предназначенные для управления
настройками системы. Доступ к настройкам имеют пользователи с ролями «admin»
(чтение и изменение) и «supervisor» (только чтение).
2.2.2.4. Системные уведомления
При выполнении операции в пользовательском интерфейсе система выдает
уведомления об успешном выполнении операции или об ошибке.
Список всех уведомлений, выданных системой до настоящего времени, можно
просмотреть, нажав значок уведомлений в правом верхнем углу страницы.
Если в списке есть уведомления, не просмотренные пользователем, значок отмечен
синей точкой.
При нажатии на значок появляется выпадающий список уведомлений.
При необходимости список можно очистить, нажав Clear в самом низу списка.
2.3. Настройка кластера и запуск системы
После успешной установки приложения на серверы необходимо настроить кластер,
что включает в себя следующие действия:
2.3.1. Создание набора реплик и задание ролей
Операции по настройке кластера можно выполнить через web-интерфейс
администратора. Для подключения к web-интерфейсу надо зайти в браузере на любой
сервер по доступному на нем http-порту http://<address>:<http_port>
(указаны в файле конфигурации кластера).
В рассматриваемом примере это может быть http://172.19.0.2:8080
(соответствует экземпляру с именем «core_1»).
На вкладке Cluster мы имеем набор несконфигурированных экземпляров (инстансов, instances), на что
указывает их текущий статус «Unconfigured».
В некоторых подсетях может не работать автоматический поиск доступных
серверов (autodiscovery). Тогда экземпляры этих серверов могут отсутствовать
в таблице Unconfigured servers. Для ручной проверки доступности и добавления
новых экземпляров в кластер можно
использовать функцию Probe server (кнопка над таблицей с экземплярами),
которая проверяет, доступен ли экземпляр по указанному адресу. Если экземпляр
доступен, он появится в таблице.
Далее необходимо сконфигурировать топологию кластера, создав
так называемые наборы реплик (replica sets, репликасеты) и задав их роли.
Выберите в таблице несконфигурированный экземпляр и нажмите кнопку Configure
в правой части строки этого экземпляра. Откроется диалог настройки репликасета
Configure server.
В диалоге необходимо выбрать нужные для данного репликасета роли
(параметр Roles
). Этот параметр обязательный, поэтому должна быть
выбрана как минимум одна роль. Если репликасет выполняет в кластере более одной
роли, то нужно отметить все необходимые роли. Подробнее о ролях репликасетов и
рекомендациях по их назначению см. раздел «Роли».
Остальные параметры репликасета являются опциональными. Тем не менее
рекомендуется задать имя репликасета (Replica set name
), чтобы в
дальнейшем было легче им управлять. У параметров Replica set weight
и
Vhsard group
можно оставить значения, заданные по
умолчанию. После определения нужных параметров нажмите кнопку
Create replica set.
Cозданный репликасет с включенным в него экземпляром отображается в таблице
Replica sets.
Оставшиеся несконфигурированные экземпляры по-прежнему отображаются в таблице
Unconfigured servers.
Продолжим настройку кластера — в нашем примере создадим
4 дополнительных репликасета, каждый из которых содержит по одному экземпляру:
На примере репликасетов с ролью storage
(их в текущей топологии кластера — два)
рассмотрим также операцию по добавлению дополнительных
экземпляров в существующий репликасет.
У одного из оставшихся несконфигурированных
экземпляров — с именем «storage_1_replica» — нажмите Configure и в диалоге
настройки репликасета перейдите на вкладку Join Replica Set. В списке
Choose replica set выберите репликасет с именем «storage_1» и нажмите
Join replica set.
Экземпляр «storage_1_replica» будет включен в состав репликасета «STORAGE_1».
Выполните аналогичную операцию для последнего несконфигурированного экземпляра
«storage_2_replica», включив его в репликасет «STORAGE_2».
На этом настройка топологии кластера завершена.
2.3.2. Запуск кластера
После настройки топологии кластера необходимо
запустить в рабочее состояние репликасеты с
ролью storage
, выполнив инициализацию модуля Tarantool vshard
.
Для этого на вкладке Cluster нажмите кнопку Bootstrap vshard:
Будут созданы виртуальные сегменты для хранения данных (virtual buckets) и
распределены по хранилищам с учетом количества экземпляров с ролью storage
.
2.3.3. Загрузка конфигурации системы
Последний этап запуска системы в работу — загрузка и применение ее конфигурации.
В web-интерфейсе перейдите на вкладку Configuration files. В секции
Upload configuration загрузите архив в формате .zip
, который должен
содержать основной файл конфигурации системы
config.yml
и другие необходимые файлы (описание модели
данных, Lua-код функций и др.). Если загрузка и применение конфигурации прошли
успешно, система выдаст уведомление об этом.
После этого система готова к работе.
В дальнейшем актуальную конфигурацию всегда можно скачать на этой же вкладке,
нажав на кнопку Current configuration в верхнем правом углу.
2.3.4. Автоматическая синхронизация настроек в кластере
Для нормального функционирования кластера все экземпляры должны иметь одинаковые
настройки. С этой целью каждый экземпляр в своей рабочей директории
/var/lib/tarantool/<instance_name>
хранит копию конфигурации —
в поддиректории /config
в виде набора yml
-файлов. Также в рабочей директории
хранятся резервная копия конфигурации (в поддиректории /config.backup
) и
токен кластера .tarantool.cookie
.
Кластер синхронизирует эти файлы настроек у всех экземпляров.
После первоначальной загрузки файла конфигурации config.yml
конфигурация автоматически
обогащается информацией о топологии кластера с указанием всех серверов,
репликасетов, экземпляров и их ролей. Также добавляется информация о созданных
спейсах для хранения объектов согласно модели данных. Именно в таком виде
конфигурация хранится в рабочей директории каждого экземпляра и синхронизируется
между ними.
При дальнейшей настройке системы — добавлении пользователей и токенов
приложений, настройке прав доступа и др. — информация об этом добавляется в
соответствующие файлы конфигурации и синхронизируется в кластере.
Аналогичная синхронизация выполняется при любых изменениях текущих настроек.
2.3.5. Токен кластера
Токен кластера (cluster_cookie
) — это некий уникальный идентификатор, одинаковый для всех узлов
кластера и хранящийся на каждом из узлов. Он шифрует UDP-трафик построения
кластера и необходим для того, чтобы все члены кластера могли общаться между
собой и не допускать в кластер посторонних членов. Если у какого-либо узла токен
другой, остальные узлы кластера его не видят, и сам узел не видит кластер.
Токен кластера можно задать, например, при установке системы, определив в
файле конфигурации кластера параметр cluster_cookie
.
Если токен кластера не задан, то берется значение по умолчанию.
По умолчанию токен кластера хранится в файле .tarantool.cookie.
в рабочей
директории каждого из узлов (экземпляров) — /var/lib/tarantool/<instance_name>
.
Поиск файла .tarantool.cookie
осуществляется в следующих местах по убыванию
важности:
{workdir}/.tarantool.cookie
, где {workdir}
задается или в параметре
инициализации, или из переменной среды $WORKDIR
, или берется текущая
директория;
$HOME/.tarantool.cookie
, где $HOME
— переменная среды.
2.4. Администрирование кластера
В данной главе описываются следующие операции по администрированию кластера:
2.4.1. Изменение топологии кластера (добавление экземпляров)
Рассмотрим добавление нового экземпляра (инстанса, instance) в кластер на
примере топологии,
которую мы использовали при описании установки системы.
Допустим, нам нужно добавить еще один экземпляр с кластерной ролью storage
.
Как и в случае первоначальной устновки, сначала необходимо подготовить файл
конфигурации разворачиваемого экземпляра в формате JSON. Значения параметров
конфигурации см. в описании примера.
Важно
Убедитесь, что значение параметра cluster_cookie
такое же, как и в
конфигурации уже развернутого кластера.
В противном случае вы не сможете включить вновь развернутый экземпляр в
кластер, поскольку принадлежность к кластеру как раз определяется
посредством этого параметра (см. подробнее).
{
"general":
{
"cluster_cookie": "ilikerandompasswords"
},
"servers":
[
{
"address": "172.19.0.3",
"username": "vagrant",
"instances":
[
{
"name": "storage_3",
"binary_port": 3003,
"http_port": 8083,
"memory_mb": 1024
}
]
}
]
}
Далее разверните экземпляр с помощью скрипта tdgctl.py
аналогично тому,
как это выполнялось при установке системы.
./tdgctl.py -c deploy_add.json deploy -f tdg-<version>.tar.gz
где
deploy_add.json
— файл с конфигурацией нового экземпляра;
tdg-<version>.tar.gz
— файл дистрибутива
(<version> — версия релиза TDG). Используйте
тот же дистрибутив,
с помощью которого выполнялась установка системы.
После успешного выполнения команды новый экземпляр должен появиться
в web-интерфейсе на вкладке Cluster
в таблице Unconfigured servers.
Если новый экземпляр не появился в web-интерфейсе,
используйте функцию Probe server для проверки его доступности
(см. подробнее).
Далее необходимо настроить конфигурацию нового экземпляра: включить его в набор
реплик — новый или уже существующий — и определить кластерную роль и другие
параметры.
Подробнее см. в разделе «Создание набора реплик и задание ролей».
В нашем примере мы включим развернутый экземпляр в новый набор реплик,
присвоив ему кластерную роль storage
. Экземпляр успешно добавлен в кластер:
Необходимо отметить, что новый
набор реплик с ролью storage
имеет вес (параметр Replica set weight
),
равный «0». Это определяется при инициализации модуля vshard
,
которая происходит во время первоначального развертывания кластера.
В данном случае — после добавления экземпляра в новый набор реплик — нужно
увеличить значение параметра Replica set weight
для того,
чтобы система произвела балансировку данных,
перенеся их часть на новый набор реплик с ролью storage
.
Для этого нажмите кнопку Edit у нужного набора реплик. В диалоговом окне
Edit Replica Set, увеличьте значение параметра Replica set weight
и
нажмите Save, чтобы начать балансировку данных.
Если мы добавляем новый экземпляр в уже существующий набор реплик
с ролью storage
, действия, описанные выше, производить не нужно —
балансировка данных будет выполнена автоматически.
При добавлении нового экземпляра в набор реплик (новый или уже существующий)
происходит следующее:
Кластер валидирует обновление конфигурации, проверяя доступность нового
экземпляра с помощью модуля Tarantool
membership
.
Все узлы в кластере должны быть рабочими, чтобы валидация была пройдена.
Новый экземпляр ожидает, пока другой экземпляр в кластере не получит обновление
конфигурации и не обнаружит его. На этом шаге у нового экземпляра еще нет своего
UUID.
Как только экземпляр понимает, что кластер знает о нем, экземпляр вызывает
функцию box.cfg()
и начинает работу.
2.4.2. Балансировка данных
Балансировка данных (решардинг) запускается регулярно, а также после добавления
в кластер нового набора реплик с ненулевым весом (параметр Replica set weight
).
Мониторинг процесса балансировки можно вести, отслеживая количество активных
виртуальных сегментов (virtual buckets) на экземплярах с ролью storage
.
Первоначально в новом наборе реплик нет активных сегментов. Через некоторое
время фоновый процесс балансировки начинает переносить сегменты из других
наборов в новый. Балансировка продолжается до тех пор, пока данные не будут
распределены равномерно по всем наборам реплик.
Чтобы отслеживать текущее количество сегментов, подключитесь к нужному экземпляру
с ролью storage
через консоль и выполните команду
vshard.storage.info().bucket
В web-интерфейсе администратора это можно сделать на вкладке Console.
Эта вкладка доступна только в режиме разработки
(начиная с версии 1.6.3).
2.4.3. Исключение экземпляра из кластера
Система позволяет исключить какой-либо экземпляр из кластера. После того как
экземпляр будет исключен, остальные экземпляры будут информированы об этом и не
будут считать его членом кластера. Снова вернуть исключенный экземпляр в кластер
будет нельзя.
Для исключения экземпляра из кластера:
В web-интерфейсе на вкладке Cluster для нужного экземпляра нажмите
[…] > Expel server.
В окне подтверждения нажмите OK.
Экземпляр больше не будет отображаться на вкладке Cluster.
2.4.4. Включение автоматического восстановления после отказа (Failover)
Если в кластере задана конфигурация «мастер-реплика» и включено автоматическое
восстановление после отказа (failover), то при отказе мастера в каком-либо
наборе реплик кластер автоматически выбирает следующую реплику из списка
приоритетов и назначает ей роль активного мастера (read/write). Когда вышедший
из строя мастер возвращается к работе, его роль восстанавливается, а назначенный
ранее активный мастер снова становится репликой (read-only).
Чтобы установить приоритет экземпляров в наборе реплик:
В web-интерфейсе на вкладке Cluster нажмите кнопку Edit у нужного
набора реплик.
В диалоговом окне с помощью перетаскивания мышью (drag-and-drop) отсортируйте экземпляры в списке
в нужном порядке приоритета и нажмите Save.
По умолчанию восстановление после отказа отключено, на что указывает статус на
кнопке Failover: disabled. Нажмите эту кнопку для включения данной функции.
В диалоговом окне Failover control выберите нужный тип автоматического восстановления.
Для опций Eventual и Stateful указано значение по умолчанию 20 секунд для параметра
Failover timeout — время, через которое запустится восстановление после отказа,
если мастер вышел из строя.
Для опции Stateful также понадобится указать следующие параметры:
State provider — выбор внешнего поставщика состояния:
Fencing (опционально) — фенсинг (изоляция узла), включается, если отметить чекбокс «Enabled».
Если включить фенсинг, то когда поставщик состояния и одна из реплик одновременно станут недоступны,
лидер перейдет в режим «только для чтения». Для этого нужно указать следующие параметры:
Fencing timeout — время для запуска фенсинга, если проверка (health check) выявила,
что экземпляр функционирует неправильно. Значение по умолчанию: 10 секунд;
Fencing pause — временной интервал в секундах для проверки состояния экземпляров (health check).
Значение по умолчанию: 2 секунды.
После настройки нужных параметров нажмите кнопку Save.
Статус функции восстановления изменится на Failover: eventual или
Failover: stateful в зависимости от выбранного типа функции восстановления.
2.4.5. Изменение мастера в наборе реплик
Текущий мастер в наборе реплик отображается символом короны. На
вкладке Cluster цвет короны символизирует статус данной реплики — зеленый
для исправно работающей реплики и красный для неработающей реплики.
В диалоге Edit replica set цвет короны всегда остается красным.
В режимах Failover: eventual и Failover: disabled, чтобы вручную
изменить мастера в наборе реплик, необходимо выполнить следующие действия:
В web-интерфейсе на вкладке Cluster нажмите кнопку Edit у нужного
набора реплик.
В диалоговом окне в разделе Failover priority при помощи перетаскивания
мышью (drag-and-drop) переместите на первую строку ту реплику, которую хотите
сделать мастером, и нажмите Save.
В режиме Failover: stateful выбор мастера осуществляется во внешней системе.
2.4.6. Отключение набора реплик
Под отключением набора реплик с ролью storage
(например, для технического
обслуживания) подразумевается перемещение всех его виртуальных сегментов в
другие наборы реплик.
Чтобы отключить набор реплик:
В web-интерфейсе на вкладке Cluster нажмите кнопку Edit у нужного
набора реплик.
В диалоговом окне установите значение параметра Replica set weight
равным
«0» и нажмите Save.
Подождите, пока процесс балансировки не завершит перенос всех виртуальных
сегментов. Текущее количество сегментов в данном наборе реплик можно
отслеживать как это описано в разделе о балансировке данных.
2.5. Роли
Функции экземпляров TDG (инстансов, instances) в кластере распределяются на основе ролей.
Кластерные роли — это Lua-модули, которые реализуют специфическую для экземпляра
логику.
Существуют 2 вида ролей: встроенные и настраиваемые.
Встроенные роли vshard-router
и vshard-storage
, а также логика
их работы уже
определены в Tarantool Cartridge,
на базе которого построен TDG, и не требуют дополнительной
конфигурации.
Встроенная роль failover-coordinator
и логика её работы также
определены в Tarantool Cartridge.
Следующие встроенные роли выполняют сугубо технические задачи и потому не
отображаются в интерфейсе пользователя:
tracing
— скрытая роль, нужна для мониторинга производительности модулей
системы;
account_provider
— скрытая роль для кэширования обращений к
account_manager
. Включена на всех экземплярах;
maintenance
— скрытая роль, необходимая для выполнения технических работ
для поддержания работы кластера. Включена на всех экземплярах;
watchdog
— скрытая роль для предотвращения зависания экземпляра.
Включена на всех экземплярах.
По усмотрению администратора экземплярам назначаются настраиваемые роли, которые
можно дополнительно сконфигурировать.
В TDG реализованы следующие настраиваемые роли:
connector
— для приема запросов на обработку по сети;
input_processor
— для обработки запросов;
storage
— для хранения данных (обработанных объектов);
account_manager
— для обеспечения работы ролевой модели доступа;
logger
— для сбора логов (со всех экземпляров кластера);
notifier
— для отправки уведомлений о событиях в ремонтной очереди (вкладка
Repair
);
output_processor
— для преобразования объектов в формат сторонней системы
и отправки в эту систему;
task-runner
— для выполнения задач;
scheduler
— для старта задач (по расписанию или вручную) на экземпляре с
ролью task-runner
;
sequence_generator
— для выдачи диапазонов уникальных номеров для
последовательностей, используемых экземплярами.
Некоторые из настраиваемых ролей требуют включения встроенных ролей.
Встроенные роли не нужно включать на экземплярах специально, они назначаются
автоматически при включении соответствующих настраиваемых ролей.
Настраиваемая роль |
Автоматически назначаемые роли |
connector |
vshard-router, tracing |
input_processor |
vshard-router, tracing |
storage |
vshard-router, vshard-storage, tracing |
logger |
vshard-router |
notifier |
vshard-router, tracing |
output_processor |
vshard-router, tracing |
scheduler |
vshard-router, tracing |
task_runner |
vshard-router, tracing |
2.5.1. Рекомендации по назначению ролей на экземплярах
Хотя в TDG нет понятия «обязательная роль», имеет смысл говорить о
минимально необходимом наборе ролей — connector
, input_processor
и storage
.
Этот минимальный набор обеспечивает основные функции системы — получение объекта,
его обработку и хранение в TDG. Остальные роли назначаются по
необходимости, в зависимости от решаемых бизнес-задач.
Роли имеют ряд характеристик, в зависимости от которых их можно делить на
различные группы:
Роль |
Stateless / Stateful |
Singleton |
connector |
stateless |
нет |
input_processor |
stateless |
нет |
storage |
stateful |
нет |
logger |
stateful |
да |
notifier |
stateful |
да |
output_processor |
stateless |
нет |
scheduler |
stateful |
да |
task_runner |
stateless |
нет |
account_manager |
stateful |
да |
sequence_generator |
stateful |
да |
failover-coordinator |
stateless |
нет |
Все роли являются реплицируемыми (репликасет (набор реплик) с ролью может
содержать более одной реплики).
На одном экземпляре можно назначить одну или несколько ролей.
Учитывая вышесказанное, можно дать следующие рекомендации по организации
кластера и назначению ролей на экземплярах кластера:
Роли connector
, input_processor
, task_runner
и output_processor
задействованы в обработке объектов. Их нужно горизонтально масштабировать
пропорционально входящей нагрузке и утилизации CPU на серверах.
Если вы назначаете на экземпляр роль connector
или input_processor
,
обычно рекомендуется назначить на этот же экземпляр и вторую роль из данной пары.
Исключение может быть в случае, когда первоначальная обработка объекта на
роли connector
является трудозатратной операцией и имеет смысл
масштабировать эту роль отдельно.
Роль storage
(хранит состояние, не singleton): создание репликасета с
нодами в разных дата-центрах. Количество репликасетов (горизонтальное
масштабирование) — пропорционально объему данных, хранимых в RAM.
Важно
Если экземпляру назначена роль storage
, то после этого исключить (expel)
из кластера этот экземпляр будет нельзя.
Роли logger
, notifier
, scheduler
(хранят состояние, singleton):
обычно достаточно репликасета с 2 репликами, разнесенными по разным дата-центрам.
Роль output_processor
не хранит состояние напрямую. Функционально роль
реплицирует объекты во внешние системы, но для хранения объектов,
предназначенных для репликации, а также объектов, чья репликация завершилась
с ошибкой, используется роль storage
.
Поэтому логика горизонтального масштабирования роли output_processor
аналогична
логике для ролей, используемых для обработки объектов (см. п.1 выше).
2.6. Основной процесс обработки запроса
Роль connector
принимает запрос (объект) из системы-источника. Исходный
формат входящего объекта — JSON или XML, в зависимости от системы-источника.
Если экземпляров с ролью connector
несколько, выбор экземпляра и балансировка
нагрузки выполняется сервисом nginx
.
На роли connector
происходит первоначальная обработка (parsing) входящего
объекта: объект преобразуется в Lua-объект (Lua-таблицу) и направляется на роль
input_processor
.
Также для каждого запроса генерируется его UUID, по которому в дальнейшем можно
проследить весь путь объекта.
Дальнейший маршрут объекта определяется в соответствии с ключом маршрутизации
(routing_key
), который присваивается объекту на разных этапах его обработки.
Логика маршрутизации по ключу и порядок обработки объекта задается в
файле конфигурации системы config.yml
.
На роли input_processor
объект проходит через конвейеры обработки (pipelines,
пайплайны), которые определены в конфигурации config.yml.
Прежде всего выполняется классификация объекта. Если классификация успешна,
объекту присваивается ключ маршрутизации, определяющий дальнейшие пайплайны
для обработки.
Если задано в конфигурации, объект проходит обработку в других пайплайнах в
соответствие с ключом маршрутизации.
Объект валидируется в соответствии с моделью данных.
Примечание
В случае, если указанный в конфигурации пайплайн не обнаружен,
выполняется попытка найти в конфигурации и выполнить одноименную функцию.
Если на любом из этапов — 1, 2, 3 — происходит ошибка, объект отправляется в
ремонтную очередь. Информация об объектах в ремонтной очереди
с описанием ошибок
доступна администратору через web-интерфейс.
Если определена роль notifier
и настроена конфигурация mail server
и
subscribers
, подписчикам будет отправлено уведомление об ошибке.
Если классификация, обработка и валидация объекта прошли успешно, объекту
присваивается соответствующий routing_key
, и объект сохраняется на экземпляре
с ролью storage
. Тип сохраняемого объекта также определяется настройками в
config.yml
в соответствии с ключом маршрутизации объекта.
Дальнейшие возможные действия с сохраненными объектами:
2.7. Настройки безопасности
Администрирование функций безопасности включает в себя следующее:
Также важная тема, относящаяся к безопасности, —
режимы функционирования TDG.
2.7.1. Ролевая модель доступа
В TDG реализована ролевая модель доступа к функциям системы и данным, хранящимся
в системе. По умолчанию,
определены три роли, которые могут быть назначены различным пользователям и внешним
приложениям для авторизованного доступа и действий в системе:
«admin»;
«supervisor»;
«user».
Данные роли можно рассматривать как своего рода шаблоны — они являются
нередактируемыми и имеют предзаданный набор прав.
Роль |
Доступ к функциям |
Доступ к данным |
admin |
Полный доступ ко всем функциям. |
read/write для всех агрегатов
|
supervisor |
Полный доступ в режиме «только для чтения» —
имеет доступ ко всем разделам системы, но не
может менять ее настройки. |
read для всех агрегатов
|
user |
Ограниченный доступ. Запрещен доступ к разделам,
связанным с администрированием системы (скрыты
вкладки Cluster, Configuration files,
Model, Audit Log, Settings). |
Отсутствует |
На основе этих ролей администратором могут быть созданы пользовательские роли
с произвольным набором прав как для доступа к функциям системы, так и для
доступа к данным.
Текущий список ролей пользователей можно увидеть на вкладке
Settings > Roles:
Для работы ролевой модели доступа необходимо назначить одному из наборов реплик
в кластере роль account_manager
. Это делается на этапе настройки кластера.
2.7.1.1. Создание роли пользователя
Администратор имеет возможность создания новых ролей доступа, в том числе на
базе уже существующих (ролей по умолчанию или ранее созданных пользовательских
ролей). Для новой роли может быть задан произвольный набор
прав, и также к ней может быть привязан профиль доступа к данным.
Для создания новой роли пользователя:
На вкладке Settings > Roles нажмите Add new role. Откроется диалог
создания роли.
В диалоге создания задайте следующие параметры:
Name
— название роли;
Description (optional)
— (опционально) произвольное описание роли;
Inherit from role
— (опционально) выбор существующей роли, на базе
которой будет создаваться новая.
В таблице отметьте флаг Allowed для тех действий, которые будут доступны
для пользователя данной роли (перечислены в списке в колонке Action).
Профили доступа к данным, если они уже созданы в системе,
отображаются в этом
же списке. Назначить профиль доступа к данным можно и позднее, отредактировав
набор действий для этой роли.
Сохраните новую роль, нажав Save.
2.7.2. Управление пользователями
Текущий список пользователей TDG и инструменты для управления ими
находятся на вкладке Settings > Users:
Управление пользователями включает в себя следующие операции:
2.7.2.1. Создание профиля пользователя
Для создания профиля пользователя TDG:
В web-интерфейсе перейдите на вкладку Settings > Users и нажмите
Create user. Откроется диалог создания пользователя.
В диалоге укажите следующие параметры:
Name
— имя пользователя;
Email
— электронная почта пользователя;
Password
— пароль для авторизации в системе (должен соответствовать
политике паролей). Также пароль можно сгенерировать
автоматически:
Expires in
— (опционально) срок действия пароля;
Примечание
Проверка истечения срока действия паролей выполняется примерно раз в 30 минут.
Учетные записи с истекшим сроком действия паролем блокируются.
Нажмите Submit.
При создании профиля автоматически генерируется уникальный идентификатор
пользователя, который отображается в колонке LOGIN в таблице User list и
используется в дальнейшем для авторизации в web-интерфейсе.
Профили пользователей, включая их пароли, хранятся в файле конфигурации системы
config.yml
, который синхронизируется между всеми
экземплярами, входящими в кластер.
В целях безопасности пароли пользователей хранятся в виде хэша с добавлением
криптографической соли.
При задании пароля можно посмотреть текущую политику паролей в режиме подсказки:
Примечание
Помимо создания профилей пользователей в web-интерфейсе, их можно
импортировать в систему посредством JSON-файла.
2.7.2.2. Редактирование и удаление профиля пользователя
Для редактирования профиля пользователя:
В web-интерфейсе перейдите на вкладку Settings > Users.
В таблице User list в колонке ACTIONS для нужного пользователя нажмите
Edit.
В диалоге редактирования профиля пользователя измените необходимые параметры
и нажмите Submit.
Для удаления профиля пользователя из системы в колонке ACTIONS нажмите More
для нужного пользователя и выберите в выпадающем меню Delete.
В диалоговом окне подтвердите удаление, нажав OK.
2.7.2.3. Изменение статуса пользователя
При создании профиля пользователя он автоматически активируется в системе, на
что указывает статус «active» в колонке STATUS таблицы User list.
Администратор может изменить статус пользователя вручную — заблокировать
пользователя в системе:
В колонке ACTIONS нажмите More для нужного пользователя и выберите в
выпадающем меню Block user:
В диалоговом окне укажите, если необходимо, причину изменения статуса
(параметр Reason) и нажмите Block. Если ранее статус пользователя уже
менялся, причина последнего изменения статуса будет указана в параметре
Previous Reason.
Статус пользователя будет изменен на «blocked» и отображен в колонке STATUS.
В дальнейшем администратор может заново активировать профиль пользователя
по аналогичной процедуре (More > Unblock user).
2.7.2.4. Сброс пароля пользователя
Администратор может сбросить пароль
любого из пользователей: в колонке ACTIONS нажмите More для нужного пользователя
и выберите в выпадающем меню Reset password. В диалоговом окне подтвердите сброс
пароля, нажав OK. После этого на электронный адрес пользователя, указанный в
его профиле, будет выслан временный пароль для авторизации в системе.
Для корректной работы отправки нового пароля на электронный адрес должен быть
настроен SMTP-сервер. Для этого необходимо:
Развернуть и запустить SMTP-сервер.
В файле конфигурации TDG config.yml
прописать следующие
настройки и загрузить измененную конфигурацию в
систему:
connector:
output:
- name: to_smtp
type: smtp
url: <URI SMTP-сервера>
from: <адрес отправителя>
timeout: <значение тайм-аута, секунды>
account_manager:
only_one_time_passwords: true
output:
name: to_smtp
2.7.2.5. Экспорт/импорт профилей пользователей
2.7.2.5.1. Экспорт
Профили пользователей можно экспортировать из системы в формате JSON. Для этого
на вкладке Settings > Users нажмите Export:
В результате система сформирует и экспортирует файл с именем users-<N>.json
(где <N> — порядковый номер), который содержит массив с профилями всех текущих
пользователей. Профиль каждого пользователя имеет следующий формат (пример):
[
{
"expires_in":2592000,
"login":"pc9199",
"email":"petrov@mailserver.com",
"created_at":1586236779870364400,
"state_reason":"petrov@mailserver.com state is changed to active: recover from disabled",
"failed_login_attempts":1,
"uid":"23563aa8-facc-4970-89f3-cfc267609c3b",
"role":"admin",
"state":"active",
"username":"Петров",
"last_login":1586236825716746000,
"last_password_update_time":null
},
{
...
}
]
2.7.2.5.2. Импорт
Также возможна обратная операция — импорт профилей пользователей в систему.
Для импорта вначале необходимо подготовить файл с профилями пользователей в
формате JSON. Это может быть ранее экспортированный файл с профилями (см. выше),
или же администратор может подготовить файл с этими данными самостоятельно.
В описанном представлении данных пользователя в формате JSON обязательными
являются все поля кроме:
Отдельно нужно сказать про поле password
и логику генерации пароля
пользователя при импорте профиля. Существует 2 варианта создания пароля:
Указать пароль в явном виде в поле password
— в этом случае флаг
Generate passwords в диалоге импорта отмечать не нужно (см. ниже процедуру
импорта), и пароль для пользователя будет взять из поля password
как есть.
При этом пароль должен соответствовать текущей политике паролей,
определенной в системе.
Сгенерировать пароль автоматически — в этом случае в поле password
необходимо передать пустое значение (null
) или просто не указывать это поле в JSON,
а в диалоге импорта нужно отметить флаг Generate passwords.
Пароль будет сгенерирован автоматически в соответствии с текущей политикой паролей.
Важно
Для всех импортируемых пользователей в JSON должен быть выбран один и тот же
вариант генерации пароля — либо вариант 1, либо вариант 2.
После успешного импорта профиля данные пользователя, включая пароль, будут
отображены в web-интерфейсе. Также можно настроить отправку данных на
электронную почту пользователя. Для этого необходимо иметь настроенный SMTP-сервер
и в диалоге импорта отметить флаг Send passwords via user’s email.
Важно
Если определенный пользователь авторизован в системе,
повторно импортировать его, стерев старые данные, нельзя.
Такое ограничение защищает пользователя от потери доступа к системе.
Для обновления профиля пользователя используйте операцию редактирования.
Это же верно и для токенов приложения.
Пример профиля пользователя в формате JSON для импорта в систему:
[
{
"expires_in":2592000,
"login":"rk7222",
"email":"ivanov@mailserver.com",
"created_at":1586236779870364400,
"state_reason":null,
"failed_login_attempts":0,
"uid":"a0067457-faf2-4441-b213-7c823422bab6",
"role":"admin",
"state":"active",
"username":"Иванов",
"last_login":null,
"last_password_update_time":null,
"password":null
}
]
Для импорта профилей пользователей:
На вкладке Settings > Users нажмите Import. Откроется диалоговое окно.
В диалоговом окне выберите JSON-файл с профилями пользователей
(Choose File), отметьте, если необходимо, опции, связанные с генерацией
паролей (Generate passwords и Send passwords via user’s email), и
нажмите Apply.
В случае успешного импорта новые профили пользователей будут добавлены в таблицу
User list. Данные профилей, включая пароли, будут показаны в web-интерфейсе
в сообщении о результатах операции импорта.
Важно
Сохраните сгенерированный пароль надежном месте.
В целях безопасности пароль в явном виде показывается только сразу после импорта.
Сообщение с паролем исчезнет, как только вы покинете данную страницу или запустите следующую
операцию импорта.
Также данные импортированного пользователя, включая пароль, можно сохранить в
формате .csv
— кнопка Download passwords в сообщении с результатами импорта.
2.7.2.6. Управление политикой паролей
Политика создания паролей для авторизации пользователей в системе регулируется
на вкладке Settings > Password Policy. Данная политика применима в равной
степени как к паролям, которые пользователи задают вручную, так и к
автоматически сгенерированным паролям. Изменять политику паролей может только
администратор.
В политике определяются:
Категории символов, которые должны быть обязательно включены в пароль:
Include Lowercase Characters
— латинские буквы в нижнем регистре. По умолчанию включено;
Include Uppercase Characters
— латинские буквы в верхнем регистре. По умолчанию включено;
Include Digits
— цифры от 0 до 9 включительно. По умолчанию включено;
Include Symbols
— дополнительные символы (!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
);
Password length
— минимальная допустимая длина пароля.
Значение по умолчанию: 8. Максимальная длина пароля: 1000 символов.
Для изменения политики паролей необходимо поменять значения нужных параметров и
нажать ОК.
2.7.3. Токен приложений
Приложениям внешних систем для доступа к данным и функциям TDG необходим
авторизованный доступ, который организуется через токены приложений.
Общий процесс выглядит следующим образом: в web-интерфейсе администратор
генерирует токен, назначает для него права доступа к объектам TDG
и передает токен разработчикам внешней системы.
Сгенерировать токен можно в web-интерфейсе: вкладка Settings > Tokens, кнопка
Create token. В диалоге создания необходимо указать следующие параметры
и нажать Submit:
Name
— имя (ключ) токена, которое будет в дальнейшем идентифицировать его в
системе;
Expires in
— (опционально) срок действия токена;
Role
— роль токена. Аналогична роли пользователя согласно ролевой модели
доступа.
В дальнейшем сам сгенерированный токен и его имя являются нередактируемыми.
Отредактировать можно только срок действия токена и его роль.
После генерации токен в явном виде будет доступен только в сообщении в
web-интерфейсе:
Важно
Сохраните сгенерированный токен в надежном месте. В целях безопасности токен
в явном виде показывается только один раз, при генерации. Сообщение с токеном
исчезнет, как только вы покинете данную страницу или сгенерируете новый токен.
Другие возможные операции по управлению токенами — изменение статуса, удаление,
экспорт, импорт — аналогичны таким же операциям для пользовательских профилей.
2.7.4. Права доступа к данным
TDG дает возможность установить для каждой роли
права на действия с данными (объекты с логическим типом «Aggregate»,
далее — агрегат), которые обрабатываются и хранятся в системе:
Устанавливать права нужно после того, как в систему загружена модель данных, в
которой описаны агрегаты и связанные с ними объекты. Модель данных загружается
при первоначальной конфигурации системы.
Права доступа к данным задаются в web-интерфейсе через создание профиля доступа
(data action), который потом назначается для каких-либо из пользовательских ролей.
Для создания профиля доступа:
Перейдите на вкладку Settings > Data actions.
Нажмите Add new Data Action. Откроется диалог создания профиля.
В поле Name введите имя профиля доступа.
Для каждого из агрегатов отметьте галочками нужные права, Read
и/или
Write
.
Сохраните профиль, нажав Save.
Созданный профиль доступа отображается на вкладке Settings > Data actions.
Профиль является редактируемым, т.е. в дальнейшем можно изменять права
Read
/Write
для нужных агрегатов.
После создания профиль становится доступен для назначения какой-либо роли.
Примечание
Назначить профиль доступа можно только для роли, созданной в системе
администратором.
Для ролей по умолчанию («admin», «supervisor», «user») эта операция недоступна,
т.к. эти роли нередактируемые и являются своего рода шаблонами с предзаданным
набором прав.
Чтобы назначить профиль доступа для существующей роли:
Перейдите на вкладку Settings > Roles и в колонке Action для нужной
роли нажмите Edit.
В диалоге редактирования отметьте флаг Allowed для нужного профиля доступа.
Сохраните настройки роли, нажав Save.
Аналогично профиль доступа можно назначить и при создании новой роли.
2.7.5. Режим обязательной аутентификации
Важно
Сразу после развертывания системы у пользователей и внешних приложений есть
возможность неавторизованного анонимного доступа ко всем функциям и данным, что
некорректно с точки зрения безопасности. Поэтому одним из первых действий
администратора должно быть отключение возможности анонимного доступа и настройка
обязательной аутентификации.
Для этого необходимо:
Создать профиль пользователя с ролью «admin».
Авторизоваться в системе под этим пользователем.
Включить режим обязательной аутентификации (отключить анонимный доступ).
Режим обязательной аутентификации включается/отключается на вкладке Cluster
переключателем Auth.
Важно
При включении режима обязательной аутентификации любой запрос к TDG,
а также доступ к веб-интерфейсу потребуют идентификации и аутентификации.
После идентификации и аутентификации пользователя в веб-интерфейсе
будут скрыты недоступные для роли текущего пользователя вкладки.
Для выполнения идентификации и аутентификации можно использовать один из
следующих способов:
Передача логина и пароля пользователя.
Передача параметра lsid
из файла-cookie (в основном используется
веб-интерфейсом после первого ввода логина и пароля для последующих
аутентификаций).
Передача токена приложения.
2.7.6. Авторизация внешних пользователей и систем через LDAP
Система TDG поддерживает технологию единого входа (Single Sign-On).
Таким образом, авторизованный доступ к данным и функциям TDG возможен не только через токены и
пользователей, но и через LDAP.
Чтобы настроить авторизацию внешних пользователей и систем через LDAP, пропишите необходимые параметры
в файле конфигурации config.yml
.
Добавить секцию LDAP в файл конфигурации можно на любом этапе работы с системой TDG.
В самом начале настройки LDAP стоит обратить внимание на параметры domain
и organizational_units
.
Они используются при аутентификации для поиска пользователя в соответсвующем домене и организационном юните(-ах).
Логином в систему является строка вида имя пользователя@domain
, где:
имя пользователя
это пользователь в LDAP, который состоит в описанном выше домене и организационном юните
domain
это домен LDAP также описанный выше
Например: johndoe@example.com
.
Однако, если опция use_active_directory
выставлена в True
, то логином в систему будет являться атрибут userPrincipalName
у пользователя LDAP.
Примечание
Для локального тестирования LDAP авторизации предлагается использовать сервер GLAuth.
Гарантируется работа с версией GLAuth 2.0.0.
2.7.7. Режимы функционирования TDG
TDG может функционировать в двух режимах:
Режим эксплуатации является основным для штатной эксплуатации TDG.
Режим разработки предоставляет дополнительный функционал (см. список ниже),
который используется при разработке пользовательского кода и отладке работы
системы. Однако при штатной эксплуатации этот функционал может
снижать производительность системы и создавать
ситуации, потенциально небезопасные с точки зрения сохранности данных и
уязвимости системы.
Поэтому в версии TDG, сертифицированной
по требованиям доверия ФСТЭК России, использование режима разработки запрещено.
Дистрибутивы установки для этих двух режимов разные.
По умолчанию выдается дистрибутив для работы в режиме эксплуатации.
В режиме разработки доступны следующие дополнительные функции
(по сравнению с режимом эксплуатации):
GraphQL-запрос на удаление данных (mutation) во всех спейсах экземпляров
с ролью «storage»;
GraphQL-запросы на чтение (query) и изменение (mutation) конфигурации
кластера и настроек системы;
GraphQL-запросы (mutation) evaluate
для выполнения любого кода, включая
код для чтения и изменения данных;
включен «строгий режим» (встроенный модуль Tarantool strict
),
который позволяет отслеживать использование
необъявленных глобальных переменных;
более полная проверка (с помощью модуля Tarantool checks
)
типов аргументов, передаваемых в функции Lua. В режиме разработки проверка
выполняется для большинства функций, принимающих на вход данные от
пользователя.
В режиме эксплуатации — только для функций репозитория;
доступна вкладка Console;
доступна вкладка Code.
Дистрибутив установки содержит
скрипты enable_dev_mode.sh
и enable_prod_mode.sh
для включения
соответствующего режима функционирования. Скрипты находятся в корневой
директории дистрибутива.
Развернутый экземпляр можно переключить в другой режим работы. Для этого
необходимо:
На сервере, где развернут экземпляр, перейти в директорию, где хранятся
исполняемые файлы для этого экземпляра /usr/share/tarantool/<instance_name>
Запустить нужный скрипт (enable_dev_mode.sh
или
enable_prod_mode.sh
).
Перезапустить экземпляр:
systemctl restart <instance_name>
2.8. Ремонтная очередь
Когда объект поступает в систему на обработку, он сразу помещается в ремонтную
очередь. Если объект удалось обработать и сохранить, он удаляется из ремонтной
очереди. В случае ошибки объекты остаются в ремонтной очереди, и администратор
имеет возможность просматривать их и после устранения источника проблемы
отправлять на повторную обработку.
Можно выделить следующие основные причины возникновения ошибок, когда объекты
остаются в ремонтной очереди:
Ошибка при обработке объекта в каком-либо из пайплайнов;
Система TDG ожидает объект в определенном формате, но объект
пришел из внешней системы в другом формате;
Внутренняя ошибка системы;
Сбой оборудования (hardware).
Работать с объектами в ремонтной очереди можно через web-интерфейс
на вкладке Repair.
В таблице отображается текущий список объектов в ремонтной очереди. Двойной клик
на объект в таблице открывает отдельное окно Object info со следующей
информацией об объекте:
Id
— UUID объекта;
Time
— дата и время, когда объект был помещен в ремонтную очередь;
Status
— статус объекта в ремонтной очереди (возможные значения:
«New», «In Progress», «Reworked»);
Reason
— описание причины ошибки и полный stack trace;
Object
— текущая структура объекта в формате JSON.
Для поиска нужного объекта есть возможность фильтрации
по любому сочетанию символов в любой колонке таблицы — поле Filter;
по дате и времени — поле Start Time ~ End Time.
Доступные действия над объектами в ремонтной очереди:
Try again — повторная обработка объекта той же функцией пайплайна, при
выполнении которой возникла ошибка;
Delete — удаление объекта из ремонтной очереди.
Когда объект попадает в ремонтную очередь, он имеет статус «New». При повторной
обработке статус объекта меняется на «In Progress». Если обработка прошла
успешна, объект удаляется из ремонтной очереди. Если при повторной обработке
опять возникла ошибка, система выдаст сообщение об ошибке, и объект останется
в ремонтной очереди со статусом «Reworked».
Аналогично действиям над отдельными объектами, можно выполнить действия над
всеми объектами в ремонтной очереди:
2.8.1. Уведомления
В системе есть возможность информировать пользователей о попадании объекта в
ремонтную очередь. Для этого должна быть определена роль notifier
, а также
заданы настройки почтового сервера и подписчиков, которым будут отправляться
уведомления.
Роль notifier
задается на одном из экземпляров при настройке ролей в кластере.
Настройки почтового сервера и подписчиков задаются через web-интерфейс на
вкладках Settings > Mail server и Settings > Subscribers соответственно.
2.8.1.1. Settings > Mail server
Настройки:
Url
— сервер SMTP, используемый для отправки уведомлений;
From
— отправитель, который будет показан в почтовом клиенте;
User name
— имя пользователя сервера SMTP;
Password
— пароль пользователя сервера SMTP;
Timeout (sec)
— тайм-аут запроса к серверу SMTP, в секундах.
2.8.1.2. Settings > Subscribers
Необходимо создать подписчиков (кнопка Create subscriber), которые будут
получать уведомления, указав их имя и Email. Возможные действия с подписчиками
аналогичны действиям с пользователями. Можно
2.9. Управление бизнес-объектами
2.9.1. Вкладка Expiration
В системе есть возможность сконфигурировать время жизни бизнес-объекта
(агрегата). В конце жизни объект физически
удаляется из системы.
Эти возможности системы полезны при работы с типами объектов, которые нет
необходимости хранить дольше определенного времени, например, суточные котировки
и т.п.
Время жизни объекта можно задать через web-интерфейс на вкладке
Settings > Expiration.
Объекты на этой вкладке описаны в модели данных и становятся доступны после
загрузки модели в систему.
В секции Time limit задаются:
Lifetime (hours)
— время жизни объекта в часах. Значение по умолчанию: 24;
Delay (seconds)
— интервал в секундах, через который запускается
очередная проверка устаревших объектов и их удаление. Значение по умолчанию:
36000.
В секции Version limit задаётся:
Keep n versions
— ограничение количества версий для объектов данного типа.
По умолчанию количество версий не ограничено. При задании параметра вначале будет выполнено
принудительное удаление устаревших
версий. В итоге будут сохранены только последние версии, количество которых
будет меньше или равно заданному в параметре. В дальнейшем такая проверка и при
необходимости удаление старых версий будет выполняться при каждой вставке новой
версии объекта.
Ограничения времени жизни объекта и ограничение количества версий могут работать
как по отдельности, так и одновременно.
Помимо пользовательского интерфейса эти параметры могут быть заданы в файле
конфигурации config.yml
в секции «expiration».
2.9.2. Вкладка Unlinked spaces
При удалении агрегатов из модели данных в базе
данных остаются спейсы, в которых хранятся объекты удаленных типов. На вкладке
Settings > Unlinked spaces находится список всех спейсов, которые больше
не привязаны к типам данных модели:
Действия, которые возможны с этими спейсами:
Для выполнения этих операций нажмите соответствующую кнопку в колонке ACTIONS
для нужного спейса.
Также эти операции можно применить сразу к нескольким спейсам: нужно выбрать
необходимые спейсы, после чего станут доступны кнопки Truncate selected и
Drop selected:
2.10. Логирование
Просмотр лога (журнала) событий, связанных с бизнес-процессами, доступен через
web-интерфейс на вкладке Logger.
Для этого в системе должна быть определена роль logger
и настроена его
конфигурация.
Каждая запись в таблице лога предоставляет следующую информацию о событии:
Level
— уровень логирования. В лог записываются события уровней
«Info», «Warning» и «Error»;
Time
— дата и время события в формате «yyyy-mm-dd hh:mm:ss»;
Node
— имя узла кластера, на котором произошло событие;
Module
— имя модуля системы, инициировавшего событие;
Message
— описание события. В начале записи приводится UUID исходного
запроса, с которым объект, в отношение которого возникло событие, пришел в
систему. В случае события уровней «Warning» и «Error» в описание также включен
полный stack trace.
Для удобства поиска записей в логе можно использовать фильтры. Фильтры
существуют для каждой из колонок таблицы. Возможно использовать несколько
фильтров совместно.
При заходе на страницу в таблице лога отображаются не более 100 записей. Если нужно
увеличить количество записей, выводимых на экран, нажмите кнопку Show more
внизу под таблицей.
Также возможно выгрузить все текущие записи лога в виде файла в формате .txt
.
Для этого нажмите кнопку Save внизу под таблицей.
Полный лог всех событий пишется средствами операционной системы и доступен при
помощи системной утилиты journalctl
:
journalctl -u <instance_name>
Также полные логи для всех экземпляров кластера можно получить при помощи скрипта
expirationd tdgctl.py
:
См. подробнее про формат данной команды.
2.11. Журнал аудита
Журнал аудита содержит в себе записи о событиях безопасности в TDG.
Подробный список событий безопасности, записи о которых попадают в журнал аудита,
приведен далее.
Просмотр журнала аудита доступен через web-интерфейс на вкладке Audit Log.
Для обеспечения надежного сохранения записей журнала аудита предусмотрено
несколько механизмов повышения надежности сохранения информации о событиях
безопасности:
Если в кластере имеется работоспособный экземпляр с ролью storage
, то все
записи журнала аудита сохраняются в хранилище, с равномерным распределением
по всем доступным наборам реплик;
Если отсутствует доступный экземпляр с ролью storage
, то записи журналов аудита
сохраняются локально на тех экземплярах, где происходят события безопасности.
При этом на вкладке Audit Log записи журнала аудита не отображаются.
После восстановления доступа к любому экземпляру с ролью storage
данные будут
сохранены на этот экземпляр;
Записи о событиях безопасности также сохраняются при помощи штатного
механизма логирования Tarantool
и, по умолчанию, доступны при помощи системной утилиты journalctl
(см.
Пример команды). Сохранение в логах Tarantool
происходит с добавлением префикса A>
в начало текста сообщения каждой записи
из журнала аудита, что облегчает их дальнейший поиск.
При этом журнал аудита ведется независимо от настроек авторизации.
Отключить ведение журнала аудита можно, сняв галочку
«Audit log is enabled» на вкладке Audit log.
Каждая запись в таблице предоставляет следующую информацию о событии:
Severity
— уровень важности сообщения. Позволяет фильтровать сообщения по
уровню важности (отображаются события, чей уровень важности совпадает с
выбранным или выше). В журнале записываются события следующих уровней важности
(в порядке возрастания важности):
Time (GMT)
— дата и время события в формате yyyy-mm-dd hh:mm:ss
. Время
отображается в часовом поясе GMT+0 (или UTC);
Subject ID
— внутренний идентификатор субъекта доступа;
Subject
— тип и наименование субъекта доступа;
Request ID
— идентификатор запроса;
Module
— имя модуля системы, инициировавшего событие;
Message
— описание события.
Для удобства поиска записей в журнале можно использовать фильтры. Фильтры
существуют для каждой из колонок таблицы. Возможно использование фильтров
по нескольким колонкам совместно.
По умолчанию используется единственный фильтр — по полю Severity
,
установленный на уровень Info
. Для фильтрации по другому уровню важности
выберите подходящий уровень из выпадающего списка, задайте фильтры по
другим полям, если необходимо, и нажмите кнопку Apply
для применения
выбранных фильтров. Для возвращения к фильтру по умолчанию — нажмите кнопку
Reset
.
При входе на страницу Audit log в таблице отображаются не более 100 записей.
Если нужно увеличить количество записей, выводимых на экран, нажмите кнопку
Show more
внизу, под таблицей.
Также возможно выгрузить все отображаемые в данный момент в таблице
записи журнала аудита в виде текстового файла в формате .txt
.
Для этого нажмите кнопку Save
внизу, под таблицей.
2.11.1. Состав событий безопасности
В журнал аудита TDG записываются следующие события.
2.11.1.1. VERBOSE
Следующие события имеют уровень важности Verbose
и не отображаются при
фильтрации по любому другому уровню.
Сообщение о предоставлении доступа по имени токена: Access granted by token
Сообщение об отклонении доступа внешним модулем авторизации: Access denied by external auth: %REASON%
Сообщение о предоставлении доступа внешним модулем авторизации: Access granted by external token
Сообщение о предоставлении доступа токену: Access granted by token
2.11.1.2. INFO
Следующие события имеют уровень важности Info
и отображаются при фильтрации
по уровням Info
и Verbose
.
Сообщение о создании токена: Token %UID% created
Сообщение об изменении статуса токена: %TOKEN_NAME% state is changed to %STATE%
Сообщение об обновлении токена: Token %UID% updatedn%UPDATE_LIST%
Сообщение об удалении токена: Token %UID% removed
Сообщение о создании пользователя: User %UID% created
Сообщение об изменении статуса пользователя: %USER_EMAIL% state is changed to %STATE%
Сообщение об обновлении пользователя: User %UID% deleted
Сообщение об удалении пользователя: User %UID% updatedn%UPDATE_LIST%
Сообщение о применении новой конфигурации к генератору/валидатору паролей: New configuration for password generator has been applied
Сообщение об успешном вводе пароля пользователя: Correct password for user %UID%
Сообщение о попытке применения нового конфига: Try to upload a config
Сообщение об успешном применении нового конфига: Config applied
Сообщение о скачивании конфига: Config downloaded
Сообщение об успешном применении новой модели: Model applied
Сообщение об использовании функции eval (запрос и результат): Result of eval %CODE% is %RESULT%
Сообщение о предоставлении доступа по cookies пользователя: Access granted to user
Сообщение о предоставлении анонимного доступа: Access granted to anonymous user
Сообщение об изменении hard-limit’а: hard-limits changed. Old value: %OLD%, new value: %NEW%
Сообщение об изменении vshard-timeout’а: vshard-timeout changed. Old value: %OLD%, new value: %NEW%
Сообщение об изменении force-yield-limit’а: force-yield-limit changed. Old value: %OLD%, new value: %NEW%
Сообщение об изменении graphql-query-cache-size’а: graphql-query-cache-size changed. Old value: %OLD%, new value: %NEW%
Сообщение об изменении настроек времени жизни объектов: Expiration settings changed. Old values: %OLD%, new values: %NEW%
2.11.1.3. WARNING
Следующие события имеют уровень важности WARNING
и отображаются при фильтрации
по всем уровням, кроме ALARM
.
Сообщение о вводе неправильного пароля пользователя: Incorrect password for user %UID%
Сообщение о попытке использования неизвестного имени токена: Access denied. Unknown token %TOKEN_NAME%
Сообщение о неизвестной ошибке при использовании имени токена: Access denied. Some error has occurred with token %TOKEN_NAME%
Сообщение об ошибке при использовании внешнего модуля авторизации: Error while performing external authentication: %ERROR%
Сообщение о попытке использования неизвестного токена: Attempt to authorize with token, but token %TOKEN% is unknown
Сообщение о неизвестной ошибке при использовании токена: Attempt to authorize with token %TOKEN%, but some error has occurred
Сообщение о попытке использования заблокированного токена: Attempt to authorize with token, token %TOKEN% blocked
Сообщение о попытке использования cookies неизвестного пользователя: Attempt to authorize with cookies, but user %LOGIN% is unknown
Сообщение о неизвестной ошибке при использовании cookies пользователя: Attempt to authorize with cookies for login %LOGIN%, but some error has occurred
Сообщение о попытке использования cookies заблокированного пользователя: Attempt to authorize with cookies, but user %LOGIN% blocked
Сообщение об отклонении доступа: Access denied
2.11.1.4. ALARM
Следующие события имеют уровень важности ALARM
и отображаются при фильтрации
по любому из уровней.
2.12. Репликация объектов
Механизм репликации объектов позволяет отправлять объекты во внешние системы в
нужном формате.
Для работы репликации в системе должна быть определена роль output_processor
и
настроена конфигурация роли.
После успешной обработки на роли input_processor
объект направляется в хранилище
(роль storage
) с определенным ключом маршрутизации. Если в конфигурации
системы для данного ключа предусмотрена репликация, объект также отправляется в
очередь репликации. Далее объект проходит так называемый preprocessing —
обрабатывается в пайплайне, указанном в конфигурации
для роли output_processor
, и отправляется во внешнюю систему при
помощи роли connector
, где уже определен endpoint внешней системы.
Также возможно настроить репликацию определенных типов объектов, попавших в
ремонтную очередь. Это тоже настраивается в конфигурации
системы.
Если во время репликации объекта произошла ошибка, объект попадает в
специальную ремонтную очередь репликации. Ее функционал идентичен
ремонтной очереди, но различие в том, что ремонтная
очередь репликации
содержит объекты, которые не удалось реплицировать, а не объекты, которые не
удалось сохранить. Администрировать объекты в ремонтной очереди репликации можно
через web-интерфейс на вкладке Output_Processor.
Информация об объектах в этой ремонтной очереди, а также операции над ними
(фильтрация, Try again, Delete и т.д.) аналогичны информации и операциям
в основной ремонтной очереди. Отличаются только статусы
объектов в этих двух очередях. В ремонтную очередь репликации объекты попадают
в результате двух типов ошибок:
При повторной операции (Try again) над объектом его статус меняется на
«In Progress». Если повторная операция успешна, объект переходит на следующий
этап обработки или удаляется из ремонтной очереди (в зависимости от предыдущего
статуса). Если повторная операция завершилась ошибкой, статус объекта меняется
на «Rereplicated (Preprocessing error)» или «Rereplicated (Sending error)»
(в зависимости от предыдущего статуса) и объект остается в ремонтной очереди
репликации.
2.13. Задачи и отложенные работы
Задачи суть те же пайплайны с набором функций,
которые могут быть применены к
сохраненным объектам или для любых других действий в системе (например, создание
отчетов, инвалидация кэшированных данных и др.) и запущены в любое время, в т.ч.
по расписанию.
Для выполнения задач в системе должны быть определены роли task_runner
и
scheduler
и настроена их конфигурация.
Отслеживать текущее состояние задач и управлять их выполнением можно через
web-интерфейс на вкладке Tasks.
ID — UUID экземпляра задачи;
Name — Имя задачи;
Kind — Вид задачи:
single_shot — единоразовая задача;
continuous — непрерывно выполняемая задача;
periodical — задача, выполняемая по расписанию;
Schedule — Расписание выполнения задачи. Актуально только для задач вида
«periodical»;
Started — Дата и время старта экземпляра задачи;
Finished — Дата и время окончания экземпляра задачи;
Status — Текущий статус задачи:
did not start;
pending;
running;
stopped;
failed;
completed;
Result — Сообщение о результате завершенной задачи (в статусе «stopped», или
«failed», или «completed»);
Action — Возможные действия для управления выполнением задач:
Start
— запустить новый экземпляр неактивной задачи (задача в статусе
«did not start» или «pending» — подсвечены зеленым в web-интерфейсе);
Stop
— прекратить работу активного экземпляра задачи (в статусе «running»);
Hide
— скрыть информацию об экземпляре задачи, завершившем свою работу
(в статусе «stopped», или «failed», или «completed»).
Имя, вид и расписание выполнения задач определяются в конфигурации системы.
Информацию о конкретном экземпляре задачи можно получить в отдельном pop-up
окне, которое выводится по клику на UUID задачи в колонке ID.
Отложенные работы (jobs) по сути аналогичны задачам
(являются пайплайнами обработки объектов). Но в отличие от задач, которые
задаются и настраиваются в конфигурации системы,
отложенные работы задаются и вызываются в клиентском программном коде:
функция push_job
программного интерфейса репозитория. Например:
local params = ...
local obj = params.obj
if obj.id ~= '26DA4133-0F97-44E8-83E6-95BA7646FC02' then
repository.push_job('bad_job')
else
for i = 1, obj.repeats do
repository.push_job('sum', {obj.id, obj.initial, i})
end
end
return params
Web-интерфейс позволяет вести мониторинг отложенных работ, которые завершились
с ошибкой, — вкладка Failed Jobs. Элементы web-интенфейса и набор операций,
доступные на этой вкладке, аналогичны элементам и операциям ремонтной очереди
на вкладке Repair (см. подробнее).
В конфигурации системы также можно задать:
2.15. Метрики
Для мониторинга работы TDG предоставляются метрики в формате Prometheus.
Для каждого из экземпляров кластера значения метрик доступны по адресу:
http://<IP_адрес_экземпляра>/metrics
. В системе-сборщике метрик необходимо
подать на вход адреса для сбора метрик со всех экземпляров кластера.
Все доступные метрики можно разделить на несколько категорий:
Используются следующие типы метрик Prometheus:
counter — монотонно возрастающий счетчик;
gauge — метрика для числовых значений;
histogram — метрика для оценки интенсивности потока во времени.
Подробнее про типы метрик см. в официальной документации Prometheus.
2.15.1. Метрики TDG
2.15.1.1. Метрики запросов GraphQL
Для мониторинга и оценки запросов GraphQL предоставляются следующие метрики:
tdg_graphql_query_time{alias,schema,entity,operation_name}
— время обработки
запроса на получение данных (query), миллисекунды. Тип метрики: histogram;
tdg_graphql_mutation_time{alias,schema,entity,operation_name}
— время обработки
запроса на изменения данных (mutation), миллисекунды. Тип метрики: histogram;
tdg_graphql_query_fail{alias,schema,entity,operation_name}
— количество запросов
на получение данных (query) c ошибками. Тип метрики: counter;
tdg_graphql_mutation_fail{alias,schema,entity,operation_name}
— количество
запросов на изменение данных (mutation) c ошибками. Тип метрики: counter.
Бакеты (bucket) гистограмм распределены в диапазоне
от 0 до 1000 миллисекунд с интервалом в 100 миллисекунд (см. пример ниже).
Каждая из метрик имеет следующие тэги:
alias
— имя экземпляра, на котором собираются метрики. Имя экземпляра было
задано при развертывании кластера;
schema
— имя схемы (default
или admin
), в которую поступил запрос
GraphQL;
entity
— сущность, над которой производится операция;
operation_name
— имя запроса GraphQL (может отсутствовать, если имя
запроса не было задано). Рекомендуется указывать имена для всех запросов,
чтобы можно было однозначно идентифицировать, к какому запросу относится
информация в метрике.
Вызов сервиса аналогичен запросу (query) для сущности.
В данном случае в тэг entity
будет записано имя сервиса.
Каждый запрос может состоять из нескольких операций, которые, в свою очередь,
могут состоять из получения/модификации нескольких сущностей. В этом случае по
каждой сущности будет выдана отдельная метрика со своим набором тэгов.
Пример:
# HELP tdg_graphql_query_time Graphql query execution time
# TYPE tdg_graphql_query_time histogram
tdg_graphql_query_time_bucket{alias="core_1",schema="default",entity="City",operation_name="GetCity",le="100"} 25
tdg_graphql_query_time_bucket{alias="core_1",schema="default",entity="City",operation_name="GetCity",le="200"} 25
tdg_graphql_query_time_bucket{alias="core_1",schema="default",entity="City",operation_name="GetCity",le="300"} 25
tdg_graphql_query_time_bucket{alias="core_1",schema="default",entity="City",operation_name="GetCity",le="400"} 25
tdg_graphql_query_time_bucket{alias="core_1",schema="default",entity="City",operation_name="GetCity",le="500"} 25
tdg_graphql_query_time_bucket{alias="core_1",schema="default",entity="City",operation_name="GetCity",le="600"} 25
tdg_graphql_query_time_bucket{alias="core_1",schema="default",entity="City",operation_name="GetCity",le="700"} 25
tdg_graphql_query_time_bucket{alias="core_1",schema="default",entity="City",operation_name="GetCity",le="800"} 25
tdg_graphql_query_time_bucket{alias="core_1",schema="default",entity="City",operation_name="GetCity",le="900"} 25
tdg_graphql_query_time_bucket{alias="core_1",schema="default",entity="City",operation_name="GetCity",le="1000"} 25
tdg_graphql_query_time_bucket{alias="core_1",schema="default",entity="City",operation_name="GetCity",le="+Inf"} 25
tdg_graphql_query_time_sum{alias="core_1",schema="default",entity="City",operation_name="GetCity"} 55
tdg_graphql_query_time_count{alias="core_1",schema="default",entity="City",operation_name="GetCity"} 25
# HELP tdg_graphql_mutation_time Graphql mutation execution time
# TYPE tdg_graphql_mutation_time histogram
tdg_graphql_mutation_time_bucket{alias="core_1",schema="default",entity="City",operation_name="InsCity",le="100"} 16
tdg_graphql_mutation_time_bucket{alias="core_1",schema="default",entity="City",operation_name="InsCity",le="200"} 16
tdg_graphql_mutation_time_bucket{alias="core_1",schema="default",entity="City",operation_name="InsCity",le="300"} 16
tdg_graphql_mutation_time_bucket{alias="core_1",schema="default",entity="City",operation_name="InsCity",le="400"} 16
tdg_graphql_mutation_time_bucket{alias="core_1",schema="default",entity="City",operation_name="InsCity",le="500"} 16
tdg_graphql_mutation_time_bucket{alias="core_1",schema="default",entity="City",operation_name="InsCity",le="600"} 16
tdg_graphql_mutation_time_bucket{alias="core_1",schema="default",entity="City",operation_name="InsCity",le="700"} 16
tdg_graphql_mutation_time_bucket{alias="core_1",schema="default",entity="City",operation_name="InsCity",le="800"} 16
tdg_graphql_mutation_time_bucket{alias="core_1",schema="default",entity="City",operation_name="InsCity",le="900"} 16
tdg_graphql_mutation_time_bucket{alias="core_1",schema="default",entity="City",operation_name="InsCity",le="1000"} 16
tdg_graphql_mutation_time_bucket{alias="core_1",schema="default",entity="City",operation_name="InsCity",le="+Inf"} 16
tdg_graphql_mutation_time_sum{alias="core_1",schema="default",entity="City",operation_name="InsCity"} 34
tdg_graphql_mutation_time_count{alias="core_1",schema="default",entity="City",operation_name="InsCity"} 16
# HELP tdg_graphql_query_fail Graphql query fail count
# TYPE tdg_graphql_query_fail counter
tdg_graphql_query_fail{alias="core_1",schema="default",entity="City",operation_name="GetCity"} 2
# HELP tdg_graphql_mutation_fail Graphql mutation fail count
# TYPE tdg_graphql_mutation_fail counter
tdg_graphql_mutation_fail{alias="core_1",schema="default",entity="City",operation_name="InsCity"} 4
Чтобы получить информацию о среднем количестве запросов GraphQL в секунду
из Prometheus, воспользуйтесь запросом
rate(tdg_graphql_query_time_count[2m])
Период, по которому вычисляется rate()
(в примере — 2m
),
должен быть как минимум в два раза больше периода сбора метрик.
Если вы добавляете панель на стандартный Grafana Tarantool dashboard,
воспользуйтесь переменной $rate_time_range
.
Среднее время выполнения запроса GraphQL можно получить с помощью
rate(tdg_graphql_query_time_sum[2m])/rate(tdg_graphql_query_time_count[2m])
95-й перцентиль времени выполнения запроса GraphQL можно получить с помощью
histogram_quantile(0.95, sum(rate(tdg_graphql_query_time_bucket[2m])) by (le))
2.15.1.2. Метрики системного администрирования
tdg_cluster_clock_delta{alias,uuid}
— разница во времени между локальными
часами (часы экземпляра, на котором собираются метрики) и часами другого экземпляра
в кластере, секунды. Положительное значение указывает на то, что часы другого
экземпляра опережают локальные. Отрицательное значение — на обратную ситуацию.
Тип метрики: gauge.
Тэги метрики:
uuid
— UUID экземпляра, разницу во времени с которым определяет
метрика;
alias
— имя экземпляра, на котором собираются метрики.
В конфигурации системы при помощи параметра clock_delta_threshold_sec
можно задать максимально допустимую рассинхронизацию по времени.
При превышении этого порога в журнал будет выведено сообщение об ошибке.
Подробнее см. в описании параметра
Если несколько экземпляров расположены на одном физическом сервере, разница во
времени между ними, как правило, будет очень небольшой, т.к. каждый из этих
экземпляров «смотрит» на одни и те же часы — часы физической машины. Разницу во
времени в этом случае можно считать сетевой погрешностью. Однако
если экземпляры расположены на нескольких физических серверах, метрика может
диагностировать ситуацию, когда между экземплярами этих серверов временная
разница значительная, что может указывать или на рассинхронизацию часов
разных физических серверов, или на сетевые проблемы.
Пример ниже иллюстрирует
подобную ситуацию: в кластере развернуты 7 экземпляров (см.
топологию кластера); мы собираем метрики на
экземпляре с именем «core_1», у которого большая временная дельта одного порядка
с тремя экземплярами, развернутыми на другой физической машине.
Пример:
# HELP tdg_cluster_clock_delta The time difference in cluster
# TYPE tdg_cluster_clock_delta gauge
tdg_cluster_clock_delta{uuid="1ab7778c-26f6-4424-b8cd-1daf1d93fc70",alias="core_1"} -0.0001205
tdg_cluster_clock_delta{uuid="76fb5acd-65fd-43f0-8d57-6622aafdc5aa",alias="core_1"} 3.5e-05
tdg_cluster_clock_delta{uuid="a87d0c8c-aced-4c5c-9880-a23cd4edbc01",alias="core_1"} -12.851604
tdg_cluster_clock_delta{uuid="401415dd-4d4d-4c29-9342-fb3efbee5d12",alias="core_1"} 4.75e-05
tdg_cluster_clock_delta{uuid="2e4bb162-d2e7-43f5-9ca9-530c60b2712d",alias="core_1"} 0.00029
tdg_cluster_clock_delta{uuid="b82e8c4f-b522-414c-9e51-a850db7302b1",alias="core_1"} -12.8515295
tdg_cluster_clock_delta{uuid="7acc17ad-aa8a-4954-b13f-3938964c9f41",alias="core_1"} -12.850967
2.15.1.3. Метрики для задач и отложенных работ
В системе TDG доступны метрики для задач (tasks) и отложенных работ (jobs).
Метрики актуальны только для экземпляров с ролью task_runner
, так как именно на этих экземплярах
запускаются задачи и отложенные работы.
Метрики задач имеют следующие тэги:
Метрики отложенных работ и системных задач имеют только тэги alias
и name
.
В TDG версий 1.6.x и 1.7.x есть только одна системная задача (system task) — это задача по архивации
(читать подробнее про секцию archivation в файле конфигурации).
tdg_tasks_started
— показывает, сколько всего запущено задач. Тип метрики: counter.
По аналогии: tdg_jobs_started
— число запущенных отложенных работ, tdg_system_tasks_started
— число запущенных
системных задач.
Пример:
# HELP tdg_system_tasks_started Total system tasks started
# TYPE tdg_system_tasks_started counter
tdg_tasks_started{alias="runner_1",name="districts_stat.calc_statistics.call",kind="periodical"} 2
# HELP tdg_jobs_started Total jobs started
# TYPE tdg_jobs_started counter
tdg_jobs_started{name="succeed",alias="runner_1"} 1
# HELP tdg_system_tasks_started Total system tasks started
# TYPE tdg_system_tasks_started counter
tdg_system_tasks_started{name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_tasks_failed
— показывает, сколько задач завершились с ошибкой. Тип метрики: counter.
По аналогии: tdg_jobs_failed
— число отложенных работ, которые завершились с ошибкой.
Пример:
# HELP tdg_tasks_failed Total tasks failed
# TYPE tdg_tasks_failed counter
tdg_tasks_succeeded{alias="runner_1",name="districts_stat.calc_statistics.call",kind="periodical"} 1
# HELP tdg_jobs_failed Total jobs failed
# TYPE tdg_jobs_failed counter
tdg_jobs_failed{name="fail",alias="runner_1"} 2
tdg_tasks_succeeded
— показывает, сколько задач было успешно выполнено. Тип метрики: counter.
По аналогии: tdg_jobs_succeeded
— число успешно выполненных отложенных работ,
tdg_system_tasks_succeeded
— число успешно выполненных системных задач.
Пример:
# HELP tdg_tasks_succeeded Total tasks succeeded
# TYPE tdg_tasks_succeeded counter
tdg_tasks_succeeded{alias="runner_1",name="districts_stat.calc_statistics.call",kind="periodical"} 2
# HELP tdg_jobs_succeeded Total jobs succeeded
# TYPE tdg_jobs_succeeded counter
tdg_jobs_succeeded{name="succeed",alias="runner_1"} 1
# HELP tdg_system_tasks_succeeded Total system tasks succeeded
# TYPE tdg_system_tasks_succeeded counter
tdg_system_tasks_succeeded{name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_tasks_stopped
— показывает, сколько задач было приостановлено. Тип метрики: counter.
Пример:
# HELP tdg_tasks_stopped Total tasks stopped
# TYPE tdg_tasks_stopped counter
tdg_tasks_stopped{alias="runner_1",name="districts_stat.calc_statistics.call",kind="periodical"} 2
tdg_tasks_running
— показывает, сколько задач запущено в данный момент. Тип метрики: gauge.
По аналогии: tdg_jobs_running
— число отложенных работ, запущенных в данных момент,
tdg_system_tasks_running
— число системных задач, запущенных в данный момент.
Пример:
# HELP tdg_tasks_running Currently running tasks
# TYPE tdg_tasks_running gauge
tdg_tasks_running{alias="runner_1",name="districts_stat.calc_statistics.call",kind="periodical"} 0
# HELP tdg_jobs_running Currently running jobs
# TYPE tdg_jobs_running gauge
tdg_jobs_running{name="succeed",alias="runner_1"} 0
# HELP tdg_system_tasks_running Currently running system tasks
# TYPE tdg_system_tasks_running gauge
tdg_system_tasks_running{name="tasks.system.archivation.start",alias="tnt_net_external_1_runner_1_1"} 0
tdg_tasks_execution_time
— показывает статистику по времени исполнения задачи. Тип метрики: histogram.
По аналогии: tdg_jobs_execution_time
— статистика по времени исполнения отложенной работы,
tdg_system_tasks_execution_time
— статистика по времени исполнения системной задачи.
Бакеты (bucket) гистограмм распределены в диапазоне
от 0 до 5 секунд: 0.0001, 0.00025, 0.0005, 0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5.
Пример:
# HELP tdg_tasks_execution_time Tasks execution time statistics
# TYPE tdg_tasks_execution_time histogram
tdg_tasks_execution_time_count{alias="runner_1",name="calc_districts_stat",kind="periodical"} 2
tdg_tasks_execution_time_sum{alias="runner_1",name="calc_districts_stat",kind="periodical"} 0.014632841999969
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="0.0001",kind="periodical"} 0
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="0.00025",kind="periodical"} 0
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="0.0005",kind="periodical"} 0
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="0.001",kind="periodical"} 0
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="0.0025",kind="periodical"} 1
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="0.005",kind="periodical"} 1
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="0.01",kind="periodical"} 1
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="0.025",kind="periodical"} 2
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="0.05",kind="periodical"} 2
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="0.1",kind="periodical"} 2
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="0.25",kind="periodical"} 2
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="0.5",kind="periodical"} 2
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="1",kind="periodical"} 2
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="2.5",kind="periodical"} 2
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="5",kind="periodical"} 2
tdg_tasks_execution_time_bucket{alias="runner_1",name="calc_districts_stat",le="+Inf",kind="periodical"} 2
# HELP tdg_jobs_execution_time Jobs execution time statistics
# TYPE tdg_jobs_execution_time histogram
tdg_jobs_execution_time_count{name="succeed",alias="runner_1"} 1
tdg_jobs_execution_time_sum{name="succeed",alias="runner_1"} 1.0725110769272e-05
tdg_jobs_execution_time_bucket{le="0.0001",name="succeed",alias="runner_1"} 1
tdg_jobs_execution_time_bucket{le="0.00025",name="succeed",alias="runner_1"}1
tdg_jobs_execution_time_bucket{le="0.0005",name="succeed",alias="runner_1"} 1
tdg_jobs_execution_time_bucket{le="0.001",name="succeed",alias="runner_1"} 1
tdg_jobs_execution_time_bucket{le="0.0025",name="succeed",alias="runner_1"} 1
tdg_jobs_execution_time_bucket{le="0.005",name="succeed",alias="runner_1"} 1
tdg_jobs_execution_time_bucket{le="0.01",name="succeed",alias="runner_1"} 1
tdg_jobs_execution_time_bucket{le="0.025",name="succeed",alias="runner_1"} 1
tdg_jobs_execution_time_bucket{le="0.05",name="succeed",alias="runner_1"}
tdg_jobs_execution_time_bucket{le="0.1",name="succeed",alias="runner_1"} 1
tdg_jobs_execution_time_bucket{le="0.25",name="succeed",alias="runner_1"} 1
tdg_jobs_execution_time_bucket{le="0.5",name="succeed",alias="runner_1"} 1
tdg_jobs_execution_time_bucket{le="1",name="succeed",alias="runner_1"} 1
tdg_jobs_execution_time_bucket{le="2.5",name="succeed",alias="runner_1"} 1
tdg_jobs_execution_time_bucket{le="5",name="succeed",alias="runner_1"} 1
tdg_jobs_execution_time_bucket{le="+Inf",name="succeed",alias="runner_1"} 1
# HELP tdg_system_tasks_execution_time System tasks execution time statistics
# TYPE tdg_system_tasks_execution_time histogram
tdg_system_tasks_execution_time_count{name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_system_tasks_execution_time_sum{name="tasks.system.archivation.start",alias="runner_1"} 0.052489631809294
tdg_system_tasks_execution_time_bucket{le="0.0001",name="tasks.system.archivation.start",alias="runner_1"} 631
tdg_system_tasks_execution_time_bucket{le="0.00025",name="tasks.system.archivation.start",alias="runner_1"} 715
tdg_system_tasks_execution_time_bucket{le="0.0005",name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_system_tasks_execution_time_bucket{le="0.001",name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_system_tasks_execution_time_bucket{le="0.0025",name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_system_tasks_execution_time_bucket{le="0.005",name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_system_tasks_execution_time_bucket{le="0.01",name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_system_tasks_execution_time_bucket{le="0.025",name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_system_tasks_execution_time_bucket{le="0.05",name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_system_tasks_execution_time_bucket{le="0.1",name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_system_tasks_execution_time_bucket{le="0.25",name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_system_tasks_execution_time_bucket{le="0.5",name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_system_tasks_execution_time_bucket{le="1",name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_system_tasks_execution_time_bucket{le="2.5",name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_system_tasks_execution_time_bucket{le="5",name="tasks.system.archivation.start",alias="runner_1"} 718
tdg_system_tasks_execution_time_bucket{le="+Inf",name="tasks.system.archivation.start",alias="runner_1"} 718
2.16. Конфигурация системы (config.yml)
config.yml
— основной файл конфигурации системы, в котором задана логика и
порядок обработки входящих запросов, а также настройки кластерных ролей
и других функций системы.
Файл загружается в систему при первоначальной конфигурации
после установки системы и настройки кластера.
Рассмотрим структуру файла и логику настроек на
примере ниже. В примере в качестве справочной информации приведены все возможные
секции и параметры.
Все секции в файле конфигурации являются опциональными, и
в реальной ситуации задаются только те настройки,
которые необходимы для решения определенных бизнес-задач.
---
types: {__file: model.avsc}
functions:
focus_decode: {__file: focus_decode.lua}
focus_routing: {__file: focus_routing.lua}
validate_coupon_payment: {__file: validate_coupon_payment.lua}
focus_classifier: {__file: focus_classifier.lua}
notification: {__file: notification.lua}
single_task: {__file: single_task.lua}
long_task: {__file: long_task.lua}
pipelines:
connect_input_handle:
- focus_routing
coupon_payment_handle:
- focus_decode
- validate_coupon_payment
focus_classifiers:
- focus_classifier
notification_filter:
- notification
single_task:
- single_task
long_task:
- long_task
connector:
input:
- name: soap
type: soap
wsdl: {__file: Connect.wsdl}
success_response_body: {__file: success_response_body.xml}
error_response_body: {__file: error_response_body.xml}
handlers:
- function: Connect
pipeline: connect_input_handle
- name: http
type: http
pipeline: connect_input_handle
- name: kafka
type: kafka
brokers:
- localhost:9092
topics:
- orders
- items
group_id: kafka
token_name: kafka_token
pipeline: connect_input_handle
output:
- name: to_input_processor
type: input_processor
- name: to_external_http_service
type: http
url: http://localhost:8021/test_json_endpoint
format: json
- name: to_external_soap_service
type: soap
url: http://localhost:8020/test_soap_endpoint
- name: to_kafka
type: kafka
brokers:
- localhost:9092
topic: objects
- name: to_smtp
type: smtp
url: localhost:2525
from: tdg@localhost
subject: TDG_Objects
timeout: 5
ssl_cert: ssl.crt
ssl_key: ssl.pem
- name: dummy
type: dummy
routing:
- key: input_processor
output: to_input_processor
- key: external_http_service
output: to_external_http_service
- key: external_soap_service
output: to_external_soap_service
- key: dummy
output: dummy
input_processor:
classifiers:
- name: focus
pipeline: focus_classifiers
routing:
- key: focus_couponpayment
pipeline: coupon_payment_handle
- key: focus_initiation
pipeline: focus_catchall_handle
- key: focus_ratechange
pipeline: focus_catchall_handle
storage:
- key: focus_couponpayment
type: CouponPayment
output_processor:
focus_couponpayment:
pipeline: notification_filter
output: to_external_http_service
unclassified:
pipeline: notification_filter
output: to_external_http_service
repair_queue:
on_object_added:
__unclassified__:
postprocess_with_routing_key: unclassified
logger:
rotate_log: true
max_msg_in_log: 500000
max_log_size: 10485760
delete_by_n_msg: 1000
audit_log:
enabled: true
severity: INFO
remove_older_than_n_hours: 12
tasks:
task_1:
kind: single_shot
pipeline: single_task
keep: 5
task_2:
kind: continuous
pipeline: long_task
pause_sec: 10
task_3:
kind: periodical
pipeline: long_task
schedule: "0 */5 * * * *"
task_runner:
running_count_threshold: 100
jobs:
max_jobs_in_parallel: 100
account_manager:
only_one_time_passwords: true
output:
name: to_smtp
options:
subject: "Registration"
password_change_timeout_seconds: 10
block_after_n_failed_attempts: 5
ban_inactive_more_seconds: 86400
password_policy:
min_length: 8
include:
lower: true
upper: true
digits: true
symbols: false
pepper: 2d60ec7f-e9f0-4018-b354-c54907b9423d
auth_external: {__file: auth.lua}
notifier:
mail_server:
url: 127.0.0.1:2525
from: TDG_repair_queue
username: user
password: passpass
timeout: 5
skip_verify_host: true
users:
- id: 1
name: Petrov
addr: petrov@mailserver.com
services:
get_price:
doc: "Get the item price by ID"
function: get_price_by_id
return_type: ItemPrice
args:
item_id: string
expiration:
- type: CouponPayment
lifetime_hours: 12
delay_sec: 1800
keep_version_count: 5
archivation:
- type: Quotation
lifetime_days: 7
schedule: "0 0 0 */1 * *"
dir: "/var/data"
file_size_threshold: 104857600
hard-limits:
scanned: 2000
returned: 100
force_yield_limits: 1000
graphql_query_chache_size: 1000
vshard-timeout: 2
maintenance:
clock_delta_threshold_sec: 5
gc:
forced: true
period_sec: 2
steps: 20
tracing:
base_url: localhost:9411/api/v2/spans
api_method: POST
report_interval: 0
spans_limit: 100
sequence_generator:
starts_with: 1
range_width: 100
test-soap-data: {__file: test_object.json}
libraries:
cache:
__file: cached.lua
utils:
__file: utils.lua
welcome-message: |
Hello! Let's start working with Tarantool Data Grid.
tdg-version: "== 1.7.0"
2.16.1. Секции
В файле конфигурации могут быть настроены следующие секции:
2.16.1.1. types
Описание модели данных (типов объектов), которые будут сохраняться
в системе. В качестве языка описания модели используется Avro Schema
— см.
подробнее раздел «Разработка доменной модели».
Как правило,
описание делается в отдельном файле с расширением .avsc
, на который
делается ссылка в этой секции. Например:
types: {__file: model.avsc}
2.16.1.2. functions
Описание функций, которые будут выполняться в конвейерах
обработки объектов (секция pipelines). Для каждой
функции указывается имя
функции и исполняемая часть — Lua-код функции, который также обычно выносится
в отдельный файл. Например:
functions:
focus_routing: {__file: focus_routing.lua}
2.16.1.3. pipelines
Описание конвейеров обработки объектов (пайплайнов).
Для каждого пайплайна указывается имя и входящие в него функции, заданные в
секции functions. Например:
pipelines:
connect_input_handle:
- focus_routing
2.16.1.4. connector
Для кластерной роли connector
настраиваются:
input
Параметры коннекторов для получения и первоначальной обработки
(parsing) входящих запросов. Ряд параметров есть у всех типов
коннекторов:
name
— имя коннектора (произвольное);
type
— тип коннектора. Поддерживаемые типы:
http
— для запросов в формате JSON по HTTP;
soap
— для запросов в формате XML (SOAP) по HTTP;
kafka
— для интеграции с шиной данных Apache Kafka.
pipeline
— конвейер обработки входящих объектов, определенный в секции
pipelines.
Помимо этих параметров, у некоторых типов коннекторов есть параметры,
специфические только для них.
type: soap
wsdl
— схема WSDL, описывающая структуру входящего XML;
success_response_body
— шаблон ответа в случае успешной обработки запроса;
error_response_body
— шаблон ответа в случае ошибки;
handlers
— обработчики входящего запроса.
Примечание
Шаблоны ответа опциональны (параметры success_response_body
и
error_response_body
).
Пользователь может задать шаблоны в нужном ему формате,
соответствующем системе, в которую отправляется ответ; сам TDG
ограничений на формат не накладывает. В шаблоне error_response_body
возможно использовать один спецификатор форматирования %s
,
чтобы передать в тело ответа текст ошибки.
Например:
connector:
input:
- name: soap
type: soap
wsdl: {__file: Connect.wsdl}
success_response_body: {__file: success_response_body.xml}
error_response_body: {__file: error_response_body.xml}
handlers:
- function: Connect
pipeline: connect_input_handle
type: kafka
brokers
— адреса (URL) брокеров сообщений;
topics
— топики (topics) в терминологии Kafka;
group_id
— идентификатор группы подписчиков;
token_name
— имя токена приложения.
Необходимо вначале сгенерировать токен в системе (см.
описание процедуры) и далее указать имя токена (name
) в
качестве значения параметра. Токен будет использоваться для
авторизации при обработке входящих сообщений, которые коннектор забирает из
шины данных Apache Kafka. Поскольку формат сообщений шины не дает
возможности передать токен в самом сообщении, токен указывается в конфигурации
коннектора;
options
— служит для передачи опций в библиотеку librdkafka
.
workers_count
— задает количество читателей сообщений (workers), которые будут работать на коннекторе. Значение по умолчанию: 10
.
Параметр может быть полезен для случая, когда нужно гарантированно сохранить порядок чтения входящих сообщений.
Если читателей сообщений несколько, то будет идти параллельная обработка нескольких сообщений сразу, и их порядок может быть нарушен.
Чтобы гарантировать сохранение порядка, необходимо в кластере оставить один connector
и один input_processor
(подробнее про эти роли экземпляров TDG см. в главе Роли) и задать значение workers_count: 1
.
Для типа Kafka в конфигурации можно задать более одного входящего коннектора.
Например:
connector:
input:
- name: kafka-1
type: kafka
brokers:
- localhost:9092
topics:
- orders-1
- items-1
group_id: kafka
token_name: kafka_token
pipeline: connect_input_handle
workers_count: 1
- name: kafka-2
type: kafka
brokers:
- localhost:9092
topics:
- orders-2
- items-2
group_id: kafka
token_name: kafka_token
pipeline: connect_input_handle
workers_count: 1
output
Параметры коннекторов для отправки исходящих запросов.
Ряд параметров есть у всех типов коннекторов:
name
— имя коннектора (произвольное).
type
— тип коннектора. Поддерживаемые типы:
input_processor
— для отправки объекта на роль input_processor
;
http
— для отправки объекта в формате JSON во внешнюю систему по HTTP;
soap
— для отправки объекта в формате XML (SOAP) во внешнюю систему по
HTTP;
kafka
— для интеграции с шиной данных Apache Kafka;
smtp
— для отправки запросов через SMTP-сервер;
dummy
— для игнорирования объектов (пришедший объект никуда не
отправляется).
headers
– заголовки HTTP, которые при необходимости может установить пользователь
при отправке данных во внешнюю систему.
Помимо этих параметров, у некоторых типов коннекторов есть параметры,
специфические только для них.
type: http
url
— URL внешней системы, куда отправляется объект;
format
— формат, в котором отправляется объект. Для коннектора типа
http
формат — JSON.
options
— опции параметра opts
(из встроенного модуля Tarantool http
).
Например:
connector:
output:
- name: to_external_http_service
type: http
url: http://localhost:8021
format: json
options:
timeout: 5
keepalive_idle: 60
keepalive_interval: 60
verify_host: true
verify_peer: true
headers:
hello: world
type: soap
Например:
connector:
output:
- name: to_external_soap_service
type: soap
url: http://localhost:8020/test_soap_endpoint
headers:
config_header: header_for_soap
type: kafka
brokers
— адреса (URL) брокеров сообщений;
topic
— топик (topic) в терминологии Kafka;
format
— формат, в котором оправляется сообщение в Kafka. Возможные значения: json
и plain
.
Значение plain
может применяться для случая, когда необходимо отправить в Kafka сообщение в формате XML.
Значение по умолчанию: json
.
options
— служит для передачи опций в библиотеку librdkafka
.
is_async
— определяет режим работы TDG в качестве Kafka-продюсера:
true
— подтверждение о доставке сообщения отправляется только после того, как сообщение было действительно доставлено в Kafka.
false
— обычный асинхронный режим, когда подтверждение о доставке сообщения отправляется сразу после того, как сообщение добавлено в очередь на отправку.
Значение по умолчанию: true
.
Например:
connector:
output:
- name: to_kafka
type: kafka
brokers:
- localhost:9092
topic: objects
options:
debug: "all"
queue.buffering.max.ms: 1
is_async: true
type: smtp
url
— URL сервера SMTP;
from
— имя отправителя, которое будет показываться в поле «From»
при получении сообщения;
subject
— тема отправляемого сообщения;
timeout
— тайм-аут запроса к серверу SMTP, секунды;
ssl_cert
— путь к SSL-сертификату;
ssl_key
— путь к приватному ключу для SSL-сертификата.
Например:
connector:
output:
- name: to_smtp
type: smtp
url: localhost:2525
from: tdg@localhost
subject: TDG_Objects
timeout: 5
ssl_cert: ssl.crt
ssl_key: ssl.pem
routing
Mаршрутизация объекта для отправки через определенный
output, который
определяется в зависимости от ключа маршрутизации key
. Ключ маршрутизации
объект получает в результате обработки в пайплайне, указанном в
input. Если в input пайплайн не указан, объект
обрабатывается пайплайном по умолчанию, которые превращает объект вида
{
"obj_type": { "field_1": 1 }
}
в объект
и задает значение ключа маршрутизации как routing_key = obj_type
.
Пример конфигурации:
connector:
routing:
- key: input_processor
output: to_input_processor
- key: external_http_service
output: to_external_http_service
- key: external_soap_service
output: to_external_soap_service
2.16.1.5. input_processor
Для кластерной роли input_processor
настраиваются:
classifiers
Определение пользовательского пайплайна, выполняющего
классификацию объектов. В результате классификации объекту
присваивается нужный ключ маршрутизации, по которому определяются дальнейшие
действия по его обработке. Если пайплайн не задан, объект никак не
обрабатывается и направляется на обработку пайплайном, указанным в секции
input_processor: routing
.
routing
Маршрутизация объекта в определенный пайплайн обработки по ключу
маршрутизации (параметр key
).
storage
Настройки сохранения объекта в роли storage
по ключу
маршрутизации (параметр key
). Параметр type
определяет тип
бизнес-объекта (агрегата).
Пример маршрута (порядка обработки) объекта
Рассмотрим порядок обработки объекта на примере конфигурации выше.
1. После попадания в систему на роль connector
, объект будет обработан в
соответствии с настройками конфигурации input в секции connector.
В нашем примере мы видим, что для всех возможных вариантов input (JSON via
HHTP, XML via SOAP, Kafka) конвейер обработки указан один и тот же:
pipeline: connect_input_handle
.
2. В секции pipelines находим этот конвейер — он
состоит из выполнения одной функции.
pipelines:
connect_input_handle:
- focus_routing
3. В секции functions мы видим, что исполняемый код
этой функции находится в файле focus_routing.lua
.
functions:
...
focus_routing: {__file: focus_routing.lua}
Листинг кода:
local first = ...
local ret = {obj = first, priority = 1, routing_key = 'input_processor'}
return ret
Необработанный объект должен быть помещен в поле obj
. Объекту будет присвоен
ключ маршрутизации «input_processor».
4. Дальнейший маршрут объекта определяется настройками routing в секции
connector. Для объектов с ключом маршрутизации
«input_processor» это будет output: to_input_processor
:
connector:
routing:
- key: input_processor
output: to_input_processor
В секции connector находим данный output:
connector:
output:
- name: to_input_processor
type: input_processor
Значение параметра type
означает, что объект будет направлен на экземпляр
с ролью input_processor
.
6. При попадании на роль input_processor
объект прежде всего будет
обрабатываться в соответствии с настройками из секции ниже:
input_processor:
classifiers:
- name: focus
pipeline: focus_classifiers
В указанном здесь пайплайне будет выполняться следующий код из
focus_classifier.lua
:
local param = ...
local type_table =
{
["Initiation"] = "focus_initiation",
["Coupon Payment"] = "focus_couponpayment",
["Exercise"] = "focus_exercise",
["Rate Change"] = "focus_ratechange"
}
local node = lom.get_by_path(param.obj, 'message.header.routedata.event_type')
if node == nil then
return param.obj
end
local event_type = string.strip(node[1])
param.routing_key = type_table[event_type]
return param
где lom.get_by_path
— функция, которая берет значение из объекта по
указанному пути. При этом путь записывается с разделением при помощи точки.
Допустим, что после обработки на данном этапе наш объект получит ключ
маршрутизации «focus_couponpayment».
7. Дальнейшая обработка объекта с таким ключом маршрутизации будет определяться
следующими настройками в секции input_processor:
input_processor:
routing:
- key: focus_couponpayment
pipeline: coupon_payment_handle
storage:
- key: focus_couponpayment
type: CouponPayment
Это означает, что сначала объект будет обработан в еще одном пайплайне —
coupon_payment_handle
, а потом направлен на роль storage
как объект типа
«CouponPayment». Данный тип должен быть описан в модели данных, которая
определена в секции
types: {__file: model.avsc}
После успешной валидации по модели данных, объект будет сохранен на роли
storage
.
8. Если для данного ключа маршрутизации указаны еще какие-либо настройки в
других секциях config.yml
, объекты с этим ключом будут обработаны далее
(см. пример про секцию output_processor).
2.16.1.6. output_processor
Задается логика обработки объектов, которые будут реплицироваться во внешние
системы. Выполняется после успешного сохранения объектов на экземплярах с ролью
storage
.
Для объектов с определенным ключом маршрутизации определяется:
pipeline
— какой конвейер обработки будет применен к объекту.
output
— с какими параметрами объект будет отправлен во внешнюю систему. Все доступные параметры
можно посмотреть в секции output.
expiration_timeout
— по истечении какого времени (в секундах) объект будет удален из очереди,
если он не был обработан. Значения по умолчанию нет.
store_strategy
— значения copy
или reference
определяют то, как хранятся объекты —
кортеж или первичный ключ соответственно. При использовании копии (copy
) сохраняется кортеж,
и даже при его удалении из хранилища никакие данные не теряются. Хранение по первичному ключу
(reference
) тратит меньше ресурсов, но при этом менее надежно — если объект был вытеснен,
то при отправке он будет пропущен.
is_async
— асинхронный или синхронный режим работы. Значение по умолчанию — is_async: true
.
В синхронном режиме (is_async: false
) доступны еще два дополнительных параметра:
sync_retry_timeout
— параметр задает таймаут (в секундах), после которого повторяется отправка объекта,
с доставкой которого возникла проблема (по умолчанию sync_retry_timeout: 60
);
sync_failed_attempt_count_threshold
— параметр задает количество повторных попыток доставки.
Значения по умолчанию нет.
В рассматриваемом примере:
output_processor:
focus_couponpayment:
pipeline: notification_filter
output: to_external_http_service
Данные настройки означают, что объекты, которые на более ранних этапах
обработки получили ключ маршрутизации focus_couponpayment
, будут вначале
обработаны функциями из пайплайна notification_filter
, а затем отосланы в
соответствии с настройками указанного здесь output (смотрим эти настройки
в секции connector):
connector:
output:
- name: to_external_http_service
type: http
url: http://localhost:8021/test_json_endpoint
format: json
headers:
config_header: header_for_http
Аналогично определяется поведение роли output_processor
в отношении объектов с
другими ключами маршрутизации.
При работе с output_processor
можно переключаться между асинхронным и
синхронным режимами работы (параметр is_async
).
Значение параметра is_async: false
переводит роль output_processor
в синхронный режим.
В асинхронном режиме есть только одна очередь вывода, и в случае сбоя объект помещается
в ремонтную очередь.
В синхронном режиме для продолжения работы необходимо, чтобы все сообщения были доставлены.
Если в параметрах для отправки исходящих запросов output хотя бы с одним из получателей
возникла ошибка, то останавливается вся очередь вывода.
Попытку доставки можно повторить по истечении заданного таймаута (параметр sync_retry_timeout
),
и только для тех output
, на которых возникла проблема.
Количество повторных попыток можно настроить с помощью параметра sync_failed_attempt_count_threshold
.
Пример:
output_processor:
Type1: # ключ маршрутизации
expiration_timeout: 1000 # время, по истечении которого необработанный объект будет удален
store_strategy: copy # объекты хранятся в виде кортежей
is_async: false # выбор синхронного режима
sync_retry_timeout: 5 # параметр доступен, если is_async: false
sync_failed_attempt_count_threshold: 1000 # параметр доступен, если is_async: false
handlers: # обработчики запросов
- pipeline: output_processor
outputs: # список получателей, которым будет доставлен объект
- http_output
- http_output2
- http_output3
2.16.1.7. repair_queue
repair_queue:
on_object_added:
__unclassified__:
postprocess_with_routing_key: unclassified
В данном примере настроен случай, когда определенные объекты, попавшие в
ремонтную очередь, также должны быть реплицированы во внешнюю систему. В данном
случае ранее на этапе классификации объект не удалось классифицировать, и ему
было присвоено специальное значение ключа «__unclassified__», которым
определяются неклассифицированные объекты.
При попадании такого объекта в ремонтную очередь, согласно настройкам
postprocess_with_routing_key: unclassified
, объекту присваивается другой ключ
маршрутизации «unclassified», и с этим ключом он направляется на роль
output_processor
. Дальнейшая обработка объекта будет выполняться в
соответствии с настройками в секции output_processor:
output_processor:
unclassified:
pipeline: notification_filter
output: to_external_http_service
2.16.1.8. logger
В этой секции определяются настройки для кластерной роли logger
. Все параметры обязательные.
rotate_log
— флаг (true
/false
), осуществлять ли ротацию лога. Значения по
умолчанию нет.
max_msg_in_log
— максимальное количество сообщений, сохраняемых в логе. Значения по
умолчанию нет.
max_log_size
— максимальный размер файла лога, в байтах. Значения по
умолчанию нет.
delete_by_n_msg
— количество одновременно удаляемых сообщений при ротации
лога. При превышении значений параметров max_msg_in_log
или
max_msg_log_size
наиболее старые n
сообщений в логе удаляются за раз,
что повышает производительность по сравнению с режимом, когда старые сообщения
удаляются по одному. Значения по умолчанию нет.
2.16.1.9. audit_log
В секции задаются параметры для работы журнала аудита. Все параметры опциональные.
enabled
— включена (true
) или выключена (false
) запись событий в
журнал аудита. По умолчанию: true
.
severity
— уровень важности событий, которые будут записываться в журнал
аудита. Возможные значения по возрастанию важности:
VERBOSE
, INFO
, WARNING
, ALARM
. По умолчанию: INFO
.
При указании определенного уровня в журнал аудита будут записываться события этого
уровня и выше. Например, если задано INFO
, будут записываться события,
соответствующие уровням INFO
, WARNING
и ALARM
.
Подробнее см. список событий, соответствующий
каждому уровню.
remove_older_than_n_hours
— максимальное время хранения записей в журнале
аудита, часы. Записи старше указанного времени удаляются. Значения по
умолчанию нет.
2.16.1.10. tasks
В этой секции настраивается конфигурация задач, выполняемых при помощи ролей
scheduler
и task_runner
. Например:
tasks:
task_1:
kind: single_shot
pipeline: single_task
keep: 5
task_2:
kind: continuous
pipeline: long_task
pause_sec: 10
task_3:
kind: periodical
pipeline: long_task
schedule: "0 */5 * * * *"
Параметры конфигурации:
task_N
— имя задачи;
kind
— вид задачи: «single_shot», «continuous» или
«periodical» (см. подробнее);
pipeline
— пайплайн, определяющий, что именно делается в рамках задачи;
keep
— количество завершенных экземпляров задачи, которые сохраняются
в истории. По умолчанию: 10;
pause_sec
— пауза в выполнении задачи вида «continuous», секунды;
schedule
— расписание выполнения для задач вида «periodical». Задается в
формате cron
. Используется расширенный синтаксис, в котором минимальной
величиной является секунда:
* * * * * *
| | | | | |
| | | | | ----- День недели (0-6) (Воскресенье = 0)
| | | | ------- Месяц (1-12)
| | | --------- День (1-31)
| | ----------- Час (0-23)
| ------------- Минута (0-59)
--------------- Секунда (0-59)
В примере выше значение параметра schedule: "0 */5 * * * *"
означает, что
задача будет запускаться периодически каждые 5 минут.
2.16.1.11. task_runner
running_count_threshold
— порог количества выполняемых пайплайнов для задач
(tasks) и отложенных работ (jobs). По умолчанию: 100.
Этот параметр не ограничивает количество запущенных пайплайнов: при
превышении порога в журнале просто создается предупреждение «Too many running
pipelines. Now running %d pipelines».
2.16.1.12. jobs
max_jobs_in_parallel
— максимальное количество отложенных работ (jobs),
которые могут выполняться одновременно. По умолчанию: 50.
Когда достигается указанное ограничение, отложенные работы сверх лимита
устанавливаются в очередь и выполняются по мере возможности.
2.16.1.13. account_manager
Настройки кластерной роли account_manager
, которая обеспечивает работу
ролевой модели доступа и связанных с ней функций безопасности
(см. подробнее).
only_one_time_passwords
— флаг (true
/false
). По умолчанию:
false
. Если указано значение
true
, будет запрещена возможность вручную задать пароль пользователя
при его создании или импорте.
TDG будет автоматически генерировать
одноразовый пароль и высылать его на адрес электронной почты пользователя.
Для отсылки пароля также необходимо иметь работающий сервер SMTP, описание его
конфигурации в секции connector
(output: type: smtp
) и указание на этот output
в секции
account_manager
:
account_manager:
only_one_time_passwords: true
output:
name: to_smtp
options:
subject: "Registration"
Опционально можно указать заголовок письма, в котором высылается одноразовый
пароль (options: subject:
).
Если указано значение only_one_time_passwords: false
, то автоматическая
генерация пароля и отсылка его пользователю также будет выполняться в случае,
когда поле Password
при создании пользователя
оставлено пустым.
password_change_timeout_seconds
— минимальное время, которое должно пройти
до следующей смены пароля, секунды. Значения по умолчанию нет.
Примечание
Ограничение на смену пароля действует только для смены собственного пароля.
При изменении пароля другому пользователю данное ограничение не действует.
block_after_n_failed_attempts
— количество неудачных попыток входа в
систему, после которых пользователь будет заблокирован
(статус пользователя станет «blocked»). Значения по
умолчанию нет.
ban_inactive_more_seconds
— максимальное время, в течение
которого пользователь может быть неактивен в системе, секунды. По истечении
этого времени пользователь будет заблокирован.
Максимально возможное значение параметра — 45 дней. Если значение превышает
45 дней или параметр не задан, будет взято значение по умолчанию, равное 45
дням.
password_policy
— настройки политики паролей. Эти настройки также
можно задать через web-интерфейс.
min_length
— минимальная допустимая длина пароля. По умолчанию: 8.
include
— флаги (true
/false
), определяющие, какие категории
символов должны обязательно входить в пароль:
lower
— латинские буквы в нижнем регистре. По умолчанию: true
.
upper
— латинские буквы в верхнем регистре. По умолчанию: true
.
digits
— цифры от 0 до 9 включительно. По умолчанию: true
.
symbols
— дополнительные символы !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
.
По умолчанию: false
.
2.16.1.14. pepper
Строка символов, которая в целях усиления безопасности добавляется к паролю
перед его хэшированием. Если данный параметр не указан в конфигурации,
добавляется строка по умолчанию, определенная в коде системы.
2.16.1.15. auth_external
В настройке задается
Lua-код или путь к файлу с Lua-кодом, в котором пользователь имеет
возможность самостоятельно задать логику для авторизации входящих запросов.
Код должен вернуть таблицу с параметром auth
, в котором лежит функция для
проверки входящих запросов в формате HTTP
.
Функция возвращает либо nil
, если доступ запрещен, либо в противном
случае — объект, содержащий аутентификационную информацию.
Примечание
В версии Tarantool Data Grid, сертифицированной по требованиям доверия ФСТЭК
России, использование параметра auth_external
запрещено.
2.16.1.16. notifier
Настройки кластерной роли notifier
.
mail_server
— секция настроек сервера SMTP, который используется для
отправки уведомлений при попадании объекта в ремонтную очередь.
Данные параметры также можно задать через web-интерфейс.
url
— URL сервера SMTP;
from
— имя отправителя, которое будет показываться в поле «From»
при получении уведомления;
username
— имя пользователя сервера SMTP;
password
— пароль пользователя сервера SMTP;
timeout
— тайм-аут запроса к серверу SMTP, секунды.
skip_verify_host
— флаг (true
/false
): пропустить проверку
хоста по протоколу TLS.
users
— секция, где задаются данные подписчиков (subscribers),
которые будут получать уведомления. Подписчиков также можно создать
через web-интерфейс.
2.16.1.17. ldap
В этой секции настраивается авторизация внешних пользователей и систем через LDAP.
ldap:
- domain: 'glauth.com'
organizational_units: ['superheros']
hosts:
- localhost:3893
use_active_directory: true
use_tls: false
search_timeout: 2
roles:
- role: 'admin'
domain_groups:
- 'cn=superheros,ou=groups,dc=glauth,dc=com'
- 'cn=users,ou=groups,dc=glauth,dc=com'
domain
— доменное имя, которое используется в доменном логине пользователя (user@domain);
organizational_units
— названия организационных подразделений или групп пользователей;
hosts
— адрес для подключения к LDAP-серверу;
use_active_directory
— параметр, определяющий, используется ли Active Directory (служба каталогов Microsoft).
Если установлено значение true
, используйте email для входа в систему и атрибут Active Directory userprincipalname=email
в качестве фильтра, где <email>
— это имя для входа пользователя в формате email-адреса;
use_tls
— параметр, определяющий, используется ли TLS. Значение по умолчанию: false
;
search_timeout
— таймаут ожидания ответа от LDAP-сервера. Значение по умолчанию: 2 секунды;
roles
— описание ролей, которые будут назначаться пользователю в зависимости от LDAP-групп, в которых он состоит:
role
— роль, назначенная пользователю или внешнему приложению для авторизованного доступа и действий в системе
(читать подробнее про роли);
domain_groups
— LDAP-группы, которые соответствуют указанной выше роли. cn
— общее имя (common name),
ou
— организационное подразделение или группа пользователей (organization unit name), dc
— компонент
домена (domain component).
2.16.1.18. services
В этой секции настраивается конфигурация сервисов.
Например:
services:
get_price:
type: query
doc: "Get the item price by ID"
function: get_price_by_id
return_type: ItemPrice
args:
item_id: string
Параметры:
Имя сервиса. В примере выше — get_price
. Имя сервиса должно начинаться с латинской буквы и может содержать
латинские буквы, цифры и символ подчеркивания (_
);
type
— тип запроса GraphQL для вызова сервиса:
query
или mutation
. Если тип — query
, параметр можно не указывать.
doc
— произвольное описание сервиса;
function
— ссылка на функцию, которая выполняет данный сервис. Функция
должна быть описанна в конфигурации в секции functions;
return_type
— тип данных, который возвращается в результате выполнения
сервиса;
args
— описание аргументов, передаваемых на вход при выполнении сервиса, в
формате «имя_аргумента: тип_данных».
2.16.1.19. expiration
В секции задаются следующие параметры:
type
— тип объекта, в отношении которого задаются ограничения;
lifetime_hours
— время жизни объекта в часах. Объекты старше этой величины
будут считаться устаревшими и подлежащими удалению. Значение по умолчанию: 24;
delay_sec
— интервал в секундах, через который запускается очередная
проверка устаревших объектов и их удаление. Значение по умолчанию: 36000;
keep_version_count
— ограничение количества версий для объектов данного
типа. По умолчанию: не ограничено.
Данные настройки также можно задать через web-интерфейс.
2.16.1.20. archivation
Определенные типы объектов могут поступать в систему в большом количестве, но
при этом иметь короткое время активного использования, после чего потребность
в обращении к ним возникает редко. Для этого случая реализован механизм
архивации, когда такие объекты выгружаются из TDG и сохраняются
отдельно на жестком диске.
Объекты сохраняются в текстовом виде в файлах формата .jsonl
(формат имени — YYYY-MM-DDTHH:MM:SS.jsonl
). Объекты записываются в файл
построчно: один объект — одна строка вида {"TypeName": {... data ...}}
, где
TypeName
— тип сохраняемого объекта.
Настройки архивации в конфигурации TDG:
type
— тип объекта;
lifetime_days
– время жизни объекта в TDG, дни. Объекты старше
этой величины архивируется;
schedule
— расписание запуска задачи на архивацию в формате cron
;
dir
— директория для хранения файлов с архивными данными;
file_size_threshold
— максимальный размер файла с архивными данными, байты.
По достижении этого размера запись архивируемых данных начинается в новый файл.
По умолчанию: 104857600 (100 Мб).
2.16.1.21. hard_limits
Для контроля нагрузки на сервер задаются следующие ограничения на выполнение
GraphQL-запросов:
При превышении одного из ограничений выполнение запроса прекращается и
возвращается ошибка.
2.16.1.22. force_yield_limits
force_yield_limits
— ограничение на количество сканирований записей в
спейсе. При достижении порога выполняется yield
. По умолчанию: 1000.
Актуально при выполнении map_reduce, чтобы избежать
зависаний на экземплярах с ролью storage
. Также учитывается при работе
функций программного интерфейса репозитория.
2.16.1.23. graphql_query_chache_size
graphql_query_chache_size
— размер кэша запросов GraphQL. Измеряется в
количестве запросов, т.е. кэшируются N последних уникальных запросов в виде
полного текста каждого запроса. По умолчанию: 3000.
2.16.1.24. vshard-timeout
vshard-timeout
— время ожидания запроса, которое передается в функции
vshard.callro()/callrw()
, секунды. По умолчанию: 2.
2.16.1.25. maintenance
clock_delta_threshold_sec
— порог рассинхронизации истинного времени
(CLOCK_REALTIME) на серверах, секунды. По умолчанию: 5.
При превышении
порога в логе создается запись об ошибке «Time deviation threshold exceeded!
Max clock delta is %s». Проверка синхронизации важна для
операций, использующих метки времени, таких как логирование и аудит.
2.16.1.26. gc
Роль garbage_collector
принудительно
запускает сборку мусора в Lua. Роль включается неявно на всех экземплярах.
Параметры:
forced
— включение (true
) или отключение (false
) принудительной
сборки мусора. По умолчанию: false
;
period_sec
— интервал, через который происходит запуск нового цикла
сборки мусора, секунды;
steps
— размер шага сборщика мусора.
2.16.1.27. tracing
Настройки коммуникации с трейсинг-системой (tracing system), куда передаются
данные для анализа
производительности выполнения кода.
base_url
— эндпойнт программного интерфейса (API endpoint), куда
отсылаются собранные для анализа спаны (spans);
api_method
— тип HTTP-запроса для обращения к base_url
. Для
трейсинг-систем, поддерживающих протокол OpenZipkin
, — POST
;
report_interval
— интервал, через который в трейсинг-систему
отсылаются накопленные спаны, секунды;
spans_limit
— размер буфера для накопления спанов перед отправкой в
трейсинг-систему. Измеряется количеством спанов.
Например:
tracing:
base_url: localhost:9411/api/v2/spans
api_method: POST
report_interval: 5
spans_limit: 100
2.16.1.28. metrics
По умолчанию значения метрик в формате Prometheus доступны по адресу http://<IP_адрес_экземпляра>/metrics
.
Подробнее о сборе метрик можно почитать в главе «Метрики».
Также для мониторинга работы TDG можно настроить просмотр метрик по другим адресам ресурсов (endpoints) и
в других форматах:
metrics:
export:
- path: '/path_for_json_metrics'
format: 'json'
- path: '/path_for_prometheus_metrics'
format: 'prometheus'
- path: '/health'
format: 'health'
export
— параметр, который определяет, что в этой секции файла конфигурации мы делаем экспорт метрик в нужную нам
базу данных временных рядов (time series database);
path
— путь, по которому будут доступны метрики. Например:
</path_for_json_metrics>
— путь, по которому будут доступны метрики в формате JSON;
</path_for_prometheus_metrics>
— путь, по которому будут доступны метрики в формате Prometheus;
</health>
— путь, по которому будет доступна информация о текущем состоянии экземпляра.
format
— формат, в котором будут доступны метрики:
Если у вас, например, несколько систем хранения метрик,
вы можете добавить несколько адресов ресурсов одного формата по разным путям:
metrics:
export:
- path: '/path_for_json_metrics'
format: 'json'
- path: '/another_path_for_json_metrics'
format: 'json'
Как только вы добавите секцию metrics
в файл конфигурации, метрики по умолчанию в формате Prometheus
по адресу http://<IP_адрес_экземпляра>/metrics
перестанут быть доступными.
Если вам нужно, чтобы метрики по-прежнему были доступны по этому адресу в том же формате,
добавьте path: '/metrics'
и format: 'prometheus'
в секцию metrics
.
2.16.1.29. sequence_generator
Настройки, используемые при генерации последовательностей уникальных чисел:
starts_with
— число, с которого начинается последовательность.
По умолчанию: 1.
range_width
— диапазон последовательности. По умолчанию: 100.
См. подробнее про функцию работы с последовательностью.
2.16.1.30. test-soap-data
Параметр позволяет задать текст, который будет по
умолчанию отображаться на закладке Test в web-интерфейсе.
Может использоваться для удобства
тестирования: в этой секции можно задать структуру тестового объекта в формате
XML
или JSON
для имитации входящего запроса.
2.16.1.31. libraries
Настройка подключения пользовательских библиотек, которые могут использоваться
в коде функций пайплайнов (см. секции functions и
pipelines).
Указывается имя библиотеки, которое будет использоваться в коде, и путь к
файлу библиотеки, который загружается вместе с конфигурацией системы.
Пример конфигурации:
libraries:
cache:
__file: cached.lua
utils:
__file: utils.lua
Пример вызова:
Если в указанной в конфигурации библиотеке (например, utils.lua
) определена
функция
local function timediff(value)
return (datetime.now() - value)
end
то в пользовательском коде функцию можно вызвать как utils.timediff(value)
.
2.16.1.32. welcome-message
Текст приветственного сообщения, которое будет появляться при входе
в систему. Ограничения на количество символов в сообщении нет.
2.16.1.33. tdg-version
Проверка версии TDG на совместимость при применении конфигурации.
Для параметра указывается условие проверки, и с ним сравнивается текущая версия TDG.
Если условие не выполняется, применение конфигурации останавливается.
Пример:
tdg-version: "== 1.7.0" # [оператор][пробел][версия]
где:
оператор: ==
, <=
, <
, >
, >=
;
версия: major.minor.patch
(семантическое версионирование) или scm-<число>
.
Важно
Чтобы избежать возможных ошибок при чтении, всегда заключайте выражение из оператора и версии в кавычки.
Обязательного помещения в кавычки не требует только выражение с «==».
Синтаксис YAML позволяет указывать многостроковые значения с помощью операторов
>
, <
, >=
, >=
в первой строке.
Например, чтобы задать версию больше, чем некоторое значение, нужно поместить оператор >
и версию в кавычки:
# ошибка, будет прочитано как "1.7.0"
# tdg-version: > 1.7.0
# правильно, будет прочитано как "больше чем 1.7.0"
tdg-version: "> 1.7.0"
2.17. Скрипт tdgctl.py
Скрипт tdgctl.py
позволяет развернуть TDG и выполнить
ряд операций по его администрированию.
В дистрибутиве установки скрипт находится в
директории /deploy
.
Для использования скрипта необходимо подготовить файл конфигурации кластера
в формате JSON
.
2.17.1. Общий формат
tdgctl.py [-h] [-c <файл_конфигурации>] [-v] <команда> [<опции>]
Команды:
deploy — развертывание экземпляров (инстансов, instances);
upgrade — функциональное обновление экземпляров;
rm — удаление экземпляров;
logs — выгрузка логов экземпляров;
backup — резервное копирование экземпляров;
restore — восстановление данных из резервных копий;
stop — остановка экземпляров;
start — старт экземпляров.
Опции, общие для всех команд (далее в описании конкретных команд не упоминаются):
-h
, --help
— вывод подсказки по использованию скрипта;
-c <файл_конфигурации>
, --config <файл_конфигурации>
— путь к
файлу конфигурации кластера в формате JSON
. Если опция не указана, скрипт ищет по
умолчанию файл с именем config.json
в той же директории, где находится скрипт;
-v
, --verbose
— вывод полного лога работы команды.
Примечание
Каждая из команд применяется только к тем экземплярам, которые указаны
в файле конфигурации, передаваемом через опцию -c
.
2.17.2. Команда deploy
Используется для развертывания экземпляров.
Формат:
tdgctl.py deploy [-f] [-r] <файл_дистрибутива>
где
<файл_дистрибутива>
— путь к файлу дистрибутива установки
в формате архива tar.gz
;
-f
, --force
— опция принудительной повторной установки (force re-deploy)
на существующем кластере. Будет обновлена только функциональная часть, данные
будут сохранены;
-r
, --roles
— опция развертывания экземпляров с назначением им ролей в кластере.
Для работы с этой опцией в файле конфигурации кластера должны
быть указаны роли для каждого из экземпляров. Например:
"servers":
[
{
"address": "172.19.0.2",
"username": "admin",
"instances":
[
{
"name": "core_1",
"binary_port": 3000,
"http_port": 8080,
"memory_mb": 128
"roles": ["connector", "input_processor"]
},
{
"name": "storage_1",
"binary_port": 3001,
"http_port": 8081,
"memory_mb": 1024
"roles": ["storage"]
},
2.17.3. Команда upgrade
Используется для функционального обновления экземпляров.
Формат:
tdgctl.py upgrade [-f] <файл_дистрибутива>
где
<файл_дистрибутива>
— путь к файлу дистрибутива установки
в формате архива tar.gz
;
-f
, --force
— опция принудительной установки. Обновляется функциональная
часть; данные экземпляров сохраняются.
2.17.4. Команда rm
Используется для удаления экземпляров кластера. При удалении экземпляра также
удаляются все его данные.
Формат:
где
-y
, --yes
— опция удаления без запроса подтверждения.
2.17.5. Команда logs
Используется для выгрузки логов всех экземпляров кластера.
Формат:
где
-d N
, --days N
— опция выгрузки логов не старше N дней.
Для каждого из экземпляров кластера формируется файл лога с именем
<instance_name>.log
. При старте команды создается директория вида
logs.yyyy-mm-ddThh:mm:ssZ
, куда складываются все файлы логов, сформированные
при данном выполнении команды (yyyy-mm-ddThh:mm:ssZ
— дата и время на момент
запуска команды).
2.17.6. Команда backup
Используется для резервного копирования данных с экземпляров.
Формат:
где
-c
, --clear
— опция очистки директории для резервных данных перед началом
резервного копирования.
Резервные данные сохраняются отдельно для каждого из экземпляров в директории
/var/lib/tarantool/<instance_name>.checkpoint/
.
В качестве резервных данных система сохраняет следующие файлы для каждого из
экземпляров:
2.17.7. Команда restore
Используется для восстановление данных из резервных копий экземпляров.
Формат:
2.17.8. Команда stop
Используется для остановки экземпляров.
Формат:
tdgctl.py stop [--backup]
где
--backup
— опция для выполнения резервного копирования (box.snapshot()
).
2.17.9. Команда start
Используется для старта экземпляров.
Формат:
3. Запросы из внешних систем
В этой главе вы познакомитесь с различными видами запросов, обрабатываемых
TDG.
Существуют следующие основные типы запросов:
Запросы на доступ к данным, хранящимся в TDG (как на чтение, так и
на обработку и запись);
Запросы на чтение и внесение изменений в параметры работы и настройки
TDG.
Подробная информация по запросам для доступа к данным находится в разделе
Запросы на получение данных.
Описание запросов, изменяющих настройки TDG, приведено в разделе
Запросы на изменение настроек.
3.1. Запросы к данным
Этот раздел посвящен различным способам обмена данными между
TDG и внешними автоматизированными системами.
Запросы к данным могут быть переданы в TDG по следующим протоколам:
Отправляя соответствующий запрос в TDG, вы можете:
загружать данные в TDG;
получать данные из TDG (только для GraphQL);
вызывать выполнение сервисов в TDG (только для GraphQL);
выполнять любой заранее заложенный при конфигурации системы
код, при поступлении определенного запроса.
Важно
Получение данных из TDG сразу в ответе на запрос
возможно только в GraphQL-запросах. Однако, при соответствующей конфигурации,
все виды запросов способны вызывать заранее заложенную логику обработки,
включая отправку данных через output_processor.
Далее в этой главе рассмотрено применение запросов для операций
с данными в TDG. В целях наглядной иллюстрации будет использоваться
следующий базовый пример (см. описание по ссылке https://github.com/tarantool/examples/blob/master/tdg/5/5_Quickstart_guide_TDG.md
),
который будет дорабатываться в случае необходимости.
В примерах этой главы за основу взяты модель данных и конфигурация системы (архив можно забрать по ссылке https://github.com/tarantool/examples/releases/download/untagged-91dd480c7608ccbcd1c0/TDG_config_example_5.zip>
)
из пятого упражнения по работе с TDG (https://github.com/tarantool/examples/tree/master/tdg/5
).
3.1.1. Описание базового примера
Рассматриваемый далее пример использует простую модель данных библиотеки.
Имеются объекты следующих типов:
Пользователь (User
) со следующими полями:
Книга (Book
) со следующими полями:
Подписка/Абонемент (Subscription
) со следующими полями:
Все объекты в примере имеют логический тип Агрегат («Aggregate» — см. подробнее).
При этом объекты Пользователь и Книга связаны с объектом Подписка (в объекте Подписка
хранится информация о том, какие книги есть у каких пользователей).
На языке доменной модели (используемом в TDG)
описание этой структуры выглядит следующим образом:
[
{
"name": "User",
"type": "record",
"logicalType": "Aggregate",
"doc": "читатель",
"fields": [
{"name": "id", "type": "long"},
{"name": "username", "type": "string"}
],
"indexes": ["id"],
"relations": [
{ "name": "subscription", "to": "Subscription", "count": "many", "from_fields": "id", "to_fields": "user_id" }
]
},
{
"name": "Book",
"type": "record",
"logicalType": "Aggregate",
"doc": "книга",
"fields": [
{"name": "id", "type": "long"},
{"name": "book_name", "type": "string"},
{"name": "author", "type": "string"}
],
"indexes": ["id"],
"relations": [
{ "name": "subscription", "to": "Subscription", "count": "many", "from_fields": "id", "to_fields": "book_id" }
]
},
{
"name": "Subscription",
"type": "record",
"logicalType": "Aggregate",
"doc": "абонемент",
"fields": [
{"name": "id", "type": "long"},
{"name": "user_id", "type": "long"},
{"name": "book_id", "type": "long"}
],
"indexes": [
{"name":"pkey", "parts": ["id", "user_id"]},
"user_id",
"book_id"
],
"affinity": "user_id",
"relations": [
{ "name": "user", "to": "User", "count": "one", "from_fields": "user_id", "to_fields": "id" },
{ "name": "book", "to": "Book", "count": "one", "from_fields": "book_id", "to_fields": "id" }
]
}
]
Примечание
Параметр affinity
не используется в данном разделе по назначению и достался
базовому примеру из пятого упражнения по работе с TDG (https://github.com/tarantool/examples/tree/master/tdg/5
).
Подробнее про параметр affinity
читайте в документации.
3.1.1.1. Подготовка TDG
Для работы с примерами запросов вам потребуется рабочий кластер TDG с
загруженной конфигурацией системы и моделью данных.
Выполните установку и настройку кластера TDG в соответствии с инструкциями
Установка и запуск и Настройка кластера.
Скачайте архив, который содержит модель данных и конфигурацию системы, по следующей ссылке: https://github.com/tarantool/examples/releases/download/untagged-91dd480c7608ccbcd1c0/TDG_config_example_5.zip
.
Загрузите архив в TDG согласно инструкции.
В результате у вас должен получиться кластер TDG с полноценным набором
ролей: connector, input_processor, storage, account_manager, logger и другие.
При этом вышеуказанный архив с конфигурацией содержит рассмотренную ранее модель
данных, включающую три агрегата User, Book и Subscription со связями
между ними.
Кроме модели данных в архиве содержится файл конфигурации системы
и файлы с исходными кодами функций обработки данных.
3.1.2. Приём HTTP-запросов
HTTP используется в TDG для работы с графическим (web) интерфейсом и обработки
запросов через HTTP API. В данном разделе будет рассмотрено использование HTTP API.
Запросы по протоколу HTTP необходимо направлять по адресу любого
из экземпляров TDG с ролью connector на порт, указанный
в параметре http_port
для данного экземпляра при конфигурации кластера.
В конец адреса после символа /
добавляется указание на тип обрабатываемых запросов:
http
— для JSON;
soap
— для XML (SOAP);
graphql
— для GraphQL.
В итоге адрес для отправки запроса в TDG должен приобрести вид полноценного
URL адреса, например: http://172.19.0.2:8080/http
.
3.1.2.1. Авторизация
Для обработки HTTP-запроса он должен пройти авторизацию, которая осуществляется
по токену приложений. Подробнее про получение токена приложений — читайте тут.
Токен приложения, сформированный в TDG, необходимо передать в HTTP-заголовке
запроса по схеме Authorization: Bearer, где „Authorization“ — имя заголовка, а „Bearer“ — тип токена, например:
Authorization: Bearer ee7fbd80-a9ac-4dcf-8e43-7c98a969c34c
Важно
Токен должен быть сформирован в кластере TDG, к которому осуществляется
доступ, иначе он не пройдет авторизацию. Кроме того, ему должны быть выданы
соответствующие права для выполнения операций чтения и записи
с объектами (агрегатами) модели данных.
3.1.2.2. Обработка запросов
Обработка поступающих запросов описывается в основном файле конфигурации системы.
В разделе connector
/input
указываются каналы, по которым могут
поступать входящие запросы (за исключением GraphQL-запросов, которые обрабатываются,
даже если не указаны в файле конфигурации, при условии успешной авторизации по токену
приложений). Дальнейшая обработка поступивших запросов выполняется в соответствии
с конфигурацией, указанной в этом же файле.
3.1.2.3. Отправка запросов
Для отправки запроса в TDG по протоколу HTTP может быть использовано
любое средство, включая даже сам TDG. Для простоты демонстрации далее
мы используем интерпретатор Python версии 3 с библиотекой requests
или программу
curl
в качестве альтернативного варианта.
Для отправки запроса, в примерах на языке Python вызовите интерпретатор языка
Python версии 3 или выше, указав ему на файл скрипта с запросом:
Примечание
Этот файл должен содержать код отправки HTTP-запроса в TDG.
Подробнее в соответствующих разделах для JSON и XML.
В результате выполнения скрипта вы не увидите никаких сообщений об успешном или
неуспешном выполнении запроса. Это связано с тем, что в скрипте не предусмотрено
подобных проверок и вывода диагностических сообщений (например возвращенного ответа
на запрос).
В результате успешного выполнения JSON и XML запросов возвращается только HTTP status code
200
(OK). Это означает только то, что запрос прошёл авторизацию и первичную валидацию.
В случае неудачной авторизации с использованием токена приложений в ответе вернется
ошибка, а в журнале аудита появится соответствующая запись.
В случае успешной авторизации, но неуспешной обработки запроса — данный запрос
попадает в ремонтную очередь.
Получение результатов обработки данных в ответе на JSON или XML запрос невозможно
(в отличии от GraphQL).
Проверить факт выполнения вышеуказанного запроса можно, убедившись в достижении результата.
В нашем случае для этого убедитесь в том, что в TDG была добавлена запись
типа User
с id
равным 1 и username
— Alex. Наиболее простой способ сделать
это — выполнить GraphQL-запрос.
3.1.3. Запросы в формате GraphQL
Рассмотрим обработку запросов в формате GraphQL на основе базового примера.
Для отправки запросов используется веб-интерфейс TDG и программа curl
.
Для приёма и обработки запросов вам понадобится кластер TDG,
настроенный ранее.
Основной язык запросов TDG основан на GraphQL.
GraphQL запросы можно выполнить с использованием веб-интерфейса на вкладке Graphql
или отправив их по протоколу HTTP (с корректным заголовком для
авторизации) на адрес вида http://172.19.0.2:8080/graphql
, где
172.19.0.2 — адрес экземпляра TDG с ролью connector
;
8080 — порт, указанный в параметре http_port
для данного экземпляра.
Запросы GraphQL делятся на следующие два типа:
Частным случаем запроса на получение данных является
запрос на выполнение сервиса.
В этом случае также используется тип query
, но обращение идет не напрямую к
хранящимся в системе данным, а к сервису, в котором можно задать
произвольную логику для выборки объектов.
3.1.3.1. Адаптация конфигурации из базового примера
Для выполнения примеров в данном разделе используйте кластер TDG,
установка и настройка которого проводились ранее.
Для повторения примеров из данного раздела вам потребуется внести определённые
исправления в конфигурацию системы, загруженную ранее.
Прежде всего, внесите изменения в модель данных. Для этого отредактируйте файл
model.avsc
из архива с конфигурацией системы. Измените описание объекта с
именем User
так, чтобы оно стало следующим:
{
"name": "User",
"type": "record",
"logicalType": "Aggregate",
"doc": "читатель",
"fields": [
{
"name": "id",
"type": "long"
},
{
"name": "username",
"type": "string"
},
{
"name": "phones",
"type": {
"type": "array",
"items": "string"
}
}
],
"indexes": [
"id",
"phones"
],
"relations": [
{
"name": "subscription",
"to": "Subscription",
"count": "many",
"from_fields": "id",
"to_fields": "user_id"
}
]
}
Также внесите изменение в описание объекта Book
так, чтобы оно стало следующим:
{
"name": "Book",
"type": "record",
"logicalType": "Aggregate",
"doc": "книга",
"fields": [
{
"name": "id",
"type": "long"
},
{
"name": "book_name",
"type": "string"
},
{
"name": "author",
"type": "string"
},
{
"name": "year",
"type": "int"
}
],
"indexes": [
"id",
"year"
],
"relations": [
{
"name": "subscription",
"to": "Subscription",
"count": "many",
"from_fields": "id",
"to_fields": "book_id"
}
]
}
Затем повторно заархивируйте новую модель с остальными файлами конфигурации и
повторите загрузку конфигурации системы согласно инструкции
для обновления модели данных.
3.1.3.2. Запрос на получение данных
Все агрегаты (объекты логического типа «Aggregate» — см. подробнее раздел о
модели данных) доступны для запроса по имени типа.
Общий вид GraphQL-запроса на получение данных следующий:
query {
aggregate_name {
fields
}
}
где
Для возвращаемого объекта можно указывать произвольный набор полей, который вы
хотите получить, т.е. необязательно указывать все поля, описанные для
данного агрегата в модели данных. Поля могут разделяться запятой, пробелом или
переносом строки.
Примечание
В запросах на получение данных ключевое слово query
можно опускать, так как
если оно не указано, GraphQL по умолчанию трактует данную операцию как query.
В дальнейших примерах так и будет сделано для простоты синтаксиса.
3.1.3.2.1. Пример выполнения запроса через веб-интерфейс
Для выполнения GraphQL-запросов проще всего использовать встроенный веб-клиент на
вкладке Graphql в веб-интерфейсе TDG. Для выполнения простого запроса
на получение данных для нашего базового примера введите следующий запрос:
Обратите внимание, что в фигурных скобках указан тип объекта (Агрегата) User
.
Далее в отдельных фигурных скобках указаны его поля id
и username
для их
отображения в получаемом ответе.
3.1.3.2.2. Пример выполнения запроса через HTTP-запрос
Также вы можете выполнить запрос, отправив его в input_processor
TDG
по протоколу HTTP. Для выполнения запроса при помощи утилиты curl
используйте
следующую команду в консоли.
curl --request POST \
--url http://172.19.0.2:8080/graphql \
--header 'Authorization: Bearer ee7fbd80-a9ac-4dcf-8e43-7c98a969c34c' \
--data '{"query":"{User{id,username}}"}'
Примечание
Используйте в качестве значения для параметра Authorization: Bearer
токен приложений,
сгенерированный заранее.
В качестве ответа возвращается объект JSON, содержащий массив со всеми записями
типа User
, при этом для каждой записи будут указаны поля id
и username
.
{
"data": {
"User": [
{
"id": 1,
"username": "John Smith"
},
{
"id": 2,
"username": "Adam Sanders"
}
]
}
}
Если в качестве fields
указать пустой список, то система также вернет пустой
объект, т.к. GraphQL возвращает именно то, что было указано в запросе.
3.1.3.3. Выборка агрегатов
Отдельно взятый агрегат можно выбрать по индексу. Для этого
индексированное поле нужно указать в качестве аргумента запроса.
В примере ниже агрегат User
выбирается по индексу id
(см. описание модели).
Проверка условия выполняется на полное совпадение.
{
User(id: 1) {
id
username
}
}
Примечание
Для запросов такого рода доступны только индексированные поля. Фильтровать по
обычным (неиндексированным) полям таким способом не получится.
В одном запросе можно указывать несколько индексов. В таком
случае запрос будет искать объекты, удовлетворяющие всем условиям
одновременно (логическая операция конъюнкция или логическое И).
query {
Subscription(user_id: 1, book_id: 3) {
id
user_id
book_id
}
}
Для запроса по составному (мультиколоночному) индексу используется массив
значений. В нашем примере у агрегата типа Subscription
есть составной индекс pkey
, включающий в себя поля id
и user_id
. Пример
запроса по этому индексу выглядит так:
{
Subscription(pkey: [2, 1]) {
id
book_id
user_id
}
}
3.1.3.4. Мультиключевые индексы
Отдельно нужно отметить запросы по так называемому мультиключевому индексу (multikey index), т.е.
индексу по полю, содержащему массив. Для иллюстрации возьмем за основу
наш пример, в который test
такое поле (phones
) и соответствующий индекс к агрегату User
.
Поле phones
будет содержать массив, в котором хранятся все телефонные номера
читателя.
Примечание
Мультиключевой индекс не может быть первичным.
Внешне запрос по мультиключевому индексу будет выглядеть так же, как и запрос
по обычному индексу. Разница может быть в возвращаемом результате, т.к. возможен
случай, когда сразу несколько элементов массива будут попадать под условие запроса
и, соответственно, у нас будет несколько ключей, указывающих на один и тот же
объект. В таком случае этот объект может быть возвращен несколько раз, и это нужно
учитывать при составлении запросов и при обработке полученных результатов.
3.1.3.4.1. Пример запроса с мультиключевым индексом
Допустим, необходимо сделать выборку читателей (User
), телефоны которых начинаются
с определенного кода страны, например, «+7». У читателя с id = 1
таких
телефонов два:
{
"id": 1,
"username": "John Smith",
"phones": [
"+74951234567",
"+79997654321",
"+19001234567"
]
}
Соответственно запрос по индексу phones
для такого случая вернет агрегат
типа User
c id = 1
дважды
Выполните следующий запрос на вкладке Graphql веб-интерфейса TDG.
{
User(phones_like: "+7%") {
id
username
phones
}
}
В результате вы получите следующий ответ.
{
"data": {
"User": [
{
"username": "John Smith",
"phone": [
"+74951234567",
"+79997654321",
"+19001234567"
],
"id": 1
},
{
"username": "John Smith",
"phone": [
"+74951234567",
"+79997654321",
"+19001234567"
],
"id": 1
}
]
}
}
3.1.3.5. Выборка со сравнением
Выборки поддерживают операции сравнения в виде суффиксов в именах индексов.
Поддерживаются следующие операторы сравнения:
_gt
(Greater Than) — строго больше;
_ge
(Greater Than or Equal) — больше либо равно;
_lt
(Less Than) — строго меньше;
_le
(Less Than or Equal) — меньше либо равно.
Формат запросов:
{
aggregate_name(index_gt: value) {
fields
}
}
где
index_gt
— наименование индекса, по которому производится выборка с индексом
для сравнения;
value
— значение для сравнения.
3.1.3.5.1. Примеры выборки со сравнением
Например, для получения всех книг, выпущенных после 2008 года, используйте следующий
запрос.
{
Book(year_gt: 2008) {
book_name
year
author
}
}
Данные суффиксы также поддерживаются для составных индексов и multikey индексов.
Предположим, что есть индекс, содержащий в себе такие части, как year
и month
.
Тогда для получения книг, выпущенных после июля 2008 года, выполните следующий запрос.
{
Book(year_month_gt: [2008, 7]) {
book_name
year
author
}
}
Примечание
Используемая в нашем примере модель данных не предполагает поля month
,
но вы можете попробовать добавить его сами (по аналогии с этим
разделом) и выполнить вышеуказанный запрос для закрепления практических навыков.
Операторы сравнения для индексов по строковым полям работают в соответствии с
правилами сортировки
(collation), принятыми в Tarantool.
Также для индексов по строковым полям поддерживается оператор _like
— для
поиска заданного шаблона в строке. В шаблоне можно использовать подстановочный
знак (wildcard) %
, который представляет любое количество любых символов.
Для примера выполните следующий запрос.
{
User(phones_like: "+7%") {
id
username
phones
}
}
3.1.3.6. Выборка агрегатов по связям
Для фильтрации связанных агрегатов используется тот же синтаксис, что и для выборки
обычных агрегатов.
В используемом базовом примере агрегаты User
и Subscription
связаны отношением «один ко многим». Соответственно, в одном
запросе можно получить одновременно и данные читателя, и информацию о его
подписках (или об отдельно взятой подписке, как в примере ниже).
{
User(id: 1) {
id
username
phones
subscription(pkey: [2, 1]) {
id
book_id
}
}
}
Данный вид запроса аналогичен SQL LEFT OUTER JOIN.
Как объясняется в разделе про отношения между объектами,
отношения могут быть односторонними или полными (двусторонними). В нашем примере
в модели данных заданы двусторонние отношения (например, поле relations
определено
и у User
, и у Subscription
), поэтому также возможен запрос «с другой
стороны» — т.е. запрашивая данные по какому-то из абонементов/подписок, в этом же
запросе можно получить и данные читателя. В качестве примера выполните следующий
запрос.
{
Subscription(pkey: [2, 1]) {
id
book_id
user {
id
username
phones
}
}
}
3.1.3.8. Версионирование и запрос исторических данных
Основные принципы версионирования данных в TDG указаны тут.
Для передачи и запроса версии используется служебное поле version
.
Важно отметить, что если в запросе не указывается версия, то возвращается последняя
(наибольшая) версия.
Количество хранимых версий можно ограничить через конфигурацию системы.
По умолчанию количество не ограничено.
3.1.3.8.1. Пример запроса с использованием исторических данных
Допустим в результате одного из запросов у записи типа User
с id
равным
1 был удалён один из номеров телефона (записана новая версия данных без этого
номера).
Нужно получить предыдущую версию, где есть удаленный номер телефона.
В таком случае сделайте запрос на получение последней версии агрегата (не указывая,
какую версию хотите получить), и добавьте в вывод значение поля version
:
{
User(id: 1) {
id
username
phones
version
}
}
В ответ будет получен следующий или подобный JSON:
{
"data": {
"User": [
{
"version": "1585393144680150551",
"username": "John Smith",
"phones": [
"+74951234567",
"+79997654321"
],
"id": 1
}
]
}
}
Берем значение «version»: «1585393144680150551», вычитаем из него 1, получаем
«version»: «1585393144680150550», и указываем это значение как аргумент в
повторном запросе.
{
User(id: 1, version: 1585393144680150550) {
id
username
phones
version
}
}
TDG будет искать или версию, равную указанной, или, если не найдет
равную, то ближайшую младшую версию. Таким образом в любом случае в ответе будет получена
предыдущая версия агрегата:
{
"data": {
"User": [
{
"version": "1585392369718590708",
"username": "John Smith",
"phones": [
"+74951234567",
"+79997654321",
"+19001234567"
],
"id": 1
}
]
}
}
Таким образом можно итерационно пройти по предыдущим версиям. При запросе версии
меньше, чем минимально существующая, в ответ вернётся пустой объект.
Также возможно запросить все версии агрегата через аргумент all_versions
:
{
User(id: 1, all_versions: true) {
id
username
version
}
}
При использовании аргумента all_versions
можно, конечно же, ограничить количество
возвращаемых версий агрегата при помощи пагинации.
{
User(id: 1, all_versions: true, first: 3) {
id
username
version
}
}
3.1.3.9. Ограничения запросов
Для контроля нагрузки на сервер сделаны следующие ограничения запроса:
Данные ограничения превентивно требуют качественного написания
запросов, являясь по сути аналогом профилирования медленных запросов.
Настройка данных параметров возможна при конфигурации системы
или с помощью специальных запросов и мутаций GraphQL.
3.1.3.10. Изменение данных
TDG поддерживает следующие запросы на изменение данных (мутации):
вставка объекта (insert);
обновление объекта (update);
удаление объекта (delete).
3.1.3.10.1. Вставка объекта
Общий вид запроса на вставку (добавление или обновление) объекта:
mutation {
aggregate_name(insert: {JSON_input_object}) {
fields
}
}
где:
mutation
— объявление типа запроса как запрос на изменение данных (мутация);
aggregate_name
— имя агрегата;
JSON_input_object
— объект для вставки в формате JSON. Необходимо указать
все обязательные поля и их значения. Обязательными являются поля, описанные в модели данных
для этого агрегата, тип которых отличен от «null»;
fields
— (опционально) список полей возвращаемого объекта.
3.1.3.10.1.1. Пример запроса на вставку объекта
Для добавления нового или обновления существующего (если объект с таким первичным
ключом уже есть) объекта перейдите на вкладку Graphql и выполните следующий запрос:
mutation {
User(
insert: {
id: 1
username: "John Smith"
phones: ["+74951234567", "+79997654321"]
}
) {
id
username
phones
}
}
В качестве альтернативы вы можете отправить HTTP-запрос, например при помощи программы
curl
. Для этого выполните следующую команду.
curl --request POST \
--url 'http://172.19.0.2:8080/graphql?=' \
--header 'Authorization: Bearer ee7fbd80-a9ac-4dcf-8e43-7c98a969c34c' \
--data '{"query":" mutation {User(insert: {id: 1, username: \"John Smith\", phones:[\"+74951234567\", \"+79997654321\"]}) {id username phones}}"}'
Примечание
Символ \"
нужен при работе из командной строки для корректной обработки
символов кавычек в команде.
Примечание
Используйте в качестве значения для параметра Authorization: Bearer
токен приложений,
сгенерированный заранее.
Какой бы способ выполнения запроса вы ни выбрали, в результате вы получите следующий
ответ:
{
"data": {
"User": [
{
"username": "John Smith",
"phones": [
"+74951234567",
"+79997654321"
],
"id": 1
}
]
}
}
3.1.3.10.1.2. Пример запроса на вставку с оптимистичной блокировкой
В запросах могут использоваться оптимистичные блокировки.
Для этого используйте параметр only_if_version
, как это показано в следующем
примере:
mutation {
User(
insert: {
id: 1
username: "John Smith"
phones: ["+74951234567", "+79997654321"]
}
only_if_version: 1585392369718590708
) {
id
username
phones
}
}
3.1.3.10.2. Обновление объекта
Общий вид запроса на обновление объекта:
mutation {
aggregate_name(update: filter [[mutator, path, new_value], ...]) {
fields
}
}
где:
mutation
— объявление типа запроса как запроса на изменение данных (мутация);
aggregate_name
— имя агрегата;
filter
— список условий-предикатов для выбора объектов указанного типа;
[[mutator, path, new_value], ...]
— список мутаторов:
mutator
— имя мутатора. Возможные значения: set
(устанавливает значение),
add
(увеличивает значение на указанное число), sub
(уменьшает значение на указанное число);
path
— строковый путь до поля объекта с точкой-разделителем (.
).
Путь до объекта массива должен включать индекс массива или символ *
для захвата всех дочерних объектов;
new_value
— новое значение.
fields
— список полей возвращаемого объекта.
3.1.3.10.2.1. Пример запроса на обновление объекта
Обновим ранее добавленный объект типа User
— изменим значение поля username
.
Нужный объект данного типа выберем по первичному ключу id
.
Перейдите на вкладку Graphql и выполните следующий запрос:
mutation {
User(id:1 update:[["set", "username", "John D. Smith"]]) {
id
username
phones
}
}
В качестве альтернативы вы можете отправить HTTP-запрос, например, при помощи программы curl
.
Для этого выполните следующую команду:
curl --request POST \
--url 'http://172.19.0.2:8080/graphql?=' \
--header 'Authentication: Bearer ee7fbd80-a9ac-4dcf-8e43-7c98a969c34c' \
--data '{"query":"mutation {User(update: id: 1 [[\"set\", \"username\", \"John D. Smith\"]]) {id username phones}}"}'
Примечание
Символ \"
нужен при работе из командной строки для корректной обработки
символов кавычек в команде.
Для параметра Authentication: Bearer
используйте в качестве значения токен приложений,
сгенерированный заранее.
Какой бы способ выполнения запроса вы ни выбрали, в результате вы получите следующий ответ:
{
"data": {
"User": [
{
"username": "John D. Smith",
"phones": [
"+74951234567",
"+79997654321"
],
"id": 1
}
]
}
}
3.1.3.10.3. Удаление объектов
Общий вид запроса на удаление объекта:
mutation {
aggregate_name(delete: true) {
fields
}
}
где:
aggregate_name
— имя агрегата. В качестве аргумента указывается delete: true
.
Остальные аргументы опциональны: их можно использовать, чтобы задавать логику
того, что нужно удалить (например значение одного из ключей или only_if_version
);
fields
— (опционально) возвращаемые поля.
3.1.3.10.3.1. Примеры запросов на удаление объектов
Если не указаны другие аргументы помимо delete: true
, будут удалены все
объекты данного типа, например:
mutation {
User(delete: true) {
id
username
}
}
В результате вы получите следующий или подобный ответ, а все перечисленные объекты
будут удалены.
{
"data": {
"User": [
{
"username": "John Smith",
"id": 1
},
{
"username": "Adam Sanders",
"id": 2
}
]
}
}
Для выборочного удаления можно использовать все те же аргументы, которые
используются для фильтрации в запросах на получение данных.
Например, удаление выборочного объекта по первичному ключу:
mutation {
User(delete: true, id: 1) {
id
username
}
}
3.1.3.10.3.1.1. Полное удаление
При обычном удалении информация об объекте помечается признаком удаления и перестаёт
учитываться при выборках. Однако существует способ полного удаления информации
из TDG без сохранения каких-либо данных, включая различные версии.
Для этого в запросе на удаление укажите параметр permanent_delete: True
.
Логика его работы совпадает с описанной для такого параметра в
данном разделе.
3.1.3.11. Выполнение сервисов
GraphQL-запросы дают возможность вызова сервисов в TDG.
Общий вида запроса на выполнение сервиса:
query {
service_name(arg1: value1, arg2: value2, ...)
}
3.1.3.11.1. Пример запроса на выполнение сервиса
В базовом примере имеется сервис, описанный в файле конфигурации следующими строками:
services:
select_user_books:
doc: "select_user_books"
function: select_user_books
return_type: string
args:
user_id: long
Для вызова этого сервиса используйте следующий запрос:
{
select_user_books(user_id: 1)
}
В ответе на данный запрос вернется результат вызова сервиса:
{
"data": {
"select_user_books": "[1, 3]"
}
}
Как видно, в ответе действительно имеется массив с идентификаторами (id
)
всех книг, которые взял пользователь с user_id
= 1.
3.1.4. Запросы в формате JSON
Рассмотрим обработку запросов в формате JSON на основе базового примера.
Для отправки запросов мы используем простые скрипты на языке Python.
Для приёма и обработки запросов нам понадобится кластер TDG,
настроенный ранее.
3.1.4.1. Адаптация конфигурации из базового примера
Поскольку базовый пример использует JSON, то адаптация конфигурации системы из
базового примера не потребуется.
3.1.4.2. Описание процесса обработки запроса
Логика обработки поступающего запроса изложена в файле конфигурации системы
config.yml
и состоит в следующем:
Согласно разделу connector
/ input
файла конфигурации config.yml
HTTP (JSON) запросы передаются на обработку конвейеру router
, который состоит
из функции router
;
Функция router
ссылается на файл router.lua
, который упаковывает поступивший
объект с ключом routing_key
равным строке input_key
;
Согласно разделу connector
/ routing
файла конфигурации config.yml
,
все объекты с ключом input_key
передаются to_input_processor
;
В секции output
для раздела connector
указана единственная запись to_input_processor
,
которая переадресует запрос в раздел input_processor
для обработки на одноименной
роли;
В разделе input_processor
все запросы попадают в секцию classifiers
, где
в нашем случае указан один единственный объект, вызывающий конвейер (pipeline)
classifier
;
Конвейер classifier
вызывает одноименную функцию, которая описана в файле
classificator.lua
. Как можно понять из названия, данная функция занимается
классификацией поступающей информации. Логика ее работы следующая:
при наличии ненулевого поля username
у поступившего объекта — ему присваивается
routing_key
= add_user
. Для объектов с таким ключом в config.yml
предусмотрено сохранение (на роли storage
) объекта с типом User
из
модели данных;
при наличии ненулевого поля book_name
у поступившего объекта — ему присваивается
routing_key
= add_book
. Для объектов с таким ключом в config.yml
предусмотрено сохранение (на роли storage
) объекта с типом Book
из
модели данных;
при наличии ненулевых полей user_id
и book_id
у поступившего объекта —
ему присваивается routing_key
= add_subscription
. Для объектов
с таким ключом в config.yml
предусмотрено сохранение (на роли storage
)
объекта с типом Subscription
из модели данных;
во всех остальных случаях объекту присваивается routing_key
= unknown_type
,
то есть объект не распознан. Такой объект обычно попадает в ремонтную очередь,
но можно настроить и иное поведение;
В секции storage
описано сохранение данных в TDG.
при значении routing_key
равном add_user
поступивший объект преобразовывается
в тип User
при сохранени;
при значении routing_key
равном add_book
поступивший объект преобразовывается
в тип Book
при сохранени;
при значении routing_key
равном add_subscription
поступивший объект
преобразовывается в тип Subscription
при сохранении.
Обратите внимание, что вся лишняя информация, не
относящаяся к типу объекта, описанному в модели данных, не будет сохранена.
Из всего файла конфигурации системы остался не рассмотренным участок, отвечающий
за сервисы. В данном примере там описан простой сервис, вызывающий функцию select_user_books
с аргументом user_id
и возвращающий строковую переменную.
Логика работы этой функции такова: переданное значение user_id
используется
для поиска в объекте Subscription
всех книг, записанных за данным пользователем.
Затем выводятся все найденные book_id
.
3.1.4.3. Подготовка запроса в формате JSON
Создайте файл request.py
со скриптом на языке Python для отправки простейшего
запроса с полями объекта типа User
(из нашей модели данных:
это поля id
и username
), который будет выглядеть следующим образом:
import requests
data = {'username' : 'John Smith', 'id' : 1}
header = {'Authorization' : 'Bearer ee7fbd80-a9ac-4dcf-8e43-7c98a969c34c'}
r = requests.post(url = "http://172.19.0.2:8080/http", json = data, headers = header)
Примечание
Используйте в качестве значения для параметра Authorization: Bearer
токен приложений,
сгенерированный ранее.
Для успешного добавления записи имеющегося в модели данных типа объекта в запросе
должны содержаться все обязательные поля для данного типа объекта.
Процедура отправки запроса и ожидаемые результаты описаны ранее в пункте
Отправка запросов.
3.1.5. Запросы в формате XML (SOAP)
Рассмотрим обработку запросов в формате XML (SOAP) на основе базового примера.
Для отправки запросов мы используем простые скрипты на языке Python.
Для приёма и обработки запросов нам понадобится кластер TDG,
настроенный ранее.
3.1.5.1. Адаптация конфигурации из базового примера
Для повторения примеров из данного раздела нам потребуется внести определенные
исправления в конфигурацию системы, загруженную ранее.
Разархивируйте архив с конфигурацией системы в отдельную директорию.
Откройте файл config.yml
и измените его так, чтобы он содержал следующий текст:
types:
__file: model.avsc
functions:
router: {__file: router.lua}
classifier: {__file: classificator.lua}
select_user_books: {__file: select_user_books.lua}
process_user: {__file: process_user.lua}
process_book: {__file: process_book.lua}
process_sub: {__file: process_sub.lua}
pipelines:
router:
- router
classifier:
- classifier
select_user_books:
- select_user_books
process_user:
- process_user
process_book:
- process_book
process_sub:
- process_sub
connector:
input:
- name: soap
type: soap
wsdl: {__file: example.wsdl}
handlers:
- function: Connect
pipeline: router
routing:
- key: input_key
output: to_input_processor
output:
- name: to_input_processor
type: input_processor
input_processor:
classifiers:
- name: classifier
pipeline: classifier
routing:
- key: process_user
pipeline: process_user
- key: process_book
pipeline: process_book
- key: process_sub
pipeline: process_sub
storage:
- key: add_user
type: User
- key: add_book
type: Book
- key: add_subscription
type: Subscription
services:
select_user_books:
doc: "select_user_books"
function: select_user_books
return_type: string
args:
user_id: long
Обратите внимание на появившийся раздел input_processor
/routing
и связанные
с ним описания новых конвейеров обработки данных (pipeline) и функций. При этом
модель данных не изменилась. В разделе connector
появилось описание входа с
типом SOAP, функцией Connect
(не являющейся функцией TDG, но описываемой
в XML/SOAP) и связанным с ней конвейером (уже известным нам по примеру с JSON — router
).
Примечание
В секции wsdl:
обязательно указывается файл спецификации веб-сервиса с
расширением .wsdl
. В нашем примере это example.wsdl
. Данный файл должен
обязательно быть включен в состав архива с конфигурацией системы. В том случае,
если WSDL не используется, данный файл может быть пустым.
Теперь откройте файл classificator.lua
и отредактируйте его так, чтобы он имел
следующее содержимое:
#!/usr/bin/env tarantool
local param = ...
if (param.obj[1].tag == "username" or param.obj[2].tag == "username") then
param.routing_key = "process_user"
return param
end
if (param.obj[1].tag == "book_name" or param.obj[2].tag == "book_name") then
param.routing_key = "process_book"
return param
end
if ((param.obj[1].tag == "user_id" and param.obj[2] == "book_id") or (param.obj[2].tag == "user_id" and param.obj[1].tag == "book_id")) then
param.routing_key = "process_sub"
return param
end
param.routing_key = "unknown_type"
return param
Создайте файлы process_user.lua
, process_book.lua
и process_sub.lua
.
Содержимое этих файлов — скрипт обработки данных, который должен формировать из
получаемого TDG информационного объекта объект, соответствующий описанному
в модели данных.
Далее приведен пример содержимого файла process_user.lua
.
Остальные файлы выполняются по аналогии.
#!/usr/bin/env tarantool
local param = ...
local id = param.obj[1][1]
local username = param.obj[2][1]
local data = {id = tonumber(id), username = username}
local ret = {obj = data, priority = 1, routing_key = 'add_user'}
return ret
Сформированный вышеуказанным скриптом объект должен содержать поля id
и username
,
так как он получит routing_key
для добавления объекта типа User
в хранилище.
Важно
Для сохранения TDG ожидает объект в формате, представленном выше,
где obj
включает в себя весь информационный объект в виде перечисленных
через запятую пар key = value
, где для каждого обязательного поля объекта
в модели данных задан одноимённый ключ (key) с непустым значением.
Закончив с подготовкой файлов, упакуйте их в zip-архив и загрузите его согласно
инструкции.
3.1.5.2. Описание процесса обработки запроса
Логика обработки поступающего запроса изложена в файле конфигурации config.yml
и состоит в следующем:
Согласно разделу connector
/ input
файла конфигурации config.yml
,
SOAP (XML) запросы передаются на обработку конвейеру router
, который состоит
из функции router
;
Функция router
ссылается на файл router.lua
, который упаковывает поступивший
объект с ключом routing_key
равным строке input_key
;
Согласно разделу connector
/ routing
файла конфигурации config.yml
,
все объекты с ключом input_key
передаются to_input_processor
;
В секции output
для раздела connector
указана единственная запись to_input_processor
,
которая переадресует запрос в раздел input_processor
для обработки на одноименной
роли;
В разделе input_processor
все запросы попадают в секцию classifiers
, где
в нашем случае указан один единственный объект, вызывающий конвейер обработки
объектов (pipeline) classifier
;
Конвейер classifier
вызывает одноименную функцию, которая описана в файле
classificator.lua
. Как можно понять из названия, данная функция занимается
классификацией поступающей информации. Логика ее работы следующая:
при наличии тэга username
у поступившего объекта — ему присваивается
routing_key
= process_user
, то есть объект направляется для формирования
объекта типа пользователь;
при наличии тэга book_name
у поступившего объекта — ему присваивается
routing_key
= process_book
, то есть объект направляется для формирования
объекта типа книга;
при наличии тэгов user_id
и book_id
у поступившего объекта
— ему присваивается routing_key
= process_sub
, то есть объект
направляется для формирования объекта типа подписка;
во всех остальных случаях объекту присваивается routing_key
= unknown_type
,
то есть объект не распознан. Такой объект обычно попадает в ремонтную очередь,
но можно настроить и иное поведение;
В разделе input_processor
в секции routing
имеются указания по обработке
объектов со следующими routing_key
: process_user
, process_book
и
process_sub
при помощи одноименных функций. Каждая из этих функций формирует
объект в нужном для сохранения формате и присваивает ему соответствующий routing_key
;
В секции storage
описано сохранение данных в TDG.
при значении routing_key
равном add_user
объект сохраняется как User
;
при значении routing_key
равном add_book
объект сохраняется как Book
;
при значении routing_key
равном add_subscription
объект сохраняется
как Subscription
.
Обратите внимание, что вся лишняя информация, не
относящаяся к типу объекта, описанному в модели данных, не будет сохранена.
Из всего файла конфигурации системы остался не рассмотренным участок, отвечающий
за сервисы. В данном примере там описан простой сервис, вызывающий функцию select_user_books
с аргументом user_id
и возвращающий строковую переменную.
Логика работы этой функции такова — переданное значение user_id
используется
для поиска в объекте Subscription
всех книг, записанных за данным пользователем.
Затем выводятся все найденные book_id
.
3.1.5.3. Подготовка запроса в формате SOAP (XML)
Создайте файл request.py
со скриптом на языке Python для отправки простейшего
запроса с полями объекта типа User
(из нашей модели данных:
это поля id
и username
), который будет выглядеть следующим образом:
import requests
data = """<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<NS2:Connect xmlns:NS2="http://172.19.0.2:8080/soap">
<id>1</id>
<username>John Smith</username>
</NS2:Connect>
</soap:Body>
</soap:Envelope>"""
header = {'Authorization' : 'Bearer c0b26a60-aebe-4899-9ab6-4458627ac61e'}
r = requests.post(url = "http://172.19.0.2:8080/soap", data = data, headers = header)
Передаваемый запрос содержит заголовок для авторизации и тело в виде
SOAP объекта с обязательными полями для объекта модели данных типа User
.
Примечание
Используйте в качестве значения для параметра Authorization: Bearer
токен приложений,
сгенерированный ранее.
Для успешного добавления записи имеющегося в модели данных типа объекта в запросе
должны содержаться все обязательные поля для данного типа объекта.
Процедура отправки запроса и ожидаемые результаты описаны ранее в пункте
Отправка запросов.
3.1.6. Взаимодействие с kafka
Рассмотрим настройку взаимодействия TDG по протоколу Kafka.
TDG может быть настроен как в качестве consumer
(роль connector
, секция input
), так и в качестве
producer (роль connector
, секция output
) в терминологии Kafka.
В целях простой демонстрации возможности взаимодействия с Kafka, в данном примере
мы обеспечим получение объекта из заданной темы (topic), его небольшую модификацию
и отправку обратно в ту же тему. В результате можно будет наблюдать рекурсивную
переотправку модифицируемых объектов.
Для завершения рекурсивной обработки необходимо будет выключить Kafka или
TDG, либо загрузить в TDG новую корректную конфигурацию без
рекурсивной отправки с получением.
Для выполнения примера нам понадобится кластер TDG,
настроенный ранее.
3.1.6.1. Конфигурация
Создайте файл config.yml
и измените его так, чтобы он содержал следующий
текст:
types:
__file: model.avsc
functions:
kafka_hlr: {__file: kafka_handler.lua}
pipelines:
kafka_handler:
- kafka_hlr
connector:
input:
- name: from_kafka
type: kafka
brokers:
- localhost:9092
topics:
- items
group_id: kafka
pipeline: kafka_handler
routing:
- key: input_key
output: to_kafka
output:
- name: to_kafka
type: kafka
brokers:
- localhost:9092
topic: items
Обратите внимание, в разделе connector
имеется описание входа с типом kafka,
реализующего роль consumer
в терминологии Kafka, для которого указаны такие
параметры как:
name
— имя коннектора;
type
— тип коннектора;
brokers
— адреса брокеров Kafka;
topics
— выбор тем (топиков) для подписки;
group_id
— consumer group id в терминологии Kafka;
pipeline
— указание на конвейер (pipeline), в который будет передана
обработка получаемых сообщений.
В разделе output
появилось описание исходящего маршрута с типом kafka,
реализующего роль producer
в терминологии Kafka, для которого указаны
следующие параметры:
name
— имя исходящего маршрута;
type
— тип исходящего маршрута;
brokers
— адреса брокеров Kafka;
topic
— в какой теме (topic) публиковать информацию.
Объекты в данном примере не сохраняются локально, поэтому модель данных может
быть пустой. Создайте пустой файл model.avsc
.
Вся обработка данных ведется конвейером kafka_handler
в файле
kafka_handler.lua
.
Создайте файл kafka_handler.lua
и наполните его следующим содержанием.
#!/usr/bin/env tarantool
local param = ...
param.version = param.version + 1
local ret = {obj = param, priority = 1, routing_key = 'input_key'}
return ret
Содержимое этого файла — скрипт обработки данных, который получает на вход JSON,
поступивший из Kafka. В этом файле можно выполнить предварительную обработку
поступившей информации, а также обернуть данные в JSON с ключом routing_key
для дальнейшей обработки
(см. подробнее).
Важно
Kafka коннектор в TDG поддерживает только валидные JSON объекты.
Сейчас в данном файле приведен пример простейшей модификации (увеличение числа,
содержащегося в поле version
, которое было получено из Kafka JSON) и оборачивания
данного модифицированного JSON в формат для дальнейшей обработки с ключом
routing_key
равным input_key
.
Закончив с подготовкой файлов, упакуйте их в zip-архив и загрузите его согласно
инструкции.
3.1.6.2. Описание процесса обработки запроса
Логика обработки данного примера изложена в файле конфигурации config.yml
и состоит в следующем:
Согласно разделу connector
/ input
файла конфигурации config.yml
,
выполняется подключение в качестве consumer
к теме (topic) items
c
параметром group id равным kafka
на брокере Kafka, расположенном по
указанному адресу. Все получаемые сообщения передаются для обработки в
конвейер kafka_handler
;
Конвейер kafka_handler
вызывает функцию kafka_hlr
, описанную в
файле kafka_handler.lua
, которая упаковывает поступивший
объект с ключом routing_key
равным строке input_key
;
Согласно разделу connector
/ routing
файла конфигурации config.yml
,
все объекты с ключом input_key
передаются по исходящему маршруту to_kafka
;
В секции output
для раздела connector
указана единственная запись
to_kafka
, которая описывает отправку запроса в качестве Kafka producer.
Обратите внимание, что сохранения информации в системе не происходит. Обработка
объекта прекращается с отправкой его во внешнюю систему, поэтому в ремонтную
очередь ничего не добавляется.
3.1.6.3. Запуск рекурсивной отправки Kafka
Для выполнения действий с Kafka необходимо установить и запустить сервер
Kafka, а также создать в нем тему (topic) с именем items
. Для этого следуйте
инструкции из официальной документации по Kafka (http://kafka.apache.org/quickstart
), изменив название
темы на items
.
В целях наглядной демонстрации работы примера и просмотра сообщений, передаваемых
в выбранную тему (topic) Kafka, используйте любой consumer
Kafka, подключенный
к тому же брокеру и теме items
.
Далее мы рассмотрим использование модуля kafka-python
, установить который
можно командой pip install kafka-python
. Тогда для просмотра сообщений Kafka
выполните следующий скрипт на языке Python (используя интерактивный режим
интерпретатора или сохранив его в файл consumer.py
и запустив командой
python consumer.py
):
from kafka import KafkaConsumer
consumer = KafkaConsumer('items')
for message in consumer:
print (message)
Примечание
Для запуска скриптов потребуется Python версии 3 и выше, запуск которого в
вашей системе может выполняться командой python3
.
Для начала демонстрации примера необходимо отправить в Kafka валидный JSON объект
с полем version
. Откройте новую консоль (оставив работать consumer
,
запущенный ранее) и выполните следующий скрипт на языке Python (используя
интерактивный режим интерпретатора или сохранив в файл его в файл producer.py
и запустив командой python producer.py
):
from kafka import KafkaProducer
producer = KafkaProducer(bootstrap_servers='localhost:9092')
producer.send('items', b'{"id": "tarantool", "version": 0}')
Данный скрипт содержит подключение к Kafka в качестве producer
и отправку
простого JSON объекта: {"id": "tarantool", "version": 0}
.
В случае успеха TDG получит данный объект, обработает (увеличив
значение поля «version») и отправит его в ту же тему Kafka, вызывая повторное
получение, обработку, отправку и так далее. В окне с consumer
при этом
будут появляться все новые сообщения с постоянно