Ошибки и диагностика: box.error, pcall

Рейтинг: 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

Ошибки и диагностика: box.error, pcall

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

В Lua штатный механизм ошибок - это пара error() (бросить) и pcall() (поймать). Tarantool строит поверх этого свою систему: модуль box.error, объект ошибки error_object (по факту cdata, а не строка) и глобальный слот "последней ошибки" - box.error.last(). Этот урок про то, КАК ИМЕННО ошибка рождается, распространяется по стеку, ловится и диагностируется - от Lua-исключения до объекта с кодом, типом, сообщением и трейсбеком, который можно прокинуть через сеть net.box без потери смысла.

Ключевая мысль: в Tarantool ошибка - это не текст, а структура. Если относиться к ней как к строке, теряются код, тип и цепочка причин - именно на этом спотыкается большинство новичков.

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

1. Два слоя - Lua и box

Голый Lua знает только error(value) и pcall(f). Значением ошибки может быть что угодно: строка, таблица, число. Когда Tarantool бросает свою ошибку (нарушение уникальности, NO_SUCH_USER, READONLY и т.п.), значением является error_object - C-объект (cdata struct error) с подсчётом ссылок. У него есть поля code, type, message, trace, а также метаметод __tostring, поэтому в консоли он печатается как обычное сообщение, хотя внутри это структура.

2. Слот last - дополнительный канал диагностики

Параллельно с возвратом значения через pcall Tarantool сохраняет последнюю поднятую ошибку в потоко-локальном (на файбер) слоте. Достать её можно box.error.last(), очистить - box.error.clear(), а установить вручную - box.error.set(err). Это удобно, когда ошибка прошла через слой, который вернул просто nil: первопричину всё ещё видно через last(). Слот привязан к файберу, поэтому конкурентные запросы не затирают чужие ошибки.

3. Цепочки причин (error chaining)

С версии 2.4.1 у объекта есть set_prev(err) и поле prev. Это позволяет строить цепь e1 -> e2 -> e3: низкоуровневая ошибка хранится как причина высокоуровневой, и при печати/распаковке видно всю историю. В Tarantool 3.1 добавили сахар - prev прямо в конструкторе: box.error.new({type=..., message=..., prev=lower_err}). Это аналог "exception chaining" в больших языках.

4. Трейсбек собирается лениво

Сбор Lua-трейсбека стоит денег, поэтому он управляется через box.error.cfg{traceback_enable = true/false}. Когда включено, в момент, когда объект ошибки впервые показывается пользователю, в поле trace заполняется список кадров (файл, строка). По сети (net.box) ошибка кодируется как MsgPack-расширение MP_ERROR (опция msgpack.cfg.encode_error_as_ext, включена по умолчанию), но локальный Lua-трейсбек удалённой стороны НЕ переносится - вы получаете код, тип, сообщение и серверный C-trace, а не Lua-стек вызвавшего файбера.

Изображение
Поток обработки ошибок и диагностики в Tarantool

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

Поймать и разобрать ошибку

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

local ok, err = pcall(function()
    box.space.users:insert{1, 'Ann'}
    box.space.users:insert{1, 'Bob'}  -- дубль первичного ключа
end)

print(ok)            -- false
print(type(err))     -- cdata, а НЕ string
print(err.code)      -- 3  (ER_TUPLE_FOUND)
print(err.type)      -- ClientError
print(err.message)   -- Duplicate key exists ...

-- полный разбор одним вызовом:
local t = err:unpack()
-- t = {code=3, type='ClientError', message='...', trace={...}}
Создать свою ошибку и бросить

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

-- new() создаёт объект, но НЕ бросает
local e = box.error.new({ code = 1001, type = 'MyAppError',
                          reason = 'budget exceeded' })

-- бросить можно тремя способами:
box.error(e)         -- поднять готовый объект
-- или
e:raise()
-- или сразу одним вызовом без промежуточного объекта:
box.error({ reason = 'budget exceeded', type = 'MyAppError' })
Предопределённые ошибки и цепочка причин

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

-- по символическому имени (см. errcode.h):
box.error(box.error.NO_SUCH_USER, 'guest')

-- обернуть нижнюю ошибку в свою (3.1+):
local ok, low = pcall(box.space.users.insert, box.space.users, {1})
if not ok then
    box.error({ type = 'ServiceError',
                reason = 'cannot register user',
                prev = low })
end
Проверка и xpcall с трейсбеком

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

box.error.is(err)            -- 3.2+: true, если это объект ошибки

-- xpcall ловит И запускает обработчик ДО разворачивания стека:
local ok, res = xpcall(work, function(e)
    return debug.traceback(tostring(e), 2)
end)
Карта инструментов

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

команда                назначение
---------------------  -------------------------------------------
pcall(f, ...)          поймать, вернуть ok + значение/ошибку
xpcall(f, handler,...)  поймать + обработчик до сворачивания стека
box.error.new(...)     создать объект (не бросает)
box.error(...)         создать и/или бросить
err:raise()            бросить готовый объект
box.error.last()       последняя ошибка файбера
box.error.clear()      очистить слот last
box.error.set(err)     записать last вручную
err:unpack()           таблица из code/type/message/trace
box.error.is(x)        это объект ошибки? (3.2+)
box.error.cfg{...}     traceback_enable on/off
Частые заблуждения и грабли
Главная ловушка: значение из pcall - это объект (cdata), а не строка. Конкатенация ('msg: ' .. err) сработает через __tostring, но сравнения err == 'some text' или err:sub(...) сломаются. Всегда обращайтесь к err.message / err.code / err.type.
  • error('строка') теряет код и тип - получится обычная ClientError без полезной семантики. Для бизнес-ошибок используйте box.error.new с code и type.
  • xpcall: обработчик выполняется ДО разворачивания стека, поэтому именно в нём (а не после) имеет смысл звать debug.traceback. Если обработчик сам бросит ошибку - получите запутанный двойной сбой, держите его коротким.
  • Трейсбек по сети не приезжает: после net.box вызова err.trace отражает серверную C-сторону, а Lua-стек удалённого файбера недоступен. Для распределённой диагностики кладите контекст в message или в prev-цепочку.
  • box.error.last() - per-fiber. Не рассчитывайте увидеть в нём ошибку из другого файбера; и помните, что успешный вызов её не очищает - читайте last сразу после сбоя.
  • traceback_enable стоит денег: на горячем пути с массой ожидаемых ошибок его иногда выключают ради latency.
  • Транзакции: пойманная через pcall ошибка НЕ откатывает транзакцию автоматически. После box.begin() ловите ошибку и сами решайте box.commit() или box.rollback().
Мини-лаба
  • Создайте спейс users с первичным ключом. Вставьте кортеж {1, 'Ann'}, затем во втором pcall вставьте {1, 'Bob'}. Поймайте ошибку и напечатайте отдельно err.code, err.type, err.message. Затем оберните её в свою ошибку через box.error.new({type='UserDupError', reason='id taken', prev=err}) внутри pcall, поймайте уже её и пройдите по полю .prev, распечатав цепочку причин сверху вниз. Сравните вывод box.error.last() сразу после сбоя.
Контрольные вопросы
  • Что физически возвращает pcall во втором значении при ошибке Tarantool - строку или объект, и какие у него ключевые поля?
  • Чем box.error.new отличается от box.error при одинаковых аргументах?
  • Почему после net.box-вызова вы не увидите полный Lua-трейсбек удалённой стороны и где тогда искать контекст?
  • Зачем нужны set_prev/prev и чем error chaining помогает диагностике в многослойном коде?
👍3 ❤️2 🔥 😄 🤔1
Ответить
← Предыдущая глава
Пулы соединений, балансировка, реконнект
Следующая глава →
Типы и сериализация: MsgPack, decimal, datetime, uuid

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

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

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

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

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