Обновление схемы спейса
В Tarantool под миграцией понимается любое изменение схемы данных, например, создание индекса, добавление поля или изменение формата поля. Если требуется изменить схему данных, возможны несколько вариантов:
-
Миграция схемы не требует миграции данных: добавление поля с параметром
is_nullableв конец спейса, создание индекса. -
Миграция схемы требует миграции данных. Например, это необходимо, когда нужно обойти весь спейс, чтобы преобразовать столбцы в новый формат или полностью удалить столбец.
Для решения задачи миграции данных можно:
- Перенести данные в новый спейс вручную.
- Использовать функцию
space:upgrade().
С помощью 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: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).
Методы объекта описаны ниже.
Показывает информацию о состоянии операции обновления.
Параметры:
-
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.
Ожидает завершения операции обновления или истечения тайм-аута. Операция
считается завершенной, если ее статус — done или error.
Параметры:
timeout(double) — если аргументtimeoutопущен, метод ожидает столько, сколько потребуется.
Возвращает:
Возвращает true, если операция завершена, false — при истечении тайм-аута.
Тип возвращаемого значения:
boolean
Отменяет операцию обновления, если она выполняется. В противном случае вызывается исключение. Отмененная операция обновления завершается с ошибкой.
Возвращает:
Ничего.
Тип возвращаемого значения:
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. Тем не менее функция обновления применяется ко всем кортежам, возвращаемым пользователю.

Пока выполняется обновление спейса, спейс нельзя изменять или удалять. Попытка сделать это вызовет исключение. Перезапуск обновления разрешен, если текущее обновление отменено или завершилось с ошибкой. Это означает, что ручной перезапуск возможен, если операция обновления находится в состоянии ошибки.
Если обновление спейса было отменено или завершилось с ошибкой, спейс нельзя изменять или удалять. Единственный вариант — перезапустить обновление с другой функцией обновления или форматом.
Состояние обновления спейса сохраняется. Оно хранится в системной
таблице _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 dobox.space.test:insert{i, 'data' .. i}if i % 1000 == 0 thenbox.commit()local p = math.floor(i / count * 100)if progress ~= p thenprogress = plog.info('Generating test data set... %d%% done', p)endbox.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: inprogressprogress: 8%owner: 579a9e99-427e-4e99-9e2e-216bbd3098a7func: 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()