====== 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", и вставка пары кортежей (записей).\\
:!: