В этой статье опишу опыт миграции данных на одном из проектов импортозамещения и подводные камни, найденные на этом пути. Источником данных был MS Dynamics, а миграция выполнялась с помощью стандартного для ELMA365 механизма импорта данных.
Стартовые условия:
- система редакции Enterprise, 7 изолированных решений, 5 кастомных интеграций;
- кастомное решение с использованием элементов системных решений CRM и Проекты, большая часть которого разработана под требования заказчика;
- около 50 приложений, данные которых нужно мигрировать;
- количество записей: от нескольких элементов — в справочниках и до 1,5 миллионов — в приложениях.
Ошибки, которые возникали, привожу текстом, чтобы их решение было удобно найти через поиск в статье.
1. Выбор способа миграции
Из названия статьи уже понятно, какой способ был выбран, но изначально рассматривалось несколько вариантов. Опишу их плюсы и минусы. Возможно, это кому‑то поможет сделать выбор в аналогичной ситуации.
Как правило, любая миграция состоит из трёх шагов:
- Выгрузка данных из источника.
- Преобразование выгруженных данных в нужный формат целевой системы.
- Загрузка данных в целевую систему.
(общепринятый термин ETL — от англ. Extract, Transform, Load)
Соответственно, при оценке вариантов миграции нужно понимать, как будет выполняться каждый из этих шагов.
Миграция из БД в БД с помощью ETL-процесса
Классический вариант переноса данных между базами, который был сразу отброшен из-за сложной структуры хранения данных в БД ELMA365. Вендор крайне не рекомендует прямые операции в БД. Затраты на разработку и последствия такой миграции невозможно было адекватно оценить.
Миграция вручную посредством MS Excel
Такой вариант изначально рассматривался как приоритетный. Предполагалось, что заказчик выгрузит данные из MS Dynamics в XLS-файлы, затем мы преобразуем их средствами Excel и загрузим в ELMA365 стандартным импортом. Вариант на первый взгляд казался самым простым и быстрым, но от него отказались по следующим причинам:
- Множество ручной работы, и как следствие — человеческие ошибки.
- MS Excel в зависимости от версии и настроек на разных ПК может искажать форматы данных (даты, преобразование чисел в строки и т. д.), что добавит ещё больше ручной работы.
- По информации от опытных коллег, есть риск, что файлы при импорте придётся разделять на части по несколько тысяч записей, чтобы избежать ошибок. А это снова ручная работа.
- Самое главное — с учётом масштаба проекта и высоких требований заказчика к точности миграции стало понятно, что потребуется много итераций. Придётся многократно проверять качество загрузки, находить и исправлять ошибки, прежде чем загрузить данные даже в тестовый контур, не говоря уже об итоговой миграции на продуктив. На каждой итерации пришлось бы повторять ручную обработку данных в Excel. Можно конечно вспомнить, что такое макросы и VBA, но это как-то совсем не современно

Миграция с помощью сервиса и/или бизнес-процессов в ELMA365
Такой подход, казалось бы, решал все проблемы предыдущих двух вариантов. Выгрузка данных выполняется запросом в БД Dynamics с помощью специального микросервиса, далее — преобразование скриптами в бизнес-процессе, и тот же процесс через стандартный блок создания элемента безопасно записывает данные в ELMA365 без прямых операций с БД. Никаких ручных операций и запускать можно сколько угодно раз.
Но от этого варианта отказались из-за его высокой стоимости и долгой разработки. Несмотря на наличие готового микросервиса, нам пришлось бы проводить аналитику, писать запросы в БД Dynamics, скрипты преобразования, бизнес-процессы. Оценка и сроки не устраивали заказчика. Кроме того, никакую часть работ при таком варианте он не мог взять на себя.
Миграция посредством SQL и стандартного импорта из CSV
В результате совместного творчества с заказчиком родился следующий вариант:
- Мы анализируем структуру данных Dynamics и составляем маппинг — таблички с сопоставлением полей Dynamics и ELMA365, и описанием логики преобразования данных на человеческом языке.
- Заказчик на основе маппинга пишет SQL-запросы, которые выдают данные сразу в структуре, нужной для ELMA365. То есть одновременно решается задача выгрузки и преобразования.
- Полученные данные сохраняются в CSV-файлы и загружаются стандартным импортом в ELMA365.
Это вариант одновременно решал проблему ручной работы, сокращал стоимость и позволял разделить задачи между нами и заказчиком. На нём и остановились.
2. Отладка миграции: возможные ошибки и их решение
На составление маппинга и написание SQL-запросов ушло примерно 2 месяца. Далее началась отладка — полученные CSV-файлы стали загружать на тестовый стенд и разбирать ошибки.
Наивно ожидать, что все ошибки вскроются сразу. Они открываются последовательно — пока не исправишь одну, не узнаешь о наличии следующей. Соответственно, процесс отладки получается итерационным и трудно прогнозируемым. Это нужно учитывать при планировании: фиксированные сроки и стоимость для миграции крайне не надёжны.
Ошибки в исходных данных
Найденные ошибки в данных — это индивидуальные особенности конкретной системы-источника, и исправляются они на уровне базы данных или скриптов. Просто примите как факт при планировании миграции, что в любой системе, прожившей несколько лет, будут нестыковки: пустые значения в обязательных полях, несовпадение типов данных, несоответствие данных бизнес-логике и другие сюрпризы.
Ошибки в синтаксисе (найди лишнюю запятую)
Следующий тип ошибок связан с тем, что для импорта используется формат CSV с разделителями в виде запятой. Соответственно, если запятая встретится где-либо в данных, при импорте она будет воспринята как разделитель. Такая проблема была очевидна заранее, поэтому на уровне скриптов все значения экранировались кавычками, но этого оказалось недостаточно:
- во-первых, оказалось, что запятые могут присутствовать и в названиях атрибутов, например Сумма, руб. Поэтому заголовки тоже нужно экранировать или вообще убирать отображаемые имена, оставляя только наименования атрибутов;
- во-вторых, в значениях могут встречаться кавычки, что ломает логику с экранированием. В таких случаях необходимо двойное экранирование;
- в-третьих, в текстовых полях встретились переносы строк, которые в CSV-файле воспринимаются как перенос всей строки — начало следующего элемента. Волевым решением от переносов просто избавились.
Пример 1
Ниже приведён пример ошибки, которую вызвала лишняя запятая в заголовке: «record on line 2: wrong number of fields». Как видно, текст ошибки далеко не всегда помогает найти причину.
Строка 1 содержит заголовки таблицы (наименования атрибутов), строка 2 — первый элемент. Очень долго искали ошибку в строке 2, а на самом деле ошибка была из-за лишней запятой в наименовании атрибута в строке 1: было определено неверное количество столбцов таблицы и потому в первом же элементе (строка 2) не совпало количество столбцов, их было на один меньше.
Пример 2
Еще один пример — ошибка, вызванная недостающей кавычкой: «extraneous or missing “ in quoted-field». Ошибка была действительно в 3-й строке, но что означает 299 в тексте ошибки, так и осталось непонятным: не совпало ни с номером символа, ни с номером колонки, в которой фактически была ошибка.
Несоответствие структуры приложения и файла (лишние атрибуты)
При составлении маппинга мы делали экспорт данных с dev-стенда ELMA365, чтобы получить основу для таблицы, т.е. все атрибуты приложения. При этом не все атрибуты требовалось заполнять при миграции. В результате в CSV-файлах содержалось множество пустых атрибутов. Так лучше не делать, по нескольким причинам:
- лишние колонки — это лишнее время обработки, и когда у вас таблица 100х1000000, то это чувствительно;
- сложнее искать ошибки, т. е. те самые лишние запятые;
- при перезаписи существующего элемента наличие колонки с пустым значением воспринимается как «стереть существующее значение» (это может проявить себя при «дозагрузке» данных);
- устаревшие атрибуты с dev-стенда могут отсутствовать на целевом стенде, что будет вызывать ошибку импорта.
Совет: выгружать в CSV-файл только колонки с нужными значениями, пустые — удалять.
Ошибки при наличии связанных приложений
В ELMA365 связь приложений реализуется через атрибуты-ссылки типа Один или Несколько. А еще связи могут быть «двусторонними», если указан параметр Поле для связи. В справке по платформе описано использование этого параметра для виджета Связанные элементы.
Рассмотрим пример. У приложения Служебные записки на закупку есть атрибут-ссылка на приложение Заявки на закупку, который так и называется — Заявка на закупку. В этом атрибуте указано поле для связи Служебная записка, который содержит ссылку на приложение Служебные записки на закупку.
Таким образом, если пользователь в Служебной записке выбирает элемент приложения Заявка на закупку, то автоматически в этом элементе заполняется атрибут-ссылка на Служебную записку. Тоже самое происходит при заполнении связей при импорте.
При подготовке к импорту для сохранения всех связей было решено переносить данные с «родными» идентификаторами из MS Dynamics (к счастью, их формат подходил), чтобы не пришлось после загрузки отдельно заниматься связыванием элементов. Причём, ELMA365 позволяет загрузить в атрибут-ссылку id элемента, которого ещё не существует, а после его появления связь корректно «стыкуется» и работает. Поэтому мы предположили, что последовательность загрузки не важна. На рисунке ниже показано, как выглядит ссылка на несуществующий элемент в таблице (на форме атрибут выглядит как незаполненный), до загрузки соответствующего приложения.
Однако, мы не учли поведение системы при «двусторонних» связях и во всех CSV-файлах были заполнены все атрибуты-ссылки с обеих сторон связи. Сначала выгрузка шла хорошо и элементы загружались, в связях отображалось ожидаемое «еще элементов…». Но дальше начали возникать подобные ошибки: «duplicate key value violates unique constraint»:
С помощью перевода и экспериментов мы расшифровали ошибку так:
- Загрузился элемент приложения А со связью на приложение Б.
- В элементе приложения Б автоматически записалась ссылка на элемент приложения А.
- При загрузке элементов приложения Б обнаруживается дубликат, т. е. загружаем связь, которая уже есть
Проделав несколько неудачных экспериментов, мы выработали итоговый подход, который привёл к успешной загрузке связанных приложений:
- «Двусторонние» связи следует заполнять только с одной стороны, т. е. только в одном из связанных приложений. При этом желательно выбирать для заполнения ту сторону, на которой элементов меньше. Например, если связь Один — Несколько, заполнять связи лучше в приложении с опцией Один. Причины такого решения две:
- меньше вероятность ошибки;
- длина строки в CSV-файле не бесконечная — слишком много идентификаторов в одну строку просто не влезет.
- Во втором приложении связь не просто остается пустой, а колонка полностью удаляется из CSV-файла, иначе при повторной загрузке связь очистится.
- Первым загружается приложение без связи, затем — приложение с заполненной связью. Таким образом, выстраивается последовательность загрузки всех приложений с учётом взаимосвязей.
3. Отладка миграции: обработчики событий и бизнес-процессы
В ELMA365 есть несколько вариантов автоматического запуска скриптов и процессов при создании или изменении элемента приложения. При миграции данных важно оценить, какие из этих скриптов необходимо применить к импортированным данным, а какие — наоборот, нужно исключить.
Формирование названия элемента по шаблону
Это стандартная настройка приложения. При импорте она не срабатывает, но в дальнейшем при работе пользователей с загруженными элементами настройка будет применяться при каждом сохранении элемента.
Таким образом, важно убедиться, что:
- название присутствует в файле импорта. Тогда все элементы получат название сразу после импорта;
- при дальнейшем применении шаблона название элемента не «сломается» из-за того, что в шаблоне используется параметр, который не заполняется при импорте.
Скрипты на формах создания/редактирования элементов
Логика аналогична предыдущему пункту — при импорте никакие формы не будут открываться и скрипты не будут выполняться. Но в дальнейшем при редактировании элемента пользователем скрипт запустится. Поэтому важно убедиться, что при миграции все необходимые для скриптов данные были загружены либо в скриптах присутствуют проверки на их отсутствие.
Обработчики событий
На форме импорта данных присутствует чек-бокс Игнорировать обработчики событий. Он влияет на запуск обработчиков событий создания элементов приложения, настроенных в разделе Администрирование > Модули.
Такие обработчики, если они есть в конкретном решении, написаны не для миграции, а для ежедневной работы в системе. Кроме того, что их запуск при импорте создаёт дополнительную нагрузку, обработчики содержат алгоритмы, которые могут быть либо полезны либо, наоборот, вредны при импорте. При подготовке к миграции проанализируйте, какие обработчики настроены на каждое приложение, как они повлияют на результат импорта и нужно ли игнорировать каждый из них.
В нашем случаем нашлись как приложения, по которым обработчики нужно обязательно запускать, так и те, которые нужно игнорировать. Отдельные обработчики пришлось доработать, чтобы их скрипты при импорте выполнялись частично.
Рассмотрим пример.
Обработчик, связанный с приложением, в «обычной жизни» создаёт папку для файлов, запускает бизнес-процесс и выполняет ряд изменений в данных. Папка нам при импорте нужна, процессы — только для определённых записей в зависимости от их статуса, а изменения в данных не нужны для всех записей. Для решения задачи мы добавили в обработчик параметр Режим работы с тремя значениями:
- загрузка записей в финальном статусе — обработчик только создаёт папку;
- загрузка записей в промежуточном статусе — обработчик создаёт папку и запускает бизнес-процесс;
- штатный режим — выполняется вся первоначальная логика обработчика.
Файл с данными для импорта этого приложения делился на две части (по статусам) и между загрузками этих частей нужно было переключить режим обработчика.
Здесь нашёлся еще один подводный камень. Обработчик событий выполняется асинхронно, для каждого загруженного элемента алгоритм запускается заново. В зависимости от количества элементов и сложности алгоритма обработка всех загруженных записей может занимать длительное время. Если переключить режим обработчика, не дождавшись завершения обработки всех элементов, очередной элемент будет обрабатываться уже с учётом переключённого режима. В нашем случае это привело к запуску несколько тысяч лишних бизнес-процессов по элементам с финальным статусом.
Чтобы избежать такой ситуации, нужно отслеживать завершение обработки всех загруженных записей. Сделать это можно без усложнения самого обработчика — по наличию видимых результатов. В нашем случае завершение обработки подтверждали тем, что не оставалось записей без созданных папок.
Бизнес-процессы, связанные с приложением
В ELMA365 есть возможность связать бизнес-процесс с приложением. Такой процесс будет автоматически запускаться при создании элемента приложения, но… только при создании пользователем. При импорте элементов такие процессы не запускаются. Хорошо это или плохо, зависит от конкретной реализации в решении. Если есть необходимость запустить процесс при импорте, решить это можно, например созданием обработчика событий.
Встроенные алгоритмы типовых решений
Типовые решения могут содержать дополнительные алгоритмы, срабатывающие при создании элемента приложения, которые не видны на уровне low-code.
Например, в системном решении ELMA365 Проекты при создании элемента приложения Проекты создаётся папка проекта, которая далее используется на вкладке Файлы типовой формы проекта.
Этот алгоритм срабатывает как при создании элемента пользователем, так и при импорте. Никакие чекбоксы на его запуск не влияют.
4. Миграция данных и бизнес-процессы
Данный пункт относится уже не столько к самой миграции, столько к тому, как работать пользователям после неё. Чтобы эта работа не доставляла лишней боли, подготовиться нужно заранее.
Как правило, при проектировании решения закладывается логичный порядок работы: сначала создаётся элемент приложения, потом по нему запускается процесс, в ходе выполнения процесса могут заполняться атрибуты и меняться статусы.
Маловероятно, что учитываются кейсы, когда, например, на шаге 2 элемент имеет статус, который он никак не может получить раньше 10-го шага. При миграции такое возможно, т .к. данные загружаются в том «текущем» состоянии, до которого они дошли в исходной системе, а процесс в новой системе запускается заново.
На практике это может привести:
- к неудобствам, когда пользователям придётся «прощелкать» лишние этапы процесса, чтобы дойти до актуального;
- к невозможности работы, когда процесс падает с ошибкой или не даёт пользователю пройти какой-либо шаг.
Универсального решения таких проблем нет. Нужно анализировать каждый процесс, тестировать его поведение с разными загруженным записями и определять решения: инструктировать пользователей или дорабатывать процесс, добавляя дополнительные переходы или проверки.
Решение может быть организационным: договориться с пользователями, что все процессы завершаются в старой системе. Только после этого элементы загружаются в новую систему с финальными статусами и без запуска процессов. И в новой системе процессы запускаются только по новым записям и выполняются с начала. Возможность такого подхода определяется особенностями конкретного бизнеса, длительностью и количеством реальных процессов.
Буду рад, если эта статья поможет кому-то не повторить аналогичные ошибки или быстрее с ними разобраться. В нашем проекте на момент публикации статьи завершается тестовая миграция, предстоит миграция на продуктив. И надеюсь, дополнять статью не придётся…