Роли и приложения в 3.x

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

Роли и приложения в 3.x

Сообщение 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 (1.x/2.x) вся бизнес-логика жила в одном init-скрипте: вы вызывали box.cfg, потом руками создавали спейсы, поднимали HTTP-сервер, регистрировали функции. Перезапуск процесса был единственным способом подхватить изменения. В 3.x появилась декларативная конфигурация (YAML/etcd), и вместе с ней - модульная архитектура для кода: роли (roles) и приложение (app). Идея проста: код тоже становится частью конфигурации. Вы перечисляете, какие куски логики включены на конкретном инстансе, а движок сам прогоняет их через единый жизненный цикл и умеет включать/выключать их на лету, без рестарта.

Роль - это Lua-модуль, реализующий конкретную функцию или логику. Роль можно включить или выключить для нужных инстансов прямо в конфигурации, и она запускается при загрузке или перезагрузке конфига - перезапуск инстанса не требуется. Роли делятся на три группы:
  • встроенные роли Tarantool (например, config.storage - превращает репликасет в централизованное хранилище конфигурации);
  • роли из сторонних официальных модулей (например, модуль crud даёт roles.crud-storage и roles.crud-router для шардированного кластера);
  • кастомные роли - часть вашего кластерного приложения (хранимая процедура, нотификатор, репликатор и т. п.).
Приложение (app) - это единая точка входа вашего сервиса, один модуль или файл, который грузится один раз после всех ролей. Разница принципиальная: ролей может быть много, у них есть формальный контракт и зависимости, а приложение - монолитный entrypoint без жизненного цикла ролей.

Механика: жизненный цикл и контракт роли

Модуль роли возвращает таблицу с заранее оговоренными полями-функциями. Минимальный контракт - три функции:

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

return {
    validate = function(cfg) --[[ проверка ]] end,
    apply    = function(cfg) --[[ применение ]] end,
    stop     = function()    --[[ остановка ]] end,
}
Опционально добавляются поле dependencies (список ролей-зависимостей) и колбэк on_event. Имя роли - это имя Lua-модуля: оно передаётся в require(), поэтому файл greeter.lua с кодом роли кладётся рядом с конфигом (либо доступен через package.path / .rocks).

Жизненный цикл роли при каждой загрузке/перезагрузке конфига проходит фазы строго в таком порядке:
  • 1. Loading (загрузка) - все роли грузятся в порядке, указанном в конфиге; выполняется код верхнего уровня модуля (инициализация). Срабатывает при включении роли или рестарте инстанса. Роль не стартует, если в конфиге нет её зависимостей.
  • 2. Stopping (остановка) - при reload вызываются stop() тех ролей, что убраны из конфига. ВСЕ stop() выполняются ДО любых validate() и apply(): сперва глушим старое, потом поднимаем новое.
  • 3. Validate (валидация) - для каждой роли вызывается validate(cfg) в порядке из конфига. Любая ошибка останавливает применение конфига целиком.
  • 4. Apply (применение) - вызывается apply(cfg) для каждой роли. apply гарантированно идёт после того, как validate отработал для ВСЕХ включённых ролей.
Изображение
Роли, их жизненный цикл и приложение в Tarantool 3.x

Все функции роли сообщают о неустранимой ошибке через throw (error). Ошибка ловится и показывается в config:info() в секции alerts. Аргумент cfg в validate/apply - это конфигурация роли из roles_cfg.<имя_роли>. Чтобы достать значения вне этой секции, используют config:get().

Зависимости и порядок

Поле dependencies перечисляет роли, без которых данная роль не стартует. Тонкость: зависимости НЕ влияют на порядок загрузки (loading), но влияют на порядок выполнения validate/apply/stop. Для дерева зависимостей сначала отрабатывают листья. Пусть role3 -> role4 -> role5 (стрелка - "зависит от"), а конфиг такой:

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

roles: [ role1, role2, role3, role4, role5 ]
Тогда validate/apply пойдут в порядке: role1 -> role2 -> role5 -> role4 -> role3. А stop при удалении ролей - в обратном порядке с учётом зависимостей.

Колбэк on_event (с 3.3.1)

on_event(config, key, value) вызывается каждый раз при широковещании системного события box.status. Аргумент key равен config.apply (триггер - обновление конфига) либо box.status (системное событие); value несёт статус инстанса, включая is_ro. Это правильное место для создания спейсов: схему можно менять только на read-write инстансе, а on_event даёт надёжный сигнал смены роли RW/RO. Все on_event с key=config.apply выполняются как часть процесса конфигурации (статусы ready/check_warnings достигаются только после них), и каждый колбэк обёрнут в pcall - ошибка логируется на уровне error, но не рушит остальные.

Код: роль, конфиг и приложение

Простая роль greeter без своего конфига и её включение:

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

-- greeter.lua
local log = require('log')
return {
    validate = function() end,
    apply = function() log.info('Hi from greeter') end,
    stop = function() log.info('greeter stopped') end,
}

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

# config.yaml
groups:
  group001:
    replicasets:
      replicaset001:
        instances:
          instance001:
            roles: [ greeter ]
Роль с конфигурацией и валидацией через встроенный экспериментальный модуль схемы:

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

local schema = require('experimental.config.utils.schema')
local greeter_schema = schema.new('greeter', schema.record({
    greeting = schema.scalar({ type = 'string' }),
}))
local function validate(cfg) greeter_schema:validate(cfg) end
local function apply(cfg) require('log').info(cfg.greeting) end
local function stop() end
return { validate = validate, apply = apply, stop = stop }

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

roles: [ greeter ]
roles_cfg:
  greeter:
    greeting: 'Hello from config'
Создание спейса через on_event (только на RW, идемпотентно):

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

on_event = function(config, key, value)
    if value.is_ro then return end
    box.schema.space.create(config.space_name or 'events',
        { if_not_exists = true })
end
Приложение как единая точка входа. В конфиге секция app:

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

app:
  file: 'myapp.lua'      # ИЛИ module: 'myapp'
  cfg:
    feature_x: true
Приложение грузится ОДИН раз, ПОСЛЕ всех ролей; его конфиг доступен внутри через config:get('app.cfg').

Частые заблуждения и грабли
  • Роль 3.x - это не роль Cartridge. Контракты разные (нет init/validate_config/apply_config из Cartridge), нет mixin'ов hot-reload Cartridge. Это новый, более простой механизм поверх декларативного конфига.
  • Не путать с другими "ролями". Есть роль доступа (контейнер привилегий для users) и sharding-роль репликасета (storage/router в vshard). Application role - третья сущность.
  • validate/apply вызываются ВСЕГДА при reload, даже если конфиг роли не менялся. Не закладывайтесь на "вызовут только при изменении" - делайте apply идемпотентным.
  • apply не вызывается при смене RO->RW, когда replication.failover = election или supervised. Если логика зависит от RW (создание спейсов), не кладите её только в apply - используйте box.watch('box.status', ...) или on_event.
  • Изменили САМ код роли (поля, зависимости) - нужен рестарт инстанса; на лету подхватывается только смена набора ролей и их roles_cfg, но не структура модуля.
  • Зависимость должна быть явно в roles. Роль с dependencies не стартует, если зависимостей нет в списке roles - их надо перечислить.
  • Создание спейса в apply без if_not_exists = true упадёт при втором reload. Всегда идемпотентность плюс проверка box.info.ro.
Правило большой кнопки: роль = переключаемый модуль с жизненным циклом и зависимостями; приложение = монолитный entrypoint, грузится один раз после ролей. Тяжёлую переключаемую логику оформляйте ролью, общий старт сервиса - приложением.
Мини-лаба

Создайте кастомную роль counter, которая на apply пишет в лог, сколько раз её применили за время жизни процесса (используйте upvalue-счётчик на уровне модуля), а на stop логирует финальное значение. Включите её в config.yaml для одного инстанса, запустите tt start, затем выполните config:reload() (или измените roles_cfg и сделайте reload) и убедитесь по логам, что счётчик растёт, а код верхнего уровня модуля (loading) выполнился лишь один раз.

Контрольные вопросы
  • 1. В каком порядке движок выполняет stop, validate и apply при перезагрузке конфига и почему все stop() идут первыми?
  • 2. Чем порядок loading отличается от порядка validate/apply для дерева зависимостей role3 -> role4 -> role5?
  • 3. Почему создание спейса нельзя надёжно делать только в apply() и какие два механизма решают проблему RW-режима?
  • 4. Чем приложение (секция app) отличается от роли по моменту загрузки и наличию жизненного цикла?
👍1 ❤️1 🔥1 😄 🤔1
Ответить
← Предыдущая глава
Декларативная конфигурация 3.x: config.yaml, иерархия
Следующая глава →
Централизованная конфигурация: etcd / config storage

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

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

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

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

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