Большие данные, большие вызовы: Контролируемая конвертация БД Directum RX на версию 4.7

6 0

Бывают такие проекты, которые ведутся на протяжение многих лет. Клиенту внедрена система Directum RX, активно развиваются модули и интеграции, внедрен долговременный архив. База растет быстро: миграция, сотни миллионов записей, терабайты данных, множество индексов. Наступает время, когда обновление системы становится неизбежным: клиенты стремятся использовать возможности новых релизов, к тому же поддержка старых версий прекращается.

С какой задачей мы столкнулись

У клиента развернута система Directum RX версии 4.2, внедрено решение долговременного архива версии 0.6. Настал момент и накопившийся объем новой функциональности и усовершенствований текущей платформы перевесил сложности на пути к обновлению. Поставлена задача по обновлению Directum RX на версию 4.11 и Долговременного архива на версию 2.1.

Закатываем рукава и приступаем к работе! Вендор предоставляет документацию по обновлению системы к каждой версии, все процессы в ней описаны достаточно подробно. В статье мы не говорим о решении долговременного архива, документации по обновлению решения с версии 0.6 нет. Как мы с этим справились - отдельная история, которая заслуживает еще одной публикации.
Обновление системы производилось поэтапно в следующем порядке: 4.2 – 4.4 – 4.7 – 4.9 – 4.11.

В этой статье рассмотрим процесс обновления DRX с версии 4.4 на 4.7, а конкретнее, этап конвертации базы данных. На проекте используется Microsoft SQL Server.

Почему возникли сложности именно в этом этапе

Как все уже знают, в версии Directum RX 4.7 идентификаторы объектов системы переведены с типа int на long. Вендор предупреждает, что на момент обновления необходимо обеспечить запас свободного места на диске, равный размеру существующей базы данных. Также в документации отмечено: “В зависимости от размера базы данных этап конвертации может занять длительное время. Например, для базы данных размером 30 ГБ, содержащей 10 млн документов, может потребоваться до 2,5 ч, а для базы размером 131 ГБ со 100 млн документов – около 10 ч”.

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

Представляя, сколько бы занял процесс конвертации такой БД, согласно примерам подсчета от вендора, становится страшно. На этот процесс по расчетам можно смело выделять минимум 5 дней, а с учетом предупреждения вендора: “Если ошибка возникла на этапе конвертации базы данных, то сначала восстановите БД из резервной копии. Иначе при повторной попытке обновления база данных станет нерабочей.”, продолжительность увеличивается до неопределенных значений (закладываем риски).
А ведь это всего лишь один из этапов полного процесса обновления системы. На системе Directum RX у клиента завязаны многие критически важные для работы процессы, поэтому временной промежуток, на который может быть остановлена система для выполнения обновления, ограничен и должен быть как можно меньше.

Как сократить время конвертации базы данных

Для начала разберемся, как вообще происходит этап конвертации БД.

В папке с DirectumLauncher по пути \etc\_builds\Platform\db\mssql\convert можно найти все скрипты, которые выполняются на этапе конвертации БД. (скрипты для postgreSQL в отдельной папке \etc\_builds\Platform\db\postgres\convert)

По названию скриптов несложно догадаться, при конвертации на какую версию они используются. Данные скрипты при обновлении выполняются по очереди, начиная со следующей от текущей версии. В нашем случае, при шаге обновления с версии 4.4 на 4.7, выполняются скрипты с 4.5.0.0008.sql по 4.7.0.0058.sql. Самым долгим по времени выполнения является скрипт 4.7.0.0001.sql. Именно в этом скрипте выполняется процедура перевода идентификаторов всех типов объектов с типа int на long, в БД поля конвертируются с int на bigint.

Если посмотреть этот скрипт, можно понять, какие действия он выполняет:

  • восстановление индексов для таблиц *_SplitDisplayValue (Sungero_Content_EDoc_SplitDisplayValue, Sungero_WF_Assignment_SplitDisplayValue, Sungero_WF_Task_SplitDisplayValue), необходимых для ускорения конвертации по этим таблицам;
  • конвертация свойства Size всех бинарных данных в bigint;
  • отбор всех полей для конвертации с int на bigint;
  • добавления копии полей (по отобранным полям) и перенос в них оригинальных значений;
  • удаление FK, индексов, PK и UK;
  • удаление оригинальных полей и переименование копий;
  • пересоздание PK и UK, индексов, FK;
  • пересоздание хранимых процедур и представлений;

Сложности, риски и как их преодолеть

Опасность возникновения ошибок

Все перечисленные действия выполняются последовательно, многие из них занимают продолжительное время даже на менее объемных базах данных. При возникновении ошибки на любом из этапов выполнения данного скрипта весь процесс конвертации придется запускать с самого начала после восстановления БД из файла бэкапа. А возможностей для возникновения ошибок здесь предостаточно: могут возникнуть проблемы при удалении индексов, если не была удалена статистика, ошибки при удалении полей таблиц, процесс может зависнуть на копировании оригинальных значений полей в копии по наиболее заполненным таблицам, за такое продолжительное время могут возникнуть проблемы с подключением к серверу sql.

Сложность в отслеживании прогресса выполнения

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

Для решения указанных выше проблем предлагаем выполнить этот скрипт вручную и поэтапно, а скрипт в папке отредактировать, оставить лишь строку exec [dbo].[Sungero_System_SetVersion] '4.7.0.0001' (как пример).

Замечание: поэтапное выполнение скрипта 4.7.0.0001.sql запускать после выполнения всех необходимых предыдущих скриптов (для версии 4.6 и ниже). Если обновление происходит с версии ниже 4.6, необходимо отредактировать после выполнения эти необходимые предыдущие скрипты.

 

Наш опыт поэтапного выполнения

1) Выполнили подготовительные действия.

Это первая часть скрипта до строки с комментарием “Добавляем копии полей и переносим в них оригинальные значения.”
Но вместо использования временных таблиц #indexesInfoTable, #pkUqCreationScriptsTable, #fkCreationScriptsTable, #sungeroConvertCommands создаем локальные таблицы без # в имени.

Эти таблицы понадобятся для выполнения оставшейся части скрипта:

  • indexesInfoTable – таблица с командами для пересоздания индексов
  • pkUqCreationScriptsTable – таблица с командами для пересоздания PK, UQ
  • fkCreationScriptsTable – таблица с командами для пересоздания FK
  • sungeroConvertCommands - таблица с командами для создания копии полей с типом bigint для полей-идентификаторов

2) Дальше запустили добавление копий полей и перенос в них оригинальных значений.

Для этого надо выполнить запросы addColumnsCopyCommand (создание полей-копий), copyValuesCommand (копирование значений из полей-оригиналов в копии), addNotNullOnCopyCommand (установка ограничений на поля, запрет значений NULL) из таблицы sungeroConvertCommands.

Пример запросов по таблице Sungero_Core_Storage:

addColumnsCopyCommand

ALTER TABLE [Sungero_Core_Storage] ADD [Id_Copy] bigint null;
ALTER TABLE [Sungero_Core_Storage] ADD [SecureObject_Copy] bigint null;

copyValuesCommand

declare @maxId int;
declare @minId int;
declare @currentFrom int;
declare @batchSize int;
set @batchSize = 1000000;
select @maxId = max(Id) from [Sungero_Core_Storage] with (nolock);
select @minId = min(Id) from [Sungero_Core_Storage] with (nolock);
set @currentFrom = @minId;
while @currentFrom <= @maxId
begin
  update [Sungero_Core_Storage] with (tablock) set 
[Id_Copy] = [Id],[SecureObject_Copy] = [SecureObject]
  where Id between @currentFrom and @currentFrom + @batchSize;
  set @currentFrom = @currentFrom + @batchSize + 1;
end;

addNotNullOnCopyCommand

ALTER TABLE [Sungero_Core_Storage] ALTER COLUMN [Id_Copy] bigint not null;

Запрос copyValuesCommand выглядит подобным образом практически для всех таблиц, за исключением таблиц *_SplitDisplayValue

Медленная работа на таблицах с большим числом записей

Данный способ копирования может медленно работать на таблицах с большим числом записей. Мы определили число записей в каждой таблице БД. По каждой таблице с большим числом записей (пороговое кол-во записей стоит определять индивидуально по каждой БД) выполнение вышеуказанных скриптов выполняли отдельно, addColumnsCopyCommand и addNotNullOnCopyCommand без изменений, а copyValuesCommand с корректировками.
Произвели анализ плана выполнения запроса copyValuesCommand на небольшой части данных, не по всей таблице. При неудовлетворительной скорости обновления таблицы, производили оптимизацию по одному из вариантов: использование транзакций с коммитами с разным значением @batchSize, добавление индексов, параллельное выполнение с разделением по диапазонам, выполнение запроса через временную таблицу, через запрос SELECT INTO. Выбор способа подбирали индивидуально по каждой таблице. После подготовки запросов для каждой таблицы на препрод среде у нас уже был план действий на боевой базе.

После переноса значений по всем выбранным таблицам, произвели по ним обновление запросов addColumnsCopyCommand, copyValuesCommand и addNotNullOnCopyCommand в таблице sungeroConvertCommands. Запросы заменили на ‘SELECT 1’, для того, чтобы при выполнении оставшихся запросов из таблицы sungeroConvertCommands не произошло повторное выполнение.

Запустили выполнение оставшихся запросов addColumnsCopyCommand, copyValuesCommand и addNotNullOnCopyCommand.

declare @convertedTableName varchar(250)
declare @addColumnsCopyCommand nvarchar(max)
declare @copyValuesCommand nvarchar(max)
declare @addNotNullOnCopyCommand nvarchar(max)
declare @dropOriginalColumnsCommand nvarchar(max)
declare @renameCopyColumnsCommand nvarchar(max)

-- Добавляем копии полей и переносим в них оригинальные значения.
declare ConvertToLongNavColumnsCursor cursor for
select addColumnsCopyCommand, copyValuesCommand, addNotNullOnCopyCommand, dropOriginalColumnsCommand, renameCopyColumnsCommand
from sungeroConvertCommands order by tableName
open ConvertToLongNavColumnsCursor
fetch next from ConvertToLongNavColumnsCursor into @addColumnsCopyCommand, @copyValuesCommand, @addNotNullOnCopyCommand, @dropOriginalColumnsCommand, @renameCopyColumnsCommand
while @@fetch_status = 0
begin
    print 'EXECUTE COMMAND: ' + @addColumnsCopyCommand
    exec sp_executesql @addColumnsCopyCommand;
    print 'EXECUTE COMMAND: ' + @copyValuesCommand
    exec sp_executesql @copyValuesCommand;
    if (@addNotNullOnCopyCommand != '' and @addNotNullOnCopyCommand is not null)
    begin
      print 'EXECUTE COMMAND: ' + @addNotNullOnCopyCommand
      exec sp_executesql @addNotNullOnCopyCommand;
    end
    fetch next from ConvertToLongNavColumnsCursor into @addColumnsCopyCommand, @copyValuesCommand, @addNotNullOnCopyCommand, @dropOriginalColumnsCommand, @renameCopyColumnsCommand
end
close ConvertToLongNavColumnsCursor
deallocate ConvertToLongNavColumnsCursor

 

3) Произвели удаление FK, индексов, PK и UK. Здесь выполнение без изменений.

4) Выполнили удаление оригинальных полей и переименовали копии.

По этому пункту в скрипте от вендора удаление и переименование производится в одном цикле. Копии полей добавляются с припиской _Copy (Пример: Id_Copy), при переименовании часть _Copy удаляется из наименования поля. В случае возникновения ошибки при удалении какого-либо поля повторный запуск цикла приведет к потере данных, так как будут удаляться уже переименованные поля. Поэтому эти две операции мы вынесли в разные циклы.

Сначала выполнили удаление оригинальных полей:
 

declare @convertedTableName varchar(250)
declare @addColumnsCopyCommand nvarchar(max)
declare @copyValuesCommand nvarchar(max)
declare @addNotNullOnCopyCommand nvarchar(max)
declare @dropOriginalColumnsCommand nvarchar(max)
declare @renameCopyColumnsCommand nvarchar(max)

declare EndConvertToLongNavColumnsCursor cursor for
select addColumnsCopyCommand, copyValuesCommand, addNotNullOnCopyCommand, dropOriginalColumnsCommand, renameCopyColumnsCommand
from sungeroConvertCommands order by tableName
open EndConvertToLongNavColumnsCursor
fetch next from EndConvertToLongNavColumnsCursor into @addColumnsCopyCommand, @copyValuesCommand, @addNotNullOnCopyCommand, @dropOriginalColumnsCommand, @renameCopyColumnsCommand
while @@fetch_status = 0
begin
    print 'EXECUTE COMMAND: ' + @dropOriginalColumnsCommand
    exec sp_executesql @dropOriginalColumnsCommand;
    fetch next from EndConvertToLongNavColumnsCursor into @addColumnsCopyCommand, @copyValuesCommand, @addNotNullOnCopyCommand, @dropOriginalColumnsCommand, @renameCopyColumnsCommand
end
close EndConvertToLongNavColumnsCursor
deallocate EndConvertToLongNavColumnsCursor

Следующим этапом – переименование полей-копий:

declare @convertedTableName varchar(250)
declare @addColumnsCopyCommand nvarchar(max)
declare @copyValuesCommand nvarchar(max)
declare @addNotNullOnCopyCommand nvarchar(max)
declare @dropOriginalColumnsCommand nvarchar(max)
declare @renameCopyColumnsCommand nvarchar(max)

declare EndConvertToLongNavColumnsCursor cursor for
select addColumnsCopyCommand, copyValuesCommand, addNotNullOnCopyCommand, dropOriginalColumnsCommand, renameCopyColumnsCommand
from sungeroConvertCommands order by tableName
open EndConvertToLongNavColumnsCursor
fetch next from EndConvertToLongNavColumnsCursor into @addColumnsCopyCommand, @copyValuesCommand, @addNotNullOnCopyCommand, @dropOriginalColumnsCommand, @renameCopyColumnsCommand
while @@fetch_status = 0
begin
    print 'EXECUTE COMMAND: ' + @renameCopyColumnsCommand
    exec sp_executesql @renameCopyColumnsCommand;
    fetch next from EndConvertToLongNavColumnsCursor into @addColumnsCopyCommand, @copyValuesCommand, @addNotNullOnCopyCommand, @dropOriginalColumnsCommand, @renameCopyColumnsCommand
end
close EndConvertToLongNavColumnsCursor
deallocate EndConvertToLongNavColumnsCursor

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

6) Завершающий этап, бэкап и собственно обновление

После завершения выполнения скрипта 4.7.0.0001.sql выполнили создание бэкапа БД. Обновили скрипт 4.7.0.0001.sql в папке скриптов конвертаций. После чего запустили стандартный процесс обновления DRX.

 

Какие результаты мы получили

  • Самый длительный этап конвертации БД перестал быть «неизмеримым» по времени: появилась возможность мониторинга на каждом подшаге.
  • Ошибки на этапе конвертации БД при переходе на версию 4.7 стали менее критичны: при ошибках мы могли повторно запустить скрипты с минимальным откатом благодаря частичной поэтапной схеме и локальным копиям полей.
  • Общее время обновления DRX с версии 4.2 на 4.11 совместно с решением ДА сократилось до примерно 4 дней (включая подготовку, конвертацию и тестовый прогон) — это значительный прогресс по отношению к «миру без контроля».

Заключение

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

Итог преобразований: сложный процесс стал прозрачным и управляемым. А это, в конечном счете, - самый прямой путь к стабильному результату без неприятных сюрпризов!

Пока комментариев нет.

Авторизуйтесь, чтобы написать комментарий