Содержание

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@<name_app>».

Параметры приложению можно заливать при создании, в основном файле «.lua», в интерактивном режиме, либо скриптами в процессе работы, утилитой «tarantoolctl eval app_name».
Примененные впоследствии скрипты не сохраняют своего действия при перезагрузке приложения + перезагрузка основной службы не влияет на работу приложений (инстансов).

Система может выполняться:

:!: Пример конфиг инстанса + создание структуры БД

За основу можно взять файл «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), бывают как минимум два типа:

Есть два движка хранения данных: «memtx» и «vinyl», первый хранит данные в ОЗУ, асинхронно записывая на диск для сохранности, второй классический вариант хранения на диске, оптимизирован для большого кол-ва операций.

Работа

После запуска инсталляции, подключится в режиме консоли можно:

# tarantoolctl enter <name>

Перечень запущенных инстансов можно просмотреть в списке процессов ОС:

# 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() для удаления)

Для создания объектов, нужна привилегия 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()- обеспечивает безопасное поведение в случае ошибки.

Кластер серверов

Оф документация
Ставим Tarantool, Cartridge-Cli, после запуска UI доступен в веб странице по порту 8081.
В кластере есть две служебные роли:

Масштабирование кластера собсна и происходит добавлением доп хранилищ (шард), в настройках параметр «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 функции модуля 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», и вставка пары кортежей (записей).

:!: