====== Tarantool ======
===== Сведения =====
:!: Установка
# Общее
apt install gnupg2 curl lsb-release
# Экспортируем ключи
curl https://download.tarantool.org/tarantool/release/2.8/gpgkey | sudo apt-key add -
# Добавляем репозиторий
echo "deb https://download.tarantool.org/tarantool/release/2.8/debian/ `lsb-release -c -s` main" | sudo tee /etc/apt/sources.list.d/tarantool_2_8.list
echo "deb-src https://download.tarantool.org/tarantool/release/2.8/debian/ `lsb-release -c -s` main" | sudo tee -a /etc/apt/sources.list.d/tarantool_2_8.list
# Ставим
apt update && apt install tarantool
Все строится **вокруг инстанса** (приложения). Пользователи/роли/объекты **заводятся внутри него** и относятся к нему. В инстансе задается порт прослушивания, системные конфиги и т.д.\\
Для хранения конфигурации есть две папки "**instances.available**" и "**instances.enabled**", из второй приложения **автозапускаются при старте** службы. (по логике файлы создаются в первой, и при активации ссылка создается в **enabled**, но так чето не работает, даже если создать ссылку вручную, пока что работает если файл сразу поместить в **enabled**.)\\
Для управления служит утилита "**tarantoolctl**", либо "systemctl tarantool@".\\
Параметры приложению можно заливать **при создании**, в основном файле ".lua", в **интерактивном режиме**, либо **скриптами** в процессе работы, утилитой **"tarantoolctl eval app_name"**.\\
Примененные впоследствии скрипты **не сохраняют своего действия** при перезагрузке приложения + перезагрузка **основной службы не влияет на работу приложений** (инстансов).\\
Система может выполняться:
* интерактивно- просто запустить tarantool
* в режиме скрипта- подать аргументом скрипт, ПО выполнит его и завершится
* в режиме серверного приложения, принимая tcp-запросы - в конфигурации нужен блок "box.cgf", с указанием порта и т.д. Для фонового исполнения нужно еще добавить параметры (разделяются ; либо ,):
* background
* log
* pid_file
:!: Пример конфиг инстанса + создание структуры БД
За основу можно взять файл "example.lua", в папке "available"
box.cfg {
-- Здесь параметры остались по умолчанию
}
local function bootstrap()
-- Создание пользователя
box.schema.user.create('good', { password = 'secret' })
box.schema.user.grant('good', 'read,write,execute', 'universe')
-----------------------------------
-- Пространство стикеров
local stickers = box.schema.create_space('stickers')
-- Индекс для файла
stickers:create_index('primary', {type = 'TREE', parts = {1, 'unsigned'}})
-- Индекс для рейтинга
stickers:create_index('secondary', {type = 'TREE', unique = false, parts = {2, 'integer'}})
-- Индекс для названий стикеров
stickers:create_index('ternary', {type ='HASH', parts = {3, 'string', 4, 'string'}})
-----------------------------------
-- Пространство стикер-пака
local packs = box.schema.create_space('packs')
-- Индекс для стикер-пака
packs:create_index('primary', {type = 'HASH', parts = {1, 'string'}})
-- Индекс для рейтинга пака
packs:create_index('secondary', {type = 'TREE', unique = false, parts = {2, 'integer'}})
end
-- При первом запуске создаем пространство и назначаем привилегии
box.once('example-2.0', bootstrap)
===== База данных =====
Записи хранятся **в пространствах (space)**, аналог **таблицы** в реляционной базе.\\
Внутри пространства находятся **кортежи (tuples)**, похоже на строку в таблице и на JSON-массив данных.\\
Необходимо создавать индексы (index), бывают как минимум два типа:
* **HASH** - значения уникальны, например key/value- хранилища (Map)
* **TREE** - не уникальны, получается массив, у которого могут быть пропущены элементы
* **RTREE** и **BITSET** - поиск на двумерной плоскости и работа с битовыми данными
Есть два движка хранения данных: **"memtx"** и **"vinyl"**, первый хранит данные в ОЗУ, асинхронно записывая на диск для сохранности, второй классический вариант хранения на диске, оптимизирован для большого кол-ва операций.\\
===== Работа =====
После запуска инсталляции, подключится в **режиме консоли** можно:
# tarantoolctl enter
Перечень запущенных инстансов можно просмотреть в списке процессов ОС:
# ps -ef | grep tarantoolctl
Запуск в докере "картриджа"
# docker run -p 3301:3301 -p 8081:8081 tarantool/getting-started
Подключение к консоли по сети (из другой консоли)
# Пока под вопросом. вроде работает но какая то специфика тут есть
tarantool> net_box = require('net.box')
tarantool> conn = net_box.connect(3301)
# Работает но непонятно с кластером в связке с Докером
$ tarantoolctl connect user:passwd@host:port
# Внутри консоли так работает:
tarantool> console = require('console')
tarantool> console.connect('user:passwd@host:port')
#
==== Пользователи ====
:!: **Максимальное количество** пользователей - 32\\
Пользователи содержатся в пространстве **_user**
> box.space._user:select()
> box.space._user:select{id}
Работа происходит через схему **schema.user**
> box.schema.user.create('user-name', {password= '..'})
> box.schema.user.passwd('user-name', 'new-passwd')
> box.schema.user.drop('user-name')
> box.schema.user.exists('user-name')
> box.schema.user.info('user-name')
# Возвращает хэш пароля
> box.schema.user.password('user-name')
Если у пользователя нет пароля, то подключится с его помощью **не получится**. Он рассматривается только как **внутренний**. Такие могут быть полезны с процедурами с **SETUID**, в которых **привилегии предоставляются** пользователям с внешним подключением.\\
==== Привилегии ====
В системе одна основная база данных- "**box.schema**" (**universe**), содержит **все объекты**. Владелец- "admin"\\
**Владельцы** автоматом получают привилегии **создаваемых объектов**, можно делится с пользователями или ролями командой- "**box.schema.user.grant()**" (revoke() для удаления)\\
* **read (write)** - чтение/запись данных
* **execute** - например вызов ф-ции или использование роли
* **create** - box.schema.space.create
* **alter** - box.space.x.inde.y:alter
* **drop** - box.sequence.x:drop
* **usage** - допустимо ли какое либо д-е независимо от других привилегий.. хз
* **session** - может ли пользователь подключаться
Для **создания объектов**, нужна привилегия **create** и как минимум **read, write** в системном пространстве.\\
Пару примеров:
> box.schema.user.grant('User','read,write,execute,create,drop','universe')
> box.schema.user.grant('User','write', 'space', '_schema')
> box.schema.user.grant('User','create,read','space','T')
> box.schema.user.grant('User','drop','sequence')
> box.schema.user.grant('User','execute','function','F')
> box.schema.user.grant('User','create','role')
:!: Пример: Создание пользователей и объектов с предоставлением привилегий
Функция будет выполняться с привилегиями внутреннего пользователя, вызываясь при этом другим пользователем.
box.schema.space.create('u')
box.schema.space.create('i')
box.space.u:create_index('pk')
box.space.i:create_index('pk')
box.schema.user.create('inner-user')
box.schema.user.grant('inner-user', 'read,write', 'space', 'u')
box.schema.user.grant('inner-user', 'read,write', 'space', 'i')
box.schema.user.grant('inner-user', 'create', 'universe')
box.schema.user.grant('inner-user', 'read,write', 'space', '_func')
function read_and_modify(key)
local u= box.space.u
local i= box.space.i
local fiber= require('fiber')
local t= u:get{key}
if t ~= nil then
u:put{key, box.session.uid()}
i:put{key, fiber.time()}
end
end
box.session.su('inner-user')
box.schema.func.create('read_and_modify', {setuid= true})
box.session.su('admin')
box.schema.user.create('public-user', {password= 'secret'})
box.schema.user.grant('public-user', 'execute', 'function', 'read_and_modify')
==== Роли ====
Роли являются **контейнерами привилегий**, которые могут применяться к пользователям. Информация о ролях хранится в пространстве "**_user**"- третье поле является ролью.\\
"**usage**" и "**session**" - не могут быть назначены ролями.\\
**Роли могут быть вложенными**, т.е. `роль2` может содержать в себе `роль1`, тогда к пользователю будет применена `роль2` вместе с `роль1` неявно.\\
Есть два способа управлять ролями:\\
> box.schema.user.grant-or-revoke(user-name-or-role-name,'execute', 'role',role-name...)
> box.schema.user.grant-or-revoke(user-name-or-role-name,role-name...)
Пару примеров:
> box.schema.role.create('Role1')
> box.schema.role.grant('Role1', 'Role2')
> box.schema.user.grant('User1', 'Role1')
-- Даже после связки ролей, изменения применятся
> box.schema.role.grant('Role2', 'read,write', 'space', 'T')
==== Потоки/процессы ====
В консольном режиме "fiber= require('fiber')", и далее через эту переменную можно получить информацию о работающих потоках, есть целый ряд методов.\\
==== Скрипты ====
Интерпретатор скриптов: "/usr/bin/evn tarantool", с помощью его можно иcполнить любой скрипт на lua.\\
Можно просто дать скрипт аргументом процессу он его выполнить и завершится: "tarantool my-script.lua"\\
Вызов функции внутри **pcall()**- обеспечивает безопасное поведение в случае ошибки.\\
==== Кластер серверов ====
[[https://www.tarantool.io/ru/doc/latest/getting_started/|Оф документация]]\\
Ставим Tarantool, Cartridge-Cli, после запуска UI доступен в веб странице по порту 8081.\\
В кластере есть две служебные роли:
* **Storage** - хранилище данных
* **Router** - посредник между клиентами и хранилищем
Масштабирование кластера собсна и происходит добавлением доп хранилищ (шард), в настройках параметр "Replica set weight" регулирует приоритет использования, по всей видимости, можно вкл/выкл и данные "перетекают" из одного шарда в другой.\\
:!:
===== Пространства =====
==== box.space._space ====
==== box.space._priv ====
==== box.space._user ====
:!:
Остались вопросы:
* подключение к консоли кластера
* либо нужно разобраться как поднимать кластер из обычной установки
===== Примеры =====
:!: "**Вставка 1 млн кортежей** с помощью хранимой процедуры"
Серверное приложение, создание одного спейса, пара функций, генерация рандомной символьной строки, вставка таких строк в цикле, + показан пример замера времени работы скрипта
Файл конфига сервер-аппа:
box.cfg {
listen = '*:3311';
--slab_alloc_arena= 0.2;
io_collect_interval = nil;
readahead = 16320;
memtx_memory = 128 * 1024 * 1024;
memtx_min_tuple_size = 16;
memtx_max_tuple_size = 128 * 1024 * 1024;
vinyl_memory = 128 * 1024 * 1024;
vinyl_cache = 128 * 1024 * 1024;
vinyl_max_tuple_size = 128 * 1024 * 1024;
vinyl_write_threads = 2;
wal_mode = "none";
wal_max_size = 256 * 1024 * 1024;
checkpoint_interval = 60 * 60;
checkpoint_count = 6;
force_recovery = true;
log_level = 5;
log_nonblock = false;
too_long_threshold = 0.5;
}
local function startbuild()
--local space = box.schema.create_space('example')
--space:create_index('primary')
-- Создание пользователя
box.schema.user.create('user2', {password = 'user2'})
box.schema.user.grant('user2', 'read,write,execute,create,session', 'universe')
---------------------------
----- Пространство стикеров
local tester = box.schema.create_space('tester')
-- Индекс для файла
tester:create_index('primary', {type= 'TREE', parts= {1, 'unsigned'}})
end
function string_function()
local random_number
local random_string
random_string = ""
for x = 1,10,1 do
random_number = math.random(65, 90)
random_string = random_string .. string.char(random_number)
end
return random_string
end
function main_function()
local string_value, t
for i = 1,1000000,1 do
string_value = string_function()
t = box.tuple.new({i,string_value})
box.space.tester:replace(t)
end
end
-- Создание пространства и привилегий
box.once('wtf', startbuild)
Использование:
tarantool> start_time = os.clock()
tarantool> main_function()
tarantool> end_time = os.clock()
tarantool> 'insert done in ' .. end_time - start_time .. ' seconds'
tarantool> box.space.tester:select{1}
:!: **Подсчет значений поля** в пространстве
Подсчет происходит в этой функции\\
"Pairs()" типа массив всех строк, по которым и делаем цикл\\
json= require('json')
function sum_json_field(field_name)
local v, t, sum, field_value, is_valid_json, lua_table
sum= 0
for v, t in box.space.tester:pairs() do
is_valid_json, lua_table= pcall(json.decode, t[2])
if is_valid_json then
field_value= lua_table[field_name]
if type(field_value)== "number" then sum= sum + field_value end
end
end
return sum
end
В двух записях намерено допущены ошибки, эти строки пропускаются
-- Создание/Заполнение пространства
box.space.tester:drop()
box.schema.space.create('tester')
box.space.tester:create_index('primary', {parts = {1, 'unsigned'}})
box.space.tester:insert{445, '{"Item": "widget", "Quantity": 7}'}
box.space.tester:insert{446, '{"Item": "golf club", "Quantity": "sunshine"}'}
box.space.tester:insert{447, '{"Item": "waffle iron", "Quantit": 3}'}
-- Вызов ф-ции
sum_json_field("Quantity")
:!: **Хранимые процедуры на С**
Проверьте наличие пакета "tarantool-dev"\\
Создайте и скомпилируйте следующий модуль (easy.c):
#include "module.h"
int easy(box_function_ctx_t *ctx, const char *args, const char *args_end)
{
printf("hello world\n");
return 0;
}
int easy2(box_function_ctx_t *ctx, const char *args, const char * args_end)
{
printf("hello world -- easy2\n");
return 0;
}
Скомпилировать можно командой:
$ gcc -shared -o easy.so -fPIC easy.c
Далее откройте клиент tarantool в интерактивном режиме:
box.cfg{listen=3306}
box.schema.space.create('capi_test')
box.space.capi_test:create_index('primary')
net_box = require('net.box')
capi_connection = net_box:new(3306)
box.schema.func.create('easy', {language = 'C'})
box.schema.user.grant('guest', 'execute', 'function', 'easy')
capi_connection:call('easy')
Находясь в той же директории где скомпилирован модуль С, тарантул находит указанные файлы.\\
Если название функции отличается он файла, то нужно указывать его:
box.schema.func.create('easy.easy2', {language = 'C'})
box.schema.user.grant('guest', 'execute', 'function', 'easy.easy2')
capi_connection:call('easy.easy2')
:!: **Еще пример использования С**
В основном используются API функции [[https://github.com/tarantool/msgpuck|модуля msgpuck]]\\
"hardest.c"
#include "/usr/include/tarantool/module.h"
#include "/var/msgpuck/msgpuck.h"
int hardest(box_function_ctx_t *ctx, const char *args, const char *args_end)
{
uint32_t space_id= box_space_id_by_name("capi_test", strlen("capi_test"));
char tuple[1024];
char *tuple_pointer= tuple;
tuple_pointer= mp_encode_array(tuple_pointer, 2);
tuple_pointer= mp_encode_uint(tuple_pointer, 10000);
tuple_pointer= mp_encode_str(tuple_pointer, "String 2", 8);
int n= box_insert(space_id, tuple, tuple_pointer, NULL);
return n;
}
После компиляции модуля выполните в терминале тарантула:
box.schema.func.create('hardest', {language = "C"})
box.schema.user.grant('guest', 'execute', 'function', 'hardest')
box.schema.user.grant('guest', 'read,write', 'space', 'capi_test')
capi_connection:call('hardest')
-- Выполнение
box.space.capi_test:select()
:!: **Использование SQL**
Подключаемся к консоли, двумя командами задаем язык и разделитель команд:\\
tarantool> \set language sql
tarantool> \set delimiter ;
Далее выполняем команды в формате SQL
tarantool> create table table1 (column1 integer primary key, column2 varchar(100));
tarantool> insert into table1 values (1, 'A'); insert into table1 values (2, 'B');
tarantool> update table1 set column2='AB' where column1='2';
tarantool> select * from table1;
Результат аналогичен использованию команд типа "box.space..."\\
Здесь создается пространство "table1", и вставка пары кортежей (записей).\\
:!: