Простое приложение для Tarantool

Введение

В последнее время на конференциях и в сети размещается не мало статей по работе с платформой Tarantool от Mail.ru Group. И недавное мне подвернулась задача где я смог его попробывать.

Задача заключалось в том, что получая телеметрию от транспортных средств мне нужно было хранить посление точки от каждой машины и отдавать их через REST API, для оперативного отображения на карте при обращении клиента.

Так как делать постоянные обновления записей (около 600 операций в секунду) в осносвной СУБД PosgtreSQL трудоемко (по причине MVCC и vauucum не успевает отрабатывать), я решил попробывать Tarantool для этой задачи, так как он имеет высокую скорость обновления и его можно сделать персистентым, что для меня было критическим условием.

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

Все ниже описанные действия я производил с Tarantool 1.10

Особенности приложений tarantool

Первая особенность с которой я столкнулся это необходимость создания рабочей дирректории (параметр work_dir) для приложения tarantool, а также установки пользователей для приложения. Если этого не сделать приложения отлаживать крайне трудно и не понятно в каком состоянии находится хранилище.

Следующий ньюанс был связан с производительностью Go-шного драйвера go-tarantool, так как при дефолтных настройках он работал крайне медленно, и после долгого чтения документации нужной произвоительности я не добился, то я решил реализовать добовление точек в хранилище через endpoint, чтобы платформа сама управляла этими операциями без посредников, и это сразу решило проблему.

Написание приложения

Приложения для платформы tarantool пишутся на языках Lua и C. В моем случае я использовал Lua.

Исходя из описанного выше, приложение должно выпорлнять следующие действия:

  • отдавать список точек по ТС (или конретному ТС)
  • обновлять информацию по последней точке от ТС

Итак, первое что нужно сделать это создать конфигурацию приложения:

#!/usr/bin/env tarantool

box.cfg {
    listen = '*:6432';
    log_level = 7;
    work_dir = "/opt/tarantool/server"
}

Тут я задаю порт приложения для внешнего соедения (например из Go-приложения), уровень логирования и рабочую дирректорию.

Далее необходимо иниицализовать рабочее пространство нашего приложения:

box.once('init', function()
    box.schema.space.create('lastpoints', { if_not_exists = true, engine = 'vinyl' })
    box.space.lastpoints:format({
        { name = 'tracker_code', type = 'unsigned' },
        { name = 'lon', type = 'number' },
        { name = 'lat', type = 'number' },
        { name = 'point_id', type = 'unsigned' },
        { name = 'navigate_date', type = 'unsigned' },
        { name = 'course', type = 'unsigned' },
    })
    box.space.lastpoints:create_index('primary', { type = 'tree', parts = { 'tracker_code' } })
    box.space.lastpoints:create_index('secondary', { unique = false, type = 'tree', parts = { 'navigate_date' } })

    box.schema.user.grant('guest', 'read,write,execute', 'universe')
    box.schema.user.passwd('admin', 'admin')
end)

В этом коде создается пространство lastpoints с заданными полями, а также ряд индексов к ней:

  • primary - отвечает за уникальность кода устройства, от которого пришла точка
  • secondary - строит дерево по датам формирования точки, для быстрого поиска

Так как Tarantool по сути является key-value хранилищим, то индекс primary будет соответствовать уникальному ключу записи.

Также нужно важно указать engine = 'vinyl', если нужна персистентность данных, т. к. по-умолчанию tarantool хранит все данные в оперативной памяти. Подробнее можно прочитать в документации.

Вначале создадим функцию для добавления точек:

local function add_point_handler(req)
    local point = req:json()
    log.debug("Received point")
    log.debug(point)

    box.space.lastpoints:put {
        point.tracker_code,
        point.lon,
        point.lat,
        point.point_id,
        point.navigate_date,
        point.course,
    }
    log.debug("Point saved success")

    local resp = req:render{text=""}
    resp.status = 201
    return resp
end

Код довольно простой: он получает json определенного формата в теле запроса и укладывает его в пространосто lastpoints, которое было создано на этапе инициализации. Кроме того используется библиотека log для логирования процесса добавления точки.

Теперь нужен enpdoint через который мы сможем посмотреть добавленные точки. Он должен обладать следующими параметрами:

  • client - возвращает точку по конкретному устройству
  • delta - возвращает массив точек за интервал размером delta секунд
  • без параметров должны вернуться все точки, находящие в базе

Функция будет выглядеть так:

local function handler(req)
    local client = tonumber(req:param('client'))
    local delta = tonumber(req:param('delta'))
    local table_last_point = box.space.lastpoints
    local result = {}

    if client then
        local rec = table_last_point:get(client)
        if delta then
            if rec.navigate_date > os.time() + delta then
                table.insert(result, rec)
            end
        else
            table.insert(result, rec)
        end
    else
        for _, rec in box.space.lastpoints.index.secondary:pairs(delta, { iterator = 'GE' }) do
            table.insert(result, rec)
        end
    end
    return req:render { json = result }
end

В ней сналача проверяется задан ли код устройства и если он задан, то выбирается записть с ключом этого устрайства, а дальше она проверяется на попадание в интревал (если он задан). Если код устройства не задан, то происхрдит поиск записей у которых вторичный индекс (secondary) удовлетворяет заданному интервалу.

Когда функции реализованы, осталось подключить библиотеку http для реализации web сервера и роутинга:

#!/usr/bin/env tarantool

box.cfg {
    listen = '*:6432';
    log_level = 7;
    work_dir = "/opt/tarantool/server"
}

log = require('log')

box.once('init', function()
    box.schema.space.create('lastpoints', { if_not_exists = true, engine = 'vinyl' })
    box.space.lastpoints:format({
        { name = 'tracker_code', type = 'unsigned' },
        { name = 'lon', type = 'number' },
        { name = 'lat', type = 'number' },
        { name = 'point_id', type = 'unsigned' },
        { name = 'navigate_date', type = 'unsigned' },
        { name = 'course', type = 'unsigned' },
    })
    box.space.lastpoints:create_index('primary', { type = 'tree', parts = { 'bnso_code' } })
    box.space.lastpoints:create_index('secondary', { unique = false, type = 'tree', parts = { 'navigate_date' } })

    box.schema.user.grant('guest', 'read,write,execute', 'universe')
    box.schema.user.passwd('admin', 'admin')
end)

local function handler(req)
    local client = tonumber(req:param('client'))
    local delta = tonumber(req:param('delta'))
    local table_last_point = box.space.lastpoints
    local result = {}

    if client then
        local rec = table_last_point:get(client)
        if delta then
            if rec.navigate_date > os.time() + delta then
                table.insert(result, rec)
            end
        else
            table.insert(result, rec)
        end
    else
        for _, rec in box.space.lastpoints.index.secondary:pairs(delta, { iterator = 'GE' }) do
            table.insert(result, rec)
        end
    end
    return req:render { json = result }
end

local function add_point_handler(req)
    local point = req:json()
    log.debug("Received point")
    log.debug(point)

    box.space.lastpoints:put {
        point.tracker_code,
        point.lon,
        point.lat,
        point.point_id,
        point.navigate_date,
        point.course,
    }
    log.debug("Point saved success")

    local resp = req:render{text=""}
    resp.status = 201
    return resp
end

local server = require('http.server').new(nil, 8080)
server:route({ path = '/', method = 'GET' }, handler)
server:route({ path = '/', method = 'POST' }, add_point_handler)

server:start()

Заключение

Создать приложений на Tarantool действительно просто, но вот сторонние драйвера для работы с ним мне показались сыроватыми. Тут есть несколько вариантов действий или обходное решение (как в моем случае) или же скурпулезно изучать документацию и крутить всевозможные настройки которых предостаточно.

В целом впечатления у меня остались положительные и если еще подвернется возможность буду его использовать в дальнейшем, тем более что у него предостаточно библиотек, даже ечть специальная библиотека gis для работы с геоданными.

Ссылки

  1. Tarantool. Общие сведения
  2. Осваиваем Tarantool 1.6
 
comments powered by Disqus