Хранимые процедуры, модули, организация приложения

Рейтинг: 61% · 6 голосов
Исчерпывающий курс по Tarantool 3.x: модель данных, движки memtx и vinyl, Lua и файберы, транзакции и MVCC, SQL, конфигурация (box.cfg и декларативная 3.x), репликация и Raft, шардирование vshard, эксплуатация, безопасность. 47 уроков со схемами.
Ответить
Аватара пользователя
denis_tnt
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Хранимые процедуры, модули, организация приложения

Сообщение denis_tnt »

Оглавление курса (47)
  1. Что такое Tarantool: in-memory СУБД и сервер приложений
  2. Архитектура изнутри: процесс, потоки, event-loop
  3. Установка и первый запуск: tt CLI, пакеты, Docker
  4. Интерактив: консоль, admin-консоль, первые команды
  5. Спейсы и кортежи: форматы, типы данных
  6. Типы индексов и их применимость
  7. Движки хранения: memtx vs vinyl
  8. DDL: схема, создание спейсов и индексов, миграции
  9. DML и выборки: insert/update/upsert, итераторы
  10. Персистентность: WAL, снапшоты, recovery
  11. Внутренности memtx: аллокаторы slab/arena, память
  12. Внутренности vinyl: LSM, компакция, тюнинг
  13. Lua и LuaJIT в Tarantool: box, модули, rocks
  14. Файберы: кооперативная многозадачность, каналы
  15. Транзакции: ACID, изоляция, MVCC
  16. Хранимые процедуры, модули, организация приложения (вы здесь)
  17. net.box: удалённые вызовы, async
  18. Пулы соединений, балансировка, реконнект
  19. Ошибки и диагностика: box.error, pcall
  20. Типы и сериализация: MsgPack, decimal, datetime, uuid
  21. SQL в Tarantool: возможности и связь с box
  22. SQL: таблицы, JOIN, подзапросы, представления
  23. SQL: подготовленные выражения, транзакции, Lua-интероп
  24. Классическая конфигурация box.cfg (legacy 1.x/2.x)
  25. Декларативная конфигурация 3.x: config.yaml, иерархия
  26. Роли и приложения в 3.x
  27. Централизованная конфигурация: etcd / config storage
  28. tt CLI глубоко: разработка, сборка, запуск
  29. Cartridge (официальный legacy) и миграция на 3.x
  30. Репликация: replicaset, топологии
  31. Механика репликации: WAL-стриминг, vclock
  32. Синхронная репликация и выборы лидера (Raft)
  33. Жизненный цикл узла: bootstrap, join, rejoin
  34. vshard: router/storage, виртуальные бакеты
  35. Решардинг и rebalancing бакетов
  36. Запросы поверх шардов: map-reduce, crud
  37. Мониторинг: метрики, Prometheus, Grafana
  38. Логирование и аудит
  39. Бэкапы и восстановление
  40. Безопасность: аутентификация, RBAC, TLS
  41. Производительность: профилирование, тюнинг
  42. Обновления: схема, rolling upgrade
  43. Деплой в продакшен: Docker, топология (официальные паттерны)
  44. Администрирование через официальный TCM (Tarantool Cluster Manager)
  45. Коннекторы: Python, Go, Java
  46. Ключевые модули (rocks): crud, metrics, queue, expirationd
  47. Capstone: шардированный отказоустойчивый кластер
Обзор: зачем Tarantool совмещает БД и сервер приложений

Tarantool это не просто хранилище, а полноценный сервер приложений: тот же процесс, что держит данные в памяти, исполняет ваш код на Lua. Поэтому хранимая процедура в Tarantool это обычная Lua-функция, которая живет в адресном пространстве сервера и работает с данными напрямую, без сетевого round-trip к БД. Вызов такой функции по сети называется RPC поверх протокола iproto.

В этом уроке разберем три вещи, которые часто путают: (1) модуль приложения (Lua-файл с кодом), (2) объект box.func (метаданные функции, зарегистрированной в схеме БД), (3) механику вызова функции локально и удаленно. Понимание границы между ними и есть ключ к правильной организации приложения.

Механика: где живет код и где живет имя функции

Важно с самого начала развести два мира.

Мир Lua-рантайма. Когда вы пишете [c]local m = require('mymodule')[/c], Lua грузит файл [c]mymodule.lua[/c], исполняет его, и результат (обычно таблица с функциями) кешируется в таблице [c]package.loaded['mymodule'][/c]. Повторный [c]require[/c] вернет тот же закешированный объект, файл заново не читается. Пути поиска берутся из [c]package.path[/c] (для .lua) и [c]package.cpath[/c] (для .so). Эти функции существуют только в памяти процесса и нигде не персистятся.

Мир схемы БД. Параллельно есть системный спейс _func, в котором хранятся зарегистрированные функции. Регистрация делается через [c]box.schema.func.create()[/c]. Зарегистрированная функция получает запись в [c]_func[/c] и становится доступна как объект [c]box.func.ИМЯ[/c]. Главное отличие от просто Lua-функции: на функцию из [c]_func[/c] можно выдавать привилегии (grant execute), и ее можно вызвать по сети через iproto.

Эти миры пересекаются двумя способами. Первый: вы регистрируете в схеме имя глобальной Lua-функции (без тела) - тогда [c]_func[/c] хранит только метаданные и права, а сам код берется из Lua-рантайма по этому имени. Второй: вы передаете тело функции в опции body - тогда исходник функции сохраняется прямо в снапшоте БД. Такая функция называется персистентной: после рестарта сервера она восстановится из снапшота, ее не нужно заново определять в коде приложения.

Изображение
Путь от модуля к box.func и вызову

Что значит "персистентная" под капотом. Строка с телом функции пишется в [c]_func[/c], попадает в снапшот и WAL, реплицируется на реплики. При старте Tarantool компилирует это тело обратно в Lua-функцию. Отсюда два следствия: персистентная функция автоматически появляется на всех репликах, и ее можно сделать sandboxed (детерминированной, без доступа к глобальным переменным) - именно такие функции разрешено использовать в функциональных индексах и ограничениях (constraints).

Ключевые команды и код

1) Модуль приложения (переиспользуемый код).

Код: Выделить всё

-- mymodule.lua
local M = {}
function M.greet(name)
    return 'Hello, ' .. name
end
return M

Код: Выделить всё

-- использование
local mymodule = require('mymodule')
print(mymodule.greet('Tarantool'))
-- при необходимости расширить пути поиска ДО require:
-- package.path = 'scripts/?.lua;' .. package.path
2) Непостоянная функция в схеме (только имя и права).

Код: Выделить всё

function add_player(name) -- глобальная Lua-функция
    return box.space.players:insert({name})
end
box.schema.func.create('add_player')           -- запись в _func
box.schema.user.grant('guest', 'execute',
                      'function', 'add_player') -- право на вызов
3) Персистентная функция (тело хранится в снапшоте).

Код: Выделить всё

local code = [[function(a, b) return a + b end]]
box.schema.func.create('sum', {body = code})

box.func.sum            -- посмотреть метаданные (id, language, body...)
box.func.sum:call({1, 2})   -- -> 3
Обратите внимание: у [c]box.func.sum[/c] есть метод :call({...}), аргументы передаются массивом. Это не то же самое, что просто вызвать Lua-функцию скобками.

4) Удаленный вызов через net.box.

Код: Выделить всё

local netbox = require('net.box')
local conn = netbox.connect('127.0.0.1:3301')
conn:call('sum', {2, 3})    -- -> 5, выполнится на сервере
Сравнение способов вызова (моноширинно):

Код: Выделить всё

способ                   что вызывает          по сети   нужен _func/grant
-----------------------  --------------------  --------  -----------------
m.greet(x)               локальную Lua-функ.   нет       нет
box.func.NAME:call{...}  функцию из схемы      нет       да (создана)
conn:call('NAME',{...})  функцию на сервере    да        да + grant execute
box.schema.func.create   регистрирует имя      -         создает запись
Языки функций. Опция language в [c]func.create[/c]: LUA (по умолчанию), C (импорт из .so по имени, перезагрузка через [c]box.schema.func.reload()[/c]), SQL_EXPR (SQL-выражение только для constraints). Для C-модуля reload грузит новую копию .so рядом со старой и переключает новые вызовы на нее; старые вызовы доживают на прежней версии (загрузка с RTLD_LOCAL).

Частые заблуждения и грабли
Заблуждение: "создал box.schema.func.create - значит код сохранился". Нет. Без опции body в _func попадает только ИМЯ и права. Сам код по-прежнему живет в Lua-рантайме и пропадет после рестарта, если вы не определите функцию заново в стартовом скрипте.
  • require кеширует. Поправили .lua-файл - повторный [c]require[/c] вернет старую версию из [c]package.loaded[/c]. Для горячей перезагрузки модуля делают [c]package.loaded['mymodule'] = nil[/c] и затем снова [c]require[/c]. Это и есть основа hot-reload без рестарта.
  • :call принимает массив. [c]box.func.sum:call({1, 2})[/c], а не [c]:call(1, 2)[/c]. Частая ошибка новичков.
  • Аргументы и результат сериализуются в MsgPack. По сети нельзя передать Lua-функцию, метатаблицу или замыкание - только данные. Для горячего пути есть опция [c]takes_raw_args=true[/c]: аргументы приходят MsgPack-объектом без декодирования в Lua.
  • setuid работает только через бинарный порт. В консоли и в Lua-скрипте опция игнорируется - права считаются по текущему пользователю.
  • Долгий код в функции блокирует поток. Tarantool кооперативно-многозадачный: функция выполняется в фибере на одном TX-потоке. Тяжелые циклы без yield (или без обращений к БД, которые делают yield сами) подвесят весь инстанс.
  • Глобальные функции засоряют namespace. В 3.x чистый способ организации - не плодить глобальные функции, а оформлять логику как роль приложения: Lua-модуль, возвращающий объект с методами [c]validate[/c], [c]apply[/c], [c]stop[/c]. Роль включается декларативно в YAML-конфиге и перезагружается без рестарта инстанса.
Двойной трек. В 1.x/2.x стартовый скрипт с [c]box.cfg{}[/c] сам создавал спейсы и регистрировал функции (часто внутри [c]box.once()[/c]). В 3.x box.cfg прячется за декларативный YAML-конфиг, а прикладной код собирается из ролей. Но сам механизм [c]box.schema.func[/c] и [c]box.func[/c] в обоих треках идентичен - меняется только то, кто и когда вызывает регистрацию.

Мини-лаба
  • Запустите Tarantool, выполните [c]box.cfg{listen=3301}[/c]. Создайте персистентную функцию [c]mul[/c] с телом [c]function(a,b) return a*b end[/c]. Проверьте [c]box.func.mul:call({6,7})[/c]. Затем посмотрите запись в схеме: [c]box.space._func.index.name:select{'mul'}[/c] и убедитесь, что тело функции лежит прямо в кортеже. Бонус: выдайте [c]guest[/c] право execute и вызовите [c]mul[/c] из второй сессии через [c]net.box[/c].
Контрольные вопросы
  • Чем запись в спейсе _func отличается от просто глобальной Lua-функции в [c]package.loaded[/c]? Что из них переживет рестарт сервера и почему?
  • Что именно делает опция body в [c]box.schema.func.create[/c] и где физически оказывается код функции?
  • Почему [c]box.func.sum:call({1,2})[/c] пишут с фигурными скобками, и в каком формате аргументы уходят при вызове через net.box?
  • Как горячо перезагрузить Lua-модуль без рестарта инстанса и почему обычный повторный [c]require[/c] не подхватит правки в файле?
👍3 ❤️3 🔥1 😄 🤔
Ответить
← Предыдущая глава
Транзакции: ACID, изоляция, MVCC
Следующая глава →
net.box: удалённые вызовы, async

Все главы курса «Tarantool: in-memory СУБД и сервер приложений с нуля до продакшена»

Поделиться темой: ✈ Telegram VK

Вернуться в «Tarantool: СУБД и сервер приложений»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость