Типы и сериализация: MsgPack, decimal, datetime, uuid

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

Типы и сериализация: MsgPack, decimal, datetime, uuid

Сообщение 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 - это не "Lua с диском". Внутри ядра (то, что мы зовём box - движок хранения, WAL, индексы, репликация, сетевой протокол iproto) данные живут не как Lua-таблицы, а как MsgPack - компактный бинарный формат. Каждый кортеж (tuple) в спейсе физически хранится как MsgPack-массив байт. Lua же оперирует своими типами: number (double), string, table, cdata, userdata. Значит на каждой границе Lua<->box происходит перевод: при insert/replace Lua-значения кодируются в MsgPack, при select/get байты декодируются обратно. Понимание этого перевода - ключ к тому, почему 0.1 ведёт себя не как decimal, почему uuid это cdata, и почему строка и varbinary кодируются по-разному.
Главная мысль урока: box не "понимает" Lua-типы напрямую. Он понимает MsgPack. Все типы Tarantool - это либо базовые типы MsgPack (int, float, str, bin, array, map, bool, nil), либо расширения MP_EXT.
Механика: базовый MsgPack плюс расширения

MsgPack кодирует скаляр одним-двумя ведущими байтами (тип и размер), дальше идут данные. Примеры из спецификации: число 127 это один байт 7f (positive fixint), 65535 это cd ff ff (uint16), false это c2, nil это c0, строка 'a' это a1 61 (fixstr длины 1 плюс байт 'a'), массив {} это 90, дробь 1.5 это float64 cb 3f f8 ... . Tarantool выбирает самое короткое представление автоматически - целые ужимаются до fixint, дроби идут как float64.

Но decimal, uuid, datetime, interval, error - этого в базовом MsgPack нет. Tarantool вводит их через механизм MP_EXT (extension): заголовок ext несёт байт-тип расширения и длину, дальше упакованная полезная нагрузка. Коды расширений фиксированы в ядре:

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

тип расширения   код   что хранит
MP_DECIMAL        1    BCD-упакованное точное число
MP_UUID           2    16 байт RFC 4122 v4
MP_ERROR          3    объект ошибки box.error
MP_DATETIME       4    epoch-секунды + nsec + tzoffset/tzindex
MP_COMPRESSION    5    сжатый блок
MP_INTERVAL       6    map полей (year..nsec) + adjust
Именно поэтому одни и те же decimal/uuid/datetime одинаково понимают и движок индексов, и WAL, и репликация, и любой коннектор (Go, Python, Java): код расширения - часть протокола, а не Lua-специфика.

Разделение строка/бинарь тоже идёт здесь. Lua-string кодируется как MsgPack str (MP_STR), а varbinary (с версии 3.0) - как MP_BIN. Это разные типы на уровне байт: '\xFF\xFE' строкой даёт a2 ff fe, а varbinary.new('\xFF\xFE') даёт c4 02 ff fe (bin8, длина 2). С 3.0 декодер по умолчанию разворачивает MP_BIN обратно в varbinary-объект, а не в строку (старое поведение возвращается опцией compat binary_data_decoding).

Изображение
Перевод типов на границе Lua и box через MsgPack

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

Сырой MsgPack руками. Модуль msgpack даёт прямой доступ к кодеку - это лучший способ "потрогать" границу.

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

local msgpack = require('msgpack')
local s = msgpack.encode({'a', 1, true})   -- Lua -> сырой MsgPack
print(string.hex(s))                       -- 93 a1 61 01 c3
local t, next_pos = msgpack.decode(s)      -- обратно в Lua-таблицу
-- t = {'a', 1, true}; next_pos = #s + 1
Управление формой таблицы - через метатег __serialize: одна и та же {'A','B'} как массив даёт 92 a1 41 a1 42, как map - 82 01 a1 41 02 a1 42.

Decimal - точные числа. Lua number это double (15-16 значащих цифр), поэтому деньги и счётчики держим в decimal.

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

local decimal = require('decimal')
local x = decimal.new('0.1') + decimal.new('0.2')  -- ровно 0.3
print(decimal.new('0.16666666666667') * 6)         -- 1.00000000000002
print(decimal.scale(decimal.new('123.4560')))      -- 4 (знаков после точки)
Точность: до Tarantool 3.5 это 38 значащих цифр, с 3.5 - 76. Конструировать лучше из строки, иначе число сначала пройдёт через double и потеряет точность ещё до упаковки.

UUID.

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

local uuid = require('uuid')
local u = uuid.new()        -- cdata, тип uuid
print(u:str())              -- 36-символьный текст
print(#u:bin())             -- 16 (бинарь для MP_UUID)
print(uuid.is_uuid(u))      -- true
Datetime и interval.

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

local datetime = require('datetime')
local d = datetime.new{year=2026, month=6, day=13, tz='Europe/Moscow'}
local iv = datetime.interval.new{month = 1}
d:add(iv)                   -- сдвиг на месяц с учётом длины месяца
print((d - datetime.now()):totable().sec)  -- разница как interval
В спейсе. Чтобы box хранил и индексировал именно нужный тип, объявляем его в формате:

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

box.schema.space.create('t')
box.space.t:format({{name='id', type='uuid'},
                    {name='price', type='decimal'},
                    {name='ts', type='datetime'}})
box.space.t:create_index('pk', {parts={{1, 'uuid'}}})
box.space.t:insert{uuid.new(), decimal.new('19.99'), datetime.now()}
Частые заблуждения и грабли
  • Lua-число != decimal. Если вставить 19.99 как обычный number, в кортеж уедет float64 со всеми артефактами double. Тип поля decimal в format не "чинит" это магически - подавать надо именно decimal.new(...).
  • Переполнение точности молча режется. Исторически Tarantool не всегда даёт ошибку при кодировании числа с точностью выше предела - лишние цифры могут округлиться. Не полагайтесь на decimal как на bignum без проверок scale/precision.
  • Строка против varbinary. До 3.0 бинарные данные приходили строкой, с 3.0 MP_BIN декодируется в varbinary-объект. Код, который ждал string и делал с ней string-операции, может сломаться после апгрейда - смотрите compat binary_data_decoding.
  • tz перебивает tzoffset. В datetime.new, если заданы и tz, и tzoffset, побеждает tz; tzoffset игнорируется. tzoffset хранится в минутах (диапазон -720..840), поэтому на древних датах возможна потеря точности зоны.
  • Арифметика месяцев неоднозначна. Прибавление interval с month/year зависит от поля adjust (по умолчанию усечение к концу месяца): 31 января + 1 месяц это не "1 марта".
  • NaN/Inf. По умолчанию они кодируются, но msgpack.cfg{encode_invalid_numbers=false} это запретит - удобно ловить мусор на входе.
  • uuid и datetime это cdata. Сравнивать их через == корректно (метаметоды есть), но не пытайтесь сериализовать произвольное cdata/userdata - получите ошибку unsupported Lua type, если не включён encode_use_tostring.
Мини-лаба

Запустите tarantool (интерактивно). Возьмите msgpack и в одну строку сравните кодирование строки и varbinary одних и тех же байт: посчитайте и распечатайте в hex msgpack.encode('\xFF\xFE') и msgpack.encode(require('varbinary').new('\xFF\xFE')). Убедитесь, что ведущий байт отличается (a2 против c4 02). Затем создайте decimal.new('0.1') и обычный 0.1, сложите каждый сам с собой трижды и сравните tostring результата. Объясните себе, почему decimal даёт ровно 0.3, а double - нет.

Контрольные вопросы
  • Почему decimal, uuid и datetime кодируются как MP_EXT, а не как базовые типы MsgPack, и что это даёт коннекторам на других языках?
  • Чем на уровне ведущих байт MsgPack отличается Lua-строка от varbinary-объекта, и что изменилось при декодировании в Tarantool 3.0?
  • Что произойдёт с точностью, если создать decimal через decimal.new(0.1) (число), а не decimal.new('0.1') (строка), и почему?
  • В datetime.new заданы одновременно tz='Europe/Moscow' и tzoffset=60. Какое значение зоны окажется в объекте и почему?
👍4 ❤️1 🔥1 😄 🤔1
Ответить
← Предыдущая глава
Ошибки и диагностика: box.error, pcall
Следующая глава →
SQL в Tarantool: возможности и связь с box

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

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

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

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

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