ФОРС – Центр разработки
Москва, Трифоновский тупик, д. 3
Москва, Графский переулок, д. 14, корп. 2
Новости и события
В этой статье расскажем о том, как ИИ помог нам разработать инструмент для проверки корректности переноса данных из Oracle в PostgreSQL. А ещё раскроем интригу, почему это оказалось совсем не так просто, как могло бы показаться...
При переносе рабочей базы данных из Oracle в PostgreSQL встаёт непростой вопрос корректности полученного результата. Ведь момент миграции — единственный, когда обе базы существуют одновременно, это своего рода суперпозиция и, пропустив его, проверить что-либо станет уже практически невозможно.
Простой проверки количества строк для такой задачи недостаточно. Нужно убедиться, что не разошлись сами значения: числа, даты, текст, причём не в одной-двух таблицах, а во всех сразу, счёт которых идёт на сотни. Построчная сверка каждой строки для такого объёма не подходит ни по времени, ни по памяти. Важно отметить, что задача касалась не только данных, но и всей структуры: типов колонок, ограничений, первичного ключа, которые тоже способны незаметно разойтись при переносе.
Отдельная сложность заключается в зыбкости границы между легитимным и нелегитимным расхождением баз данных Oracle и PostgreSQL, поскольку физически они не совпадают друг с другом в точности ни по типам данных, ни по структуре. Для данных же в момент их переноса любое расхождение является проблемой миграции, а не особенностью системы. Для эффективного решения этой задачи требовался специальный инструмент, который мы и разработали с помощью ИИ. Он получил название Bdcompare в соответствии со своим назначением: сопоставлять, сверять результаты переноса баз данных, находить отклонения и устранять ошибки.
Первая версия инструмента была предельно простой: один класс на один view, запросы к Oracle и PostgreSQL и построчное сравнение прямо в коде. Такой подход работал, пока объектов было пять. Когда счёт объектов пошёл на сотни, он перестал масштабироваться не по скорости выполнения, а по вниманию: невозможно было держать в голове десятки view с разными ключами и особенностями периода, не упустив ни одного.
Во втором релизе инструмента использовался принцип «каталог вместо кода»; один прогон осуществлялся уже по всем view сразу.
В третьем релизе охват был расширен ещё больше: с view на исходные таблицы, плюс отдельная сверка структуры по типам, введение признака «может быть пустым», первичный ключ. И главное — мы использовали агрегатный режим для любого объёма данных.
В части данных решение Bdcompare позволило считать агрегаты (количество строк, минимум, максимум, сумму) прямо внутри базы и сравнивать только итог, а не каждую строку. Оно работает на любом объёме, так как база никогда не отдаёт наружу больше одной строки результата. Для нечисловых колонок понятие суммы пришлось расширить.
Числовые данные складываются как есть, без округления. Даты и время переводятся в число (по сути те же цифры года, месяца, дня и часов, только без разделителей) и складываются как числа.
Текст суммируется по длине строки.
Если сумма за весь период не совпадает, проверка не останавливается на этом уровне. Период дробится на месяцы, а внутри расходящегося месяца дополнительно на пятидневные окна, пока расхождение не сузится до конкретного участка. Позже к сумме добавились минимум, максимум и счётчик пустых значений по каждой колонке: сумма способна маскировать компенсирующие ошибки, когда одни строки завышены, а другие занижены, и физически не видит переход значения из пустого в нулевое.
Отметим, что для данных не существует «ожидаемых» расхождений. Это правило было закреплено на этапе проектирования и с тех пор не нарушалось. Если в момент миграции Oracle и PostgreSQL показывают разные числа, то, как мы уже отмечали выше, это проблема конкретного проекта, а не особенность системы. И это не может быть поводом создавать прецедент. Со структурой правило мягче, потому что Oracle и PostgreSQL физически не совпадают по типам данных один в один: словарь легитимных соответствий типов здесь выступает штатным механизмом проверки, а не временным «костылём».
Сегодня список проверок вырос из одной сверки количества строк до набора, который анализирует и структуру, и данные:
СТРУКТУРА
Тип колонки, позиция, признак «может быть пустым», точность и масштаб числа, состав первичного ключа.
ОБЪЁМ
Количество строк, минимум и максимум по числовому идентификатору.
КАЖДАЯ КОЛОНКА
Сумма без округления, минимум, максимум и число пустых значений, целиком за период и по месяцам, с разбором расходящегося месяца на пятидневные окна.
УСТОЙЧИВОСТЬ
Ошибка или переименование одной колонки не портит отчёт по остальным: проблемная колонка помечается отдельно, а не «роняет» всю таблицу.
ПО ЗАПРОСУ
Полная построчная сверка, по ключу или как множество без ключа, с защитой от переполнения памяти на больших объёмах.
ОТЧЁТ
Один читаемый HTML-файл вместо набора логов: оглавление с цветным вердиктом по каждой таблице и разворачиваемые подробности по клику.
В ходе работы над этим проектом нам пришлось столкнуться с рядом трудностей. Не всё из задуманного удалось реализовать сразу, часть трудностей потребовала пересмотра самого подхода, а не точечного исправления.
Расширение сверки структуры не удалось.
Попытка добавить в структурную сверку внешние ключи, уникальные и проверочные ограничения, а также значения по умолчанию дважды закончилась ошибкой ORA-00932: несовместимые типы данных. Два системных представления Oracle, которые хранят нужные значения, используют тип LONG, с которым штатные средства сравнения не работают. Частичные обходные решения проблему не сняли, и эту часть работы пришлось откатить до рабочей версии. Расширение структурной сверки остаётся открытой задачей на будущее.
Погрешность, которой не может быть.
На одной из таблиц сумма по колонке с датой не совпадала, но не грубо, а на 20-30-м знаке после запятой: ...523119051.650747. Разница такого порядка не может быть настоящим расхождением данных, и её действительно там не было. Причина оказалась во внутренней арифметике Oracle: при вычитании дат доли секунды не всегда укладываются в точные двоичные дроби, и сама операция вычитания вносит крошечную погрешность. Решение оказалось в том, чтобы вообще не считать разность: дата форматируется в цифровую строку и разбирается обратно в число, без единой операции вычитания.
«Неверное число», которого не должно быть.
Тот же приём «дата как число» один раз неожиданно «упал» с ORA-01722: неверное число на колонке с дробными секундами. Причина: Oracle разбирает число по десятичному разделителю текущей сессии, а не по универсальному правилу. Если у сессии разделитель запятая, а не точка, строка с точкой в дробной части просто не парсится. Этой ошибки можно избежать, если задавать формат и разделитель явно, а не полагаться на настройки среды, в которой выполняется запрос.
Одна колонка не должна «топить» всю таблицу.
Изначально ошибка сравнения одной колонки, например, если её переименовали при переносе, прерывала сверку всей таблицы целиком: количество строк, минимум и максимум по остальным колонкам терялись вместе с одной проблемной. Решением стала изоляция ошибки на уровне колонки и новый статус отчёта:
OK частично — проверено не всё Расхождение
Статус «частично проверено» не путает необследованную часть с подтверждённым совпадением, и рядом с ним всегда указан список тех колонок, которые при сверке не совпали, а также причина этого.
Подведём итоги того, что удалось улучшить в ходе нашего проекта по миграции баз данных с применением инструмента Bdcompare.
Полный прогон по всем схемам (283 таблицы) подтвердил, что расширенные проверки не добавляют ложных срабатываний: таблицы, которые уже были подтверждены как совпадающие, остались совпадающими и по новым критериям, минимуму, максимуму и счётчику пустых значений. При этом полный охват вскрыл находки, которые не были видны при простой проверке количества строк: в двух таблицах при переносе исчезли конкретные колонки (сертификатные поля, поля верификации), в одной пропал весь набор строк, а в одной колонке-флаге значение при миграции оказалось раскатано в два отдельных поля.
Заметно улучшилась и наглядность самого отчёта. Пустое или отсутствующее значение всегда подписано текстом («нет данных»), а не прочерком, который приходится расшифровывать. А там, где включена построчная сверка, к каждой найденной несовпадающей строке сразу прикладывается готовый запрос, который достанет именно эту строку из Oracle и из PostgreSQL по значениям ключа:
ГОТОВЫЙ ЗАПРОС ИЗ ОТЧЁТА, ДЛЯ СТРОКИ С РАСХОЖДЕНИЕМ
Oracle: SELECT * FROM SCHEMA.TABLE WHERE ID = 128450
PostgreSQL: SELECT * FROM schema.table WHERE id = 128450
Синтаксис вспоминать не нужно: запрос уже готов, осталось скопировать и выполнить. Наконец, весь набор шагов — от выгрузки списка таблиц до полной сверки данных теперь запускается из одного меню, без необходимости держать в голове дюжину классов.
Тем не менее, не все поставленные задачи удалось решить. Значения по умолчанию для колонок так и не сравниваются: причина — упомянутая ранее в пункте №1. Переполнение целочисленных типов, точное число знаков после запятой и специфичные для бизнеса граничные значения тоже остаются в списке на будущее, каждый пункт по конкретной, разобранной выше причине, а не потому что о нём забыли.
Расскажем, кто придумал и кто написал код Bdcompare.
У этого инструмента, как и у многих рабочих решений последних лет, два соавтора с разными ролями. Какие проверки нужны, что считать настоящей проблемой, а что нет, где остановиться и не переусложнять: все эти решения принимал человек, ведущий миграцию. Формулы, код, устройство отчёта и разбор каждой странной цифры до самой причины: это работа Большой языковой модели, ИИ-ассистента, работающего прямо в терминале рядом с проектом.
Важно отметить, что концепция сверки не была принята за один присест: первый вариант обсуждался и дорабатывался около четырёх дней, прежде чем в проекте появилась первая строка кода. К реализации переходили только тогда, когда решение выглядело продуманным и устойчивым к рискам, а не как первая пришедшая в голову идея. Тот же порядок повторялся и на уровне отдельных доработок: ассистент предлагает решение и обсуждает риски, и только после согласия пишет код, а результат проверяется не на словах, а на запросах к настоящим базам. Находку про погрешность на 20-30-м знаке, например, сначала перепроверили вручную, и только когда цифры совпали с реальными данными, фикс попал в код.
Тем не менее, из этого не следует, что ассистент заменяет инженерное решение. Оптимальной оказалась модель, при которой на ассистента приходится скорость реализации и рутина диагностики, а решение о том, что проверять и какой риск допустим, вместе с финальной проверкой результата на реальных данных, остаётся за человеком.
Москва, Трифоновский тупик, д. 3
Москва, Графский переулок, д. 14, корп. 2
Москва, Графский переулок, д. 14, корп. 2
Москва, ул. Авиамоторная, д. 8, стр. 12, 5 этаж
Москва, Трифоновский тупик, д. 3
Москва, Графский переулок, д. 14, корп. 2
Москва, Графский переулок, д. 14, корп. 2
Москва, ул. Авиамоторная, д. 8, стр. 12, 5 этаж
Благодарим за ваш запрос.
Мы обязательно
свяжемся с вами!
Благодарим Вас!
Регистрация
прошла успешно.