Tarantool CE/EE Documentation portal logo
Помощь
Обновлена 15 сентября 2026 г. в 08:55

Обновление схемы спейса

В Tarantool под миграцией понимается любое изменение схемы данных, например, создание индекса, добавление поля или изменение формата поля. Если требуется изменить схему данных, возможны несколько вариантов:

  • Миграция схемы не требует миграции данных: добавление поля с параметром is_nullable в конец спейса, создание индекса.

  • Миграция схемы требует миграции данных. Например, это необходимо, когда нужно обойти весь спейс, чтобы преобразовать столбцы в новый формат или полностью удалить столбец.

Для решения задачи миграции данных можно:

Про обновление спейса

С помощью space:upgrade() можно обновить формат спейса и хранящиеся в нем кортежи без блокировки базы данных.

Как применить обновление спейса

Сначала задайте функцию обновления — функцию, которая преобразует кортежи в спейсе к новому формату. Требования к этой функции перечислены ниже.

  • Функция обновления принимает два аргумента. Первый аргумент –- кортеж, который нужно обновить. Второй аргумент необязательный. Он содержит дополнительную информацию, хранящуюся в виде обычного Lua-объекта. Если аргумент опущен, его значение — nil.
  • Функция возвращает новый кортеж или Lua-таблицу. Например, она может добавить новое поле в кортеж. Новый кортеж должен соответствовать новому формату спейса, заданному операцией обновления.
  • Функция должна быть зарегистрирована с помощью box.schema.func.create. Она также должна быть хранимой, детерминированной и написанной на Lua.
  • Функция не должна изменять первичный ключ кортежа.
  • Функция должна быть идемпотентной: f(f(t)) = f(t). Это необходимо, так как функция применяется ко всем кортежам, возвращаемым пользователю, и некоторые из них могли быть уже обновлены в фоновом режиме.

Затем задайте новый формат спейса. Этот шаг необязателен. Однако он может быть полезен, если, например, требуется добавить новый столбец с данными. Подробнее см. в разделе Пример использования.

Следующий необязательный шаг — выбор режима обновления. Доступны три режима: upgrade, dryrun и dryrun+upgrade. Значение по умолчанию — upgrade. Чтобы проверить функцию обновления без применения изменений, выберите режим dryrun. Чтобы запустить обновление спейса без тестирования функции, выберите режим upgrade. Если нужно выполнить и тест, и фактическое обновление, используйте режим dryrun+upgrade. Подробнее см. в разделе Режимы обновления.

Как работает обновление

Пользователь задает функцию обновления. Каждый кортеж выбранного спейса пропускается через эту функцию. Функция преобразует кортеж из старого формата в новый. Функция применяется ко всем кортежам, хранящимся в спейсе, в фоновом режиме. Кроме того, функция применяется ко всем кортежам, возвращаемым пользователю через box API (например, select, get). Таким образом, создается впечатление, что спейс обновляется мгновенно.

Учтите, что space:upgrade имеет следующие особенности по сравнению с space_object:format:

Отличие

space:upgrade()

space:format()

Неблокирующее

Да. Возвращает кортежи в новом формате, независимо от того, были ли они уже преобразованы.

Да.

Задание формата, несовместимого с текущим

Да. Работает только для неиндексированных типов полей.

Нет, только расширение формата совместимым образом.

Видимость изменений

Немедленно. Все изменения видны и реплицируются немедленно. Новые данные должны соответствовать новому формату сразу после вызова.

После проверки данных. Проверка данных запускается в фоновом режиме и не блокирует базу данных. Вставка данных, несовместимых с новым форматом, допускается до завершения проверки — в этом случае space.format завершается с ошибкой.

Отмена (ошибка/перезапуск)

Записывает состояние в системную таблицу. Перезапуск: операция продолжается. Ошибка: операцию необходимо перезапустить вручную, любая другая попытка изменить таблицу завершается с ошибкой.

Не оставляет следов.

Задание функции обновления

Да. Обновление может занять некоторое время на обход спейса и преобразование кортежей.

Нет.

Пользовательский API

Функция space:upgrade() является методом объекта спейса:

space:upgrade({func[, arg, format, mode, is_async]})

Параметры:

  • func (string/integer) — имя функции обновления (строка) или идентификатор (целое число). Подробнее см. в разделе про требования к функции обновления.

  • arg — дополнительная информация, передаваемая функции обновления во втором аргументе. Параметр принимает любое значение Lua, которое может быть закодировано в MsgPack, то есть msgpack.encode(arg) должно выполняться успешно. Например, можно передать скаляр или Lua-таблицу. Значение по умолчанию — nil.

  • format (map) — новый формат спейса. Требования к нему такие же, как и к любому другому space:format(). Если поле опущено, формат спейса останется таким же, как до обновления.

  • mode (string) — режим обновления. Возможные значения: upgrade, dryrun, dryrun+upgrade. Значение по умолчанию — upgrade.

  • is_async (boolean) — флаг, указывающий, нужно ли дожидаться завершения операции обновления перед выходом из функции. Значение по умолчанию — false — означает, что функция блокируется до завершения операции обновления.

Возвращает:

Объект, описывающий статус операции (также известный как future). Методы объекта описаны ниже.

info(dryrun, status, func, arg, owner, error, progress)

Показывает информацию о состоянии операции обновления.

Параметры:

  • dryrun (boolean) — флаг режима пробного запуска. Возможные значения: true для пробного запуска, nil для фактического обновления.

  • status (string) — статус обновления. Возможные значения: inprogress, waitrw, error, replica, done.

  • func (string/integer) — имя функции обновления. Совпадает с тем, что передано методу space:upgrade. Поле имеет значение nil, если status равен done.

  • arg — дополнительная информация, передаваемая функции обновления. Совпадает с тем, что передано методу space:upgrade. Поле имеет значение nil, если оно опущено в space:upgrade.

  • owner (string) — UUID экземпляра, на котором выполняется обновление (см. box.info.uuid). Поле имеет значение nil, если status равен done.

  • error (string) — сообщение об ошибке, если status равен error, иначе nil.

  • progress (string) — процент завершения, если status равен inprogress/waitrw, иначе nil.

Возвращает:

Таблицу с информацией о состоянии операции обновления.

Тип возвращаемого значения:

table

К полям можно также обращаться напрямую, без вызова метода info(). Например, future.status — то же самое, что future:info().status.

wait([timeout])

Ожидает завершения операции обновления или истечения тайм-аута. Операция считается завершенной, если ее статус — done или error.

Параметры:

  • timeout (double) — если аргумент timeout опущен, метод ожидает столько, сколько потребуется.

Возвращает:

Возвращает true, если операция завершена, false — при истечении тайм-аута.

Тип возвращаемого значения:

boolean

cancel()

Отменяет операцию обновления, если она выполняется. В противном случае вызывается исключение. Отмененная операция обновления завершается с ошибкой.

Возвращает:

Ничего.

Тип возвращаемого значения:

void

Вызов space:upgrade() с is_async = false развнозначен вызову без указания поля is_async:

local future = space:upgrade({func = 'my_func', is_async = true})future:wait()return future

При вызове без аргументов space:upgrade() возвращает объект future для активной операции обновления. Если активной операции нет, возвращается nil.

Режимы обновления

Доступны три режима обновления: dryrun, dryrun+upgrade и upgrade. Независимо от выбранного режима, обновление не блокирует выполнение. Периодически фоновый файбер фиксирует обновленные кортежи и отдает управление.

Вызов space:upgrade без аргументов всегда возвращает текущее состояние обновления спейса, но не состояние пробного запуска. Если в фоновом режиме выполняется пробный запуск, space:upgrade все равно вернет nil. В отличие от фактического обновления спейса, объект future, возвращаемый при пробном запуске, не может быть восстановлен при потере. Поэтому пробный запуск прерывается при сборке мусора.

Режимы обновления:

  • Режим upgrade: фоновый файбер обходит спейс, применяет функцию обновления, проверяет, что полученные кортежи соответствуют новому формату спейса, и обновляет кортежи. В этом режиме спейс изменять нельзя. Данный режим работает только на мастер-экземпляре.

  • Режим dryrun: режим пробного запуска используется для проверки функции обновления. Режим не вносит никаких изменений в целевой спейс. Он запускает фоновый файбер, который:

    • Обходит целевой спейс.
    • Пытается применить функцию обновления к каждому кортежу, хранящемуся в спейсе.
    • Проверяет, соответствует ли возвращенный кортеж новому формату.
    • Проверяет, является ли функция идемпотентной.
    • Проверяет, что функция не изменяет первичный ключ. Подробнее см. в разделе про требования к функции обновления.

    Чтобы запустить пробный запуск, передайте mode='dryrun' в метод space:upgrade. В этом случае объект future имеет поле dryrun со значением true. Возможные статусы — inprogress и dryrun. Состояния replica и waitrw никогда не устанавливаются для объекта future при пробном запуске.

    Режим dryrun не сохраняется. Перезапуск экземпляра не приводит к осуществлению пробного запуска. Пробный запуск работает только на исходном экземпляре, но не на репликах. В отличие от фактического обновления, пробный запуск не запрещает изменение спейса. Спейс можно даже удалить. В этом случае пробный запуск завершится с ошибкой.

  • Режим dryrun+upgrade: запускает пробный запуск, который при успешном завершении запускает фактическое обновление.

    Объект future, возвращаемый методом space:upgrade, остается действительным на протяжении всего процесса. Сначала он является объектом future пробного запуска. Затем, под капотом, он преобразуется в объект future обновления. Ожидание на нем означает ожидание завершения и пробного запуска, и обновления. Во время пробного запуска объект future имеет поле dryrun со значением true. При запуске фактического обновления поле dryrun принимает значение nil. Данный режим работает только на мастер-экземпляре.

Состояния

Операция обновления может находиться в одном из следующих состояний:

  • inprogress — операция обновления выполняется в фоновом режиме. Функция применяется ко всем кортежам, возвращаемым пользователю.
  • waitrw — экземпляр переключен в режим только для чтения (например, с помощью box.cfg.read_only), поэтому обновление не может продолжаться. Процесс обновления возобновится, как только экземпляр переключится обратно в режим чтения-записи. Тем не менее функция обновления применяется ко всем кортежам, возвращаемым пользователю.
  • error — операция обновления завершилась с ошибкой. Сообщение об ошибке см. в поле error. Кортеж, вызвавший ошибку, можно найти в журнале. Операции изменения не разрешены, за исключением другого обновления, которое должно исправить проблему. Тем не менее функция обновления применяется ко всем кортежам, возвращаемым пользователю. Спейс доступен для записи.
  • done — операция обновления успешно завершена. Функция обновления больше не применяется к кортежам, возвращаемым пользователю. Функцию можно удалить.
  • replica — операция обновления либо выполняется, либо завершилась с ошибкой на другом экземпляре. UUID экземпляра, на котором выполняется обновление, см. в поле owner. Тем не менее функция обновления применяется ко всем кортежам, возвращаемым пользователю.

image

Взаимодействие с операциями изменения (alter)

Пока выполняется обновление спейса, спейс нельзя изменять или удалять. Попытка сделать это вызовет исключение. Перезапуск обновления разрешен, если текущее обновление отменено или завершилось с ошибкой. Это означает, что ручной перезапуск возможен, если операция обновления находится в состоянии ошибки.

Если обновление спейса было отменено или завершилось с ошибкой, спейс нельзя изменять или удалять. Единственный вариант — перезапустить обновление с другой функцией обновления или форматом.

Взаимодействие с восстановлением

Состояние обновления спейса сохраняется. Оно хранится в системной таблице _space. Если экземпляр с выполняющимся обновлением спейса (состояние inprogress) выключен, обновление спейса перезапускается после восстановления. Если обновление спейса завершилось с ошибкой (переход в состояние error), оно остается в состоянии ошибки после восстановления.

Взаимодействие с репликацией

Изменения, внесенные в спейс при обновлении, реплицируются. Как и на экземпляре, где выполняется обновление, функция обновления применяется ко всем кортежам, возвращаемым пользователю на репликах. Однако операция обновления не выполняется на репликах в фоновом режиме. Реплики ожидают завершения операции обновления на мастере. Они не могут изменять или удалять спейс. Как правило, они также не могут отменить или перезапустить операцию обновления.

Существует аварийное исключение, когда мастер окончательно недоступен. В этом случае можно перезапустить обновление спейса, начатое на другом экземпляре. Перезапуск возможен, если UUID владельца обновления (см. поле owner) удален из системной таблицы _cluster.

Пример использования

Предположим, в спейсе test есть два столбца — id (unsigned) и data (string). В примере показано, как обновить схему и добавить еще один столбец в спейс с помощью space:upgrade(). Новый столбец содержит значения id, преобразованные в строку. Каждый шаг занимает некоторое время.

Тестовый спейс создается следующим скриптом:

local log = require('log')box.cfg{    checkpoint_count = 1,    memtx_memory = 5 * 1024 * 1024 * 1024,}box.schema.space.create('test')box.space.test:format{    {name = 'id', type = 'unsigned'},    {name = 'data', type = 'string'},}box.space.test:create_index('pk')local count = 20 * 1000 * 1000local progress = 0box.begin()for i = 1, count do    box.space.test:insert{i, 'data' .. i}    if i % 1000 == 0 then        box.commit()        local p = math.floor(i / count * 100)        if progress ~= p then            progress = p            log.info('Generating test data set... %d%% done', p)        end        box.begin()    endendbox.commit()box.snapshot()os.exit(0)

Чтобы обновить спейс, подключитесь к серверу и выполните следующие команды:

localhost:3301> box.schema.func.create('convert', {              >     language = 'lua',              >     is_deterministic = true,              >     body = [[function(t)              >         if #t == 2 then              >             return t:update({{'!', 2, tostring(t.id)}})              >         else              >             return t              >         end              >     end]],              > })localhost:3301> box.space.test:upgrade({              >     func = 'convert',              >     format = {              >         {name = 'id', type = 'unsigned'},              >         {name = 'id_string', type = 'string'},              >         {name = 'data', type = 'string'},              >     },              > })

Пока обновление выполняется, можно отслеживать его состояние. Чтобы проверить статус, подключитесь к Tarantool из другой консоли и выполните следующие команды:

localhost:3311> box.space.test:upgrade()---- status: inprogress  progress: 8%  owner: 579a9e99-427e-4e99-9e2e-216bbd3098a7  func: convert...

Несмотря на то, что обновление выполнено только на 8%, выборка данных из спейса возвращает преобразованные кортежи:

localhost:3311> box.space.test:select({}, {iterator = 'req', limit = 5})---- - [20000000, '20000000', 'data20000000']  - [19999999, '19999999', 'data19999999']  - [19999998, '19999998', 'data19999998']  - [19999997, '19999997', 'data19999997']  - [19999996, '19999996', 'data19999996']...

Дождитесь завершения обновления спейса с помощью следующей команды:

localhost:3311> box.space.test:upgrade():wait()