Хабрахабр

Приложения для Tarantool. Часть 3. Тестирование и запуск

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

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

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

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

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

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

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

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

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
...

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

Коннектор

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

Рассмотрим пример асинхронного использования 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. Распространенная задача, когда приложение уже запущено в бою — изменение схемы данных.

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

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.

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

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

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

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

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»