Приложение для Tarantool — это, по сути, набор хранимых процедур, используемых как API. Данные обрабатываются на стороне хранилища, что позволяет значительно повысить производительность. Однако поддержка хранимых процедур может превратиться в кошмар.


Может. Но не сегодня.


Сегодня мы рассмотрим вопросы обеспечения качества приложения. В частности, поговорим о тестировании, разберемся, как запуститься в production, как использовать коннекторы, а также поговорим о тонкостях миграции схемы данных.



Содержание цикла «Приложения для Tarantool 1.7»


Часть 1. Хранимые процедуры
Часть 2. OAuth2-авторизация
— Часть 3. Тестирование и запуск


Напомню, что все примеры в этом данном цикле статей основаны на приложении для авторизации пользователей tarantool-authman.


Тестирование


Тестирование — только один из способов повысить качество.


В случае с хранимыми процедурами, функциональные тесты — это хороший вариант поддержки актуального API. Для тестирования используется встроенный в Tarantool модуль tap (Test Anything Protocol). Он включает в себя несколько методов для базовых проверок и сравнения Lua-объектов. Давайте протестируем два кейса регистрации пользователя auth.registration(email).


local case = {}

local tap = require('tap')
local response = require('authman.response')
local error = require('authman.error')
local db = require('authman.db')
local config = require('test.config')

-- Инициализируем приложение для тестирования
local auth = require('authman').api(config)
local test = tap.test('registration_test')

-- Очищаем space перед каждым запуском теста
function case.before() db.truncate_spaces() end
function case.after() end

function test_registration_succes()
    local ok, code
    ok, code = auth.registration('test@test.ru')

    -- Проверим, что регистрация прошла успешно, а тип возвращаемого параметра — строка
    test:is(ok, true, 'test_registration_succes user created')
    test:isstring(code, 'test_registration_succes code returned')
end

function test_registration_user_already_exists()
    local ok, code, got, expected, user
    ok, code = auth.registration('test@test.ru')
    -- Один из методов API, который активирует пользователя с паролем
    ok, user = auth.complete_registration('test@test.ru', code, 'password')

    -- Проверим, что значение соответствует ожидаемой ошибке
    -- Метод возвращает два параметра, поэтому для упрощения проверки вызов обернут в инициализацию Lua-таблицы
    got = {auth.registration('test@test.ru'), }
    expected = {response.error(error.USER_ALREADY_EXISTS), }
    -- is_deeply проверяет совпадение ключей и данных в Lua-таблицах
    test:is_deeply(got, expected, 'test_registration_user_already_active')
end

case.tests = {
    test_registration_succes,
    test_registration_user_already_exists
}

return case

Теперь напишем небольшой скрипт для запуска тестов. Не забудем также учесть методы before и after, которые упростят тестирование.


-- Прежде всего, необходимо запустить Tarantool
box.cfg {
    listen = 3331,
}

local TEST_CASES = {
    'test.case.registration',
}

function run()
    for case_index = 1, #TEST_CASES do
        local case = require(TEST_CASES[case_index])

        for test_index = 1, #case.tests do
            -- Вызов конкретного теста
            case.before()
            case.tests[test_index]()
            case.after()
        end
    end
end

run()

Результат запуска тестов:


$ tarantool test/authman.test.lua
...
TAP version 13
ok — test_registration_succes user created
ok — test_registration_succes code returned
ok — test_registration_user_already_active

Запуск инстанса


Приложение написано и протестировано, а значит, пришло время разворачивать его в production-среде.


Для управления инстансами Tarantool используется встроенная утилита tarantoolctl. Сначала создадим инстанс с приложением. Не забудем добавить пользователя и выдать ему права.


box.cfg {
    listen = 3331;
}

local function bootstrap()
    box.schema.user.create('my_user', {password = '123'})
    box.schema.user.grant('my_user', 'read,write,execute', 'universe')
end

box.once('init_user', bootstrap)

config = {
    -- Конфигурации приложения
}

-- Объявляем переменную auth глобально
auth = require('auth').api(config)

Теперь создадим символьную ссылку на этот файл из директории instances.enabled:


sudo ln -s /etc/tarantool/instances.available/auth.lua /etc/tarantool/instances.enabled/auth.lua

Запустим tarantoolctl и проверим, что можем подключиться к Tarantool и использовать методы приложения:


$ tarantoolctl start auth
$ tarantoolctl enter auth
connected to unix/:/var/run/tarantool/auth.control

unix/:/var/run/tarantool/auth.control> ok, user = auth.registration('ivanov@mail.ru')
unix/:/var/run/tarantool/auth.control> ok, user = auth.complete_registration('ivanov@mail.ru', user.code, '123')
unix/:/var/run/tarantool/auth.control> user
---
- is_active: true
  email: ivanov@mail.ru
  id: 8cd27d26-3974-43d6-a2b2-87202664753d
...

Если что-то пошло не так, следует посмотреть логи /var/log/tarantool/auth.lua. Подробнее об администрировании сервера Tarantool можно почитать в документации.


Коннектор


Одно из преимуществ хранимых процедур — предоставление общего интерфейса для сервисов, написанных на разных языках программирования.



Рассмотрим пример асинхронного использования Tarantool с помощью Python-коннектора.


import asyncio
import asynctnt

async def create_user():
    tnt_connection = asynctnt.Connection(
        host='127.0.0.1', port='3367', username='my_user', password=’123’
    )

    # Асинхронная регистрация пользователя
    user_data = await tnt_connection.call(
        'auth.registrtion', ['exaple@mail.ru', ]
    )

    await tnt_connection.disconnect()

# Вызовем создание пользователя
loop = asyncio.get_event_loop()
loop.run_until_complete(create_user())

Стоит отметить, что взаимодействовать с Tarantool можно также из другого инстанса Tarantool с помощью встроенного модуля net.box. Подробнее о нём читайте в документации.


Миграции данных


Распространенная задача, когда приложение уже запущено в бою — изменение схемы данных. Например, пользователю требуется добавить поле gender.



Для этого нужно создать скрипт, изменяющий все данные в space auth_user. Сам скрипт можно добавить в вызов инициализации приложения, а функция box.once ограничит повторный вызов.


local fiber = require('fiber')

local function migrations()

    -- Выполним миграцию только один раз
    box.once('20171101_add_gender', function ()
        local counter = 0
        -- Обход space с помощью итератора сократит использование оперативной памяти
        -- В памяти окажется только текущий объект
        for _, tuple in box.space.auth_user:pairs(
            nil, {iterator=box.index.ALL}
        ) do

            local user_tuple = tuple:totable()
            -- Запишем в таблицу новые данные
            user_tuple[4] = get_user_gender()
            box.space.auth_user:replace(user_tuple)

            counter = counter + 1
        end
    end)
end

migrations()

Несмотря на то, что миграция может выполняться долго, главный поток исполнения не блокируется. Это связано с тем, что метод replace неявно вызывает yield. Для того чтобы явно освободить поток исполнения, нужно вызвать метод fiber.sleep(0). Подробнее об этом можно прочитать в документации.


Что дальше?


Tarantool не стоит на месте и активно развивается.


За рамками этого обучающего цикла статей осталось несколько важных вопросов, на которые стоит обратить внимание при написании собственных сервисов:


  • Шардирование и репликация. Использование модуля vshard.
  • Поддержка SQL-синтаксиса в Tarantool. Оптимизация хранимых процедур с помощью SQL.
  • Обзор дискового движка Vinyl. Сравнение с Memtx, или «Что делать, если закончилась оперативка?».
  • Оптимизация и профилирование хранимых процедур, написанных на языке C.

Напоминаю, что все примеры кода взяты из приложения tarantool-authman. Оно тоже развивается, и теперь может быть использовано как Oauth2-server.


Если у вас остались вопросы, выходящие за рамки цикла статей, то задать их можно в телеграм-чате. Отзывчивое сообщество Tarantool, скорее всего, сможет вам помочь.


Спасибо всем, кто дождался третьей части.


До новых встреч!


Сообщество Tarantool проводит первую конференцию по in-memory computing 21 июня в Москве. Доклады ещё принимаются, call for papers открыт.

Комментарии (0)