net.box: удалённые вызовы, async

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

net.box: удалённые вызовы, async

Сообщение 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: шардированный отказоустойчивый кластер
Обзор: зачем нужен net.box

Tarantool - это и база данных, и сервер приложений, который слушает порт по бинарному протоколу IPROTO. Модуль net.box - это встроенный Lua-клиент к этому порту. Он позволяет из одного инстанса Tarantool (или из tt run -i, из теста, из соседнего сервиса) подключиться к другому и выполнять там запросы так, будто они локальные: select/insert/update по спейсам, вызов хранимых функций через call, выполнение произвольного Lua через eval.

Главная идея урока: net.box - это не просто обёртка над сокетом, а полноценная асинхронная машина с воркер-фибером, конвейером запросов (pipelining) и объектами-фьючерсами для неблокирующей работы. Понимание этой механики отделяет рабочий код от кода, который тормозит или ловит гонки.

Механика: что происходит внутри

Подключение ленивое. Вызов net_box.connect(URI) (синоним net_box.new) сразу возвращает объект conn, но физическое соединение устанавливается на первом запросе либо в фоне. Под капотом connect порождает отдельный воркер-фибер, который ведёт конечный автомат соединения: initial -> auth -> fetch_schema -> active. На этапе fetch_schema клиент скачивает с сервера описание спейсов и индексов - именно поэтому после connect доступны conn.space.bands и conn.space.bands.index.

Один сокет, много фиберов. Все методы conn фибер-безопасны. Если десять фиберов делают запросы через один conn, все они мультиплексируются в один TCP-сокет: net.box присваивает каждому запросу уникальный sync-id (IPROTO_SYNC), отправляет их подряд не дожидаясь ответов (pipelining), а приходящие ответы раскладывает обратно по sync-id нужному фиберу. Меньше сокетов - меньше системных вызовов - выше throughput. Поэтому правильная практика: один общий conn на много фиберов, а не conn на каждый запрос.

call против eval - это разные IPROTO-команды. conn:call('func', {args}) отправляет IPROTO_CALL и вызывает заранее зарегистрированную глобальную функцию по имени. conn:eval('return ...', {args}) отправляет IPROTO_EVAL и выполняет произвольную строку Lua на сервере. Различие принципиально для безопасности: право execute можно выдать точечно на конкретную функцию (lua_call), а eval требует execute на lua_eval (фактически на universe) - это почти root. В проде наружу отдают call на белый список функций, eval оставляют для отладки.

Синхронность и yield. Любой удалённый запрос по сети делает yield фибера: он отдаёт управление, пока ждёт ответа. Это значит, что глобальные переменные и данные в базе могут измениться, пока ваш select висит в полёте - в отличие от локального box.space, который при чтении не уступает управление. Особый случай - net_box.self: встроенное "соединение" к самому себе, где запросы не уходят в сеть и опции is_async/timeout/on_push игнорируются.

Изображение
Клиент-сервер net.box: call, eval, future

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

Сервер (sample_db, декларативный конфиг 3.x слушает 127.0.0.1:3301, у пользователя есть права на спейс bands и на функцию get_bands_older_than). Клиент:

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

local net_box = require('net.box')

-- ленивое подключение, ждём не дольше 1.5 c
local conn = net_box.connect('sampleuser:secret@127.0.0.1:3301',
                             { wait_connected = 1.5 })
print(conn:ping())            -- true, если живо

-- работа со спейсом как с локальным
conn.space.bands:insert({1, 'Scorpions', 1965})
conn.space.bands:select({1})

-- call: вызов зарегистрированной функции
conn:call('get_bands_older_than', {1970})

-- eval: произвольный Lua (нужно право execute на lua_eval)
conn:eval('return box.info.version')

conn:close()                  -- close() - системный вызов, закрываем явно
Асинхронность через future. Опция is_async=true применима к любому запросу (call, eval, select, insert...). Запрос всё равно делает yield, но не ждёт ответа: вместо результата сразу возвращается объект future. У него четыре метода:

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

local f = conn.space.bands:insert({10, 'Queen', 1970}, {is_async = true})
f:is_ready()              -- true, когда ответ пришёл
f:result()                -- результат (или nil, если не готов/ошибка)
f:wait_result(0.5)        -- блокирующе ждать до 0.5 c, иначе ошибка
f:discard()               -- забыть про ответ, освободить слот в таблице запросов
Map-reduce: разослать N запросов, потом собрать. Главный сценарий is_async - параллельные запросы к нескольким источникам:

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

local futures = {}
for idx, conn in ipairs(shards) do
    futures[idx] = conn:call('count_rows', {}, {is_async = true})
end
local total = 0
for _, f in ipairs(futures) do
    total = total + f:wait_result(1.0)[1]   -- ответ async - таблица!
end
Важно: результат async-запроса структурирован иначе, чем синхронного. Синхронный conn:call возвращает распакованные значения, а future:result() всегда возвращает их таблицей (как [val1, val2, ...]). Это частая причина "почему у меня nil вместо числа".
Полезные опции connect. reconnect_after=N - прозрачно переподключаться каждые N секунд при обрыве (но box.session.id() после реконнекта меняется - нельзя полагаться на серверную сессию между обрывами). fetch_schema=false - не качать схему: ускоряет старт, если делаете только call/eval и спейсы не нужны (тогда conn.space недоступен). connect_timeout - таймаут установления соединения.

Частые заблуждения и грабли
  • "net.box нужен для связи с внешней СУБД." Нет, это вариант для Tarantool-Tarantool. Для MySQL/PostgreSQL - отдельные коннекторы.
  • Соединение на каждый запрос. connect/close - дорого (системные вызовы, auth, fetch_schema). Держите долгоживущий conn и шарьте между фиберами.
  • eval наружу. Выдать execute на lua_eval - это открыть выполнение любого кода. Наружу - только call на конкретные функции.
  • Забыть, что удалённый select делает yield. Логика "прочитал и тут же обновил" по сети не атомарна; данные могли уехать.
  • Async без таймаута и без discard. Брошенные future копятся в таблице запросов и тормозят остальные; discard освобождает слот.
  • timeout=nil в горячем цикле. Внутри без явного timeout net.box зовёт fiber.self() (C-call), что мешает JIT и просаживает скорость call примерно на 15%. В нагруженном пути указывайте timeout явно.
  • is_async ради "ускорения" одиночного запроса. Профита нет - выгода только при пачке параллельных запросов (map-reduce) или очень высокой латентности.
Мини-лаба

Поднимите локальный инстанс на 127.0.0.1:3301 с функцией

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

function add(a, b) return a + b end
(через box.schema.func.create/grant execute). Из tt run -i подключитесь net.box и сравните три способа сложить 2 и 3: (1) синхронно conn:call('add', {2, 3}); (2) асинхронно с future = conn:call('add', {2, 3}, {is_async=true}) и future:wait_result(1); (3) conn:eval('return 2 + 3'). Запишите, чем отличается тип/форма возвращаемого значения у async-варианта от синхронного.

Контрольные вопросы
  • Чем отличаются IPROTO_CALL (conn:call) и IPROTO_EVAL (conn:eval) с точки зрения механики и безопасности (какие права нужны)?
  • Почему один conn можно безопасно использовать из многих фиберов и что при этом происходит с сетевым сокетом?
  • Что именно возвращает запрос с is_async=true и какими четырьмя методами future из него достают результат?
  • Чем форма результата future:result() отличается от результата синхронного conn:call и почему это ловушка?
👍6 ❤️5 🔥 😄 🤔
Ответить
← Предыдущая глава
Хранимые процедуры, модули, организация приложения
Следующая глава →
Пулы соединений, балансировка, реконнект

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

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

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

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

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