Бывают такие проекты, которые ведутся на протяжение многих лет. Клиенту внедрена система 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.
Если посмотреть этот скрипт, можно понять, какие действия он выполняет:
Все перечисленные действия выполняются последовательно, многие из них занимают продолжительное время даже на менее объемных базах данных. При возникновении ошибки на любом из этапов выполнения данного скрипта весь процесс конвертации придется запускать с самого начала после восстановления БД из файла бэкапа. А возможностей для возникновения ошибок здесь предостаточно: могут возникнуть проблемы при удалении индексов, если не была удалена статистика, ошибки при удалении полей таблиц, процесс может зависнуть на копировании оригинальных значений полей в копии по наиболее заполненным таблицам, за такое продолжительное время могут возникнуть проблемы с подключением к серверу sql.
Помимо опасности возникновения ошибок, есть ещё одна проблема – сложность в отслеживании прогресса выполнения, логов конвертации не всегда хватает для понимания, на каком этапе находится скрипт. Поэтому сложно оценить, когда выполнение скрипта завершится.
Для решения указанных выше проблем предлагаем выполнить этот скрипт вручную и поэтапно, а скрипт в папке отредактировать, оставить лишь строку exec [dbo].[Sungero_System_SetVersion] '4.7.0.0001' (как пример).
Замечание: поэтапное выполнение скрипта 4.7.0.0001.sql запускать после выполнения всех необходимых предыдущих скриптов (для версии 4.6 и ниже). Если обновление происходит с версии ниже 4.6, необходимо отредактировать после выполнения эти необходимые предыдущие скрипты.
Это первая часть скрипта до строки с комментарием “Добавляем копии полей и переносим в них оригинальные значения.”
Но вместо использования временных таблиц #indexesInfoTable, #pkUqCreationScriptsTable, #fkCreationScriptsTable, #sungeroConvertCommands создаем локальные таблицы без # в имени.
Эти таблицы понадобятся для выполнения оставшейся части скрипта:
Для этого надо выполнить запросы 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
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
Временные рамки для процесса обновления на данном проекте побудили нас глубже погрузиться в механизм конвертации базы данных. Стало понятно, что перевод полей из int в bigint можно значительно ускорить, используя индивидуальные скрипты копирования для больших таблиц. Разработанный подход по поэтапному выполнению скриптов конвертации можно использовать и на других проектах.
Итог преобразований: сложный процесс стал прозрачным и управляемым. А это, в конечном счете, - самый прямой путь к стабильному результату без неприятных сюрпризов!
Авторизуйтесь, чтобы написать комментарий