Обновления: схема, rolling upgrade

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

Обновления: схема, rolling upgrade

Сообщение 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, останавливаете инстанс и стартуете его на новой версии. Сами данные (snapshot и WAL) при этом не трогаются - они читаются как есть.
  • Обновление схемы (data layout) - это апгрейд системных спейсов (_space, _index, _user, _func, _cluster и прочих служебных _*-таблиц), чтобы база начала использовать новые возможности формата. Это делается ОДНОЙ командой box.schema.upgrade() уже после того, как все инстансы перешли на новый бинарник.
Ключ к пониманию: новый бинарник умеет читать старую схему. Поэтому можно сперва спокойно прокатить все инстансы на новую версию, не меняя формат данных, и только в самом конце единым махом поднять версию схемы.
Бинарный формат, клиент-серверный протокол (iproto) и протокол репликации совместимы между 2.x и 3.x. Именно эта совместимость и делает rolling upgrade возможным: старая и новая версии в одном кластере временно сосуществуют и реплицируются друг в друга.
Механика rolling upgrade: почему без даунтайма

Rolling upgrade ("перекатывание") опирается на избыточность кластера. Пока вы гасите один инстанс, его работу подхватывает другой: для шардированного стораджа - реплика того же replica set, для роутера - любой другой роутер. Идём по инстансам по одному, и в каждый момент времени кластер обслуживает запросы.

Порядок обновления (для vshard-кластера):
  • Сначала роутеры - они stateless, у них нет своих данных, схему апгрейдить не нужно. Гасим и поднимаем по одному.
  • Потом стораджи, по одному replica set за раз. Внутри набора - сначала read-only реплики, в конце мастер.
Внутри одного replica set порядок строгий:
  • Перезапускаем на новой версии read-only реплику (по одной), ждём box.info.status == running.
  • Перезапускаем остальные read-only реплики.
  • Делаем одну из обновлённых реплик новым мастером (переключение мастера - см. ниже), чтобы старый мастер тоже можно было перезапустить.
  • Перезапускаем бывший мастер (теперь реплику) на новой версии.
  • Только теперь на новом мастере выполняем box.schema.upgrade(). Изменения системных спейсов разъезжаются на остальные узлы обычной репликацией.
Почему box.schema.upgrade() именно в конце и именно один раз? Потому что апгрейд схемы - это обычная запись в системные спейсы. Она пишется в WAL мастера и реплицируется. Если выполнить её, когда часть инстансов ещё на старом бинарнике, старая версия может не понять новые записи в _*-спейсах и упасть при применении. Поэтому правило железное: все инстансы набора уже на целевой версии бинарника, и только потом upgrade схемы.

Изображение
Rolling upgrade кластера и апгрейд схемы

Переключение мастера во время перекатывания

Чтобы перезапустить мастер, его роль надо временно отдать обновлённой реплике. Способ зависит от того, как устроен failover:

Raft (автоматические выборы лидера):

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

-- на кандидате (read-only реплике):
box.ctl.promote()                       -- запускает выборы и ждёт
-- на старом мастере:
box.cfg{ election_mode = 'voter' }      -- он больше не претендует на лидерство
-- проверка на кандидате:
box.info.ro                             -- должно стать false
Legacy (ни Cartridge, ни Raft) - переключаем вручную и СВЕРЯЕМ vclock:

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

-- на текущем мастере:
box.cfg{ read_only = true }
-- на кандидате проверяем, что он догнал мастера:
-- box.info.vclock[master_id] == box.info.lsn (взятый на мастере)
Если vclock кандидата не совпал с lsn мастера - реплика отстала, переключаться нельзя: вернуть box.cfg{ read_only = false } на мастере и взять другого кандидата. Это защита от потери последних транзакций.

Схема: upgrade и downgrade

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

box.schema.upgrade()              -- поднять версию схемы до версии бинарника
box.schema.downgrade(version)     -- откатить схему к указанной версии
box.schema.downgrade_versions()   -- список версий, на которые можно откатиться
box.schema.downgrade_issues(ver)  -- что мешает откату на ver (какие новые фичи задействованы)
Проверить текущую версию схемы:

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

tarantool> box.internal.schema_version and box.space._schema:get{'version'}
-- или просто посмотреть box.info.schema_version (в свежих 3.x)
Точка невозврата. Вызов box.schema.upgrade() - это та самая точка. До неё откат тривиален: рестартуете уже обновлённые узлы обратно на старом бинарнике, данные полностью совместимы. После неё схема несовместима со старой версией, и откат требует явного box.schema.downgrade(). Откат вниз возможен только до версий 2.8.2 и новее - это самый старый релиз, поддерживающий downgrade. Если стартовая версия старше - назад дороги нет, только восстановление из бэкапа.

Откат после точки невозврата (на каждом replica set):

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

box.schema.downgrade('2.11.0')    -- на мастере, указываем исходную версию
box.snapshot()                    -- на КАЖДОМ инстансе набора
-- далее по одному рестартуем реплики на старом бинарнике,
-- переключаем мастера, рестартуем бывший мастер
box.snapshot() здесь обязателен: он фиксирует новое (пониженное) состояние схемы в snap-файл, чтобы при рестарте инстанс не пытался восстановиться из старых WAL с записями новой схемы.

Двойной трек: box.cfg (1.x/2.x) и декларативный 3.x

Сама последовательность rolling upgrade одинакова для обоих стилей конфигурации - меняется только то, КАК вы рестартуете инстанс на новой версии.

Классический box.cfg. Вы запускаете тот же init.lua/instance-файл новым бинарником. Никаких изменений в коде конфигурации не требуется, кроме совместимости приложения.

Декларативный 3.x (config.yaml + tt). Конфигурация лежит в YAML, инстансы поднимаются через tt. Обновление бинарника - это переустановка версии и rolling-перезапуск инстансов (tt restart по одному). Схема всё так же поднимается явным box.schema.upgrade() на лидере набора - декларативная конфигурация автоматически версию схемы НЕ двигает.

Модуль compat - мост между версиями. При мажорном апгрейде часть поведения меняется. Модуль compat позволяет временно оставить старое поведение опции и переключаться на новое по одной, тестируя постепенно, а не "всё сразу":

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

tarantool> require('compat').<option_name>
-- даёт 'old' / 'new' / 'default'; можно зафиксировать:
require('compat').<option_name> = 'old'
Это развязывает обновление бинарника и адаптацию кода приложения во времени.

Частые заблуждения и грабли
  • "Обновил пакет - схема обновилась сама". Нет. box.schema.upgrade() надо вызвать руками. Без него вы на новом бинарнике, но без новых фич формата.
  • Запуск box.schema.upgrade() на каждом инстансе. Не нужно и опасно: вызываем только на мастере, остальное разъезжается репликацией.
  • Upgrade схемы, когда часть узлов ещё на старой версии. Старые узлы могут упасть на новых записях системных спейсов. Сначала весь набор на целевую версию.
  • Забыли box.snapshot() после upgrade/downgrade. Тогда после рестарта реплика восстанавливается из старых WAL и видит не то состояние. Снапшот фиксирует новое состояние схемы.
  • Прыжок 1.6/1.7/1.9 -> 2.x напрямую без даунтайма. Невозможно без промежуточного шага через 1.10.
  • Не сверили vclock при ручном переключении мастера. Можно потерять последние транзакции.
  • Не выключили rebalancer/failover перед обновлением стораджей. vshard.storage.rebalancer_disable() и отключение failover - иначе кластер начнёт двигать бакеты прямо во время перекатывания.
Мини-лаба

На локальном инстансе (standalone, любой 2.11+ или 3.x):
  • Запустите инстанс, посмотрите box.info.schema_version (или box.space._schema:get{'version'}).
  • Выполните box.schema.downgrade_versions() - изучите, до каких версий можно откатиться.
  • Выберите версию из списка и выполните box.schema.downgrade_issues('<version>') - посмотрите, какие фичи (если есть) мешают откату.
  • Сделайте box.schema.downgrade('<version>'), затем box.snapshot(), снова проверьте box.info.schema_version. Верните обратно box.schema.upgrade().
Цель: увидеть своими глазами, что версия схемы - это отдельный, явно управляемый параметр, не привязанный жёстко к версии бинарника.

Контрольные вопросы
  • Почему box.schema.upgrade() выполняется только один раз и только на мастере, а не на каждом инстансе replica set?
  • Что такое "точка невозврата" при апгрейде и как откатиться, если вы её уже прошли?
  • Зачем перед рестартом инстанса на старой версии (при downgrade) обязателен box.snapshot()?
  • В каком порядке обновляются роутеры и стораджи и почему именно так - что обеспечивает работу без даунтайма?
👍2 ❤️1 🔥 😄 🤔2
Ответить
← Предыдущая глава
Производительность: профилирование, тюнинг
Следующая глава →
Деплой в продакшен: Docker, топология (официальные паттерны)

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

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

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

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

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