Слышали о ГИС ГМП? Скорее всего, мало кто слышал. Зато точно видели, если:
— вам на Госуслуги приходила пошлина на оплату нового загранпаспорта
— вы получали уведомление о штрафе ГИБДД в банковском приложении
— вы узнавали состояние своего единого налогового счёта (ЕНС)

Чтобы всё это стало возможным, Федеральное Казначейство создало Государственную информационную систему о государственных и муниципальных платежах (ГИС ГМП). Именно она аккумулирует все назначенные людям и компаниям платежи и контролирует их оплату, сверяя платёжные поручения банков с начислениями.

Как вы думаете, много ли там начислений? А платежей? Сотни миллиардов.

В рамках импортозамещения нам в РТЛабс поставили задачу — мигрировать ГИС ГМП с базы данных Oracle на другую подходящую. Да-да, нам предстояло мигрировать систему, которая хранит сотни терабайт данных — кому и что было начислено, как и когда это оплатили.

Как нам это удалось? Именно об этом я и хочу рассказать. На связи Михаил Денисов — технический директор блока развития казначейских проектов.

Выбираем подход к решению задачи

Отмечу ещё особенность самой системы. Основную бизнес-логику в ГИС ГМП обрабатывают процессы, настроенные в SVIP (SmartVista Intergration Platform), которая является средой исполнения BPMN-процессов «на стероидах». Это, конечно, сильно усложняло задачу и сужало выбор возможных подходов.

Но как говорится, нерешаемых задач не бывает. Мы разработали 3 подхода.

Подход 1. Коннектор внутри SmartVista

В рамках первого подхода мы хотели добавить в SmartVista возможность через один коннектор работать с двумя базами данных. Так мы бы обеспечили режим параллельной записи и в фоне мигрировали данные, используя подходы, уже опробованные на других проектах, в том числе на Госуслугах.

Однако команда SmartVista отказалась добавлять такую поддержку, и этот вариант мы отклонили.

Подход 2. Внешний адаптер базы данных

Мы поняли, что не можем добавить в систему хитрый адаптер базы данных, который прятал бы под собой 2 других. Тогда мы решили попробовать реализовать его снаружи и использовать для передачи запроса HTTP.

Идея была в том, чтобы реализовать сервис-прослойку, который ходил бы в старую и новую базу данных прозрачно для приложения. При этом вызов SQL-запросов из SmartVista шёл бы не по JDBC (Java Database Connectivity) к базе, а по HTTP к прослойке.

Да, при таком подходе возникают вопросы к транзакционности, но они решаемы. Те же вопросы появлялись и при подходе с коннектором внутри SmartVista, который был уже неоднократно реализован на других проектах. Короче, вариант, как говорится, «сотка».

Но и к этому подходу были претензии как у разработчиков бизнес-процессов для SmartVista, так и у заказчика.

Разработчикам пришлось бы перелопатить все бизнес-процессы — более 500 штук. А также адаптировать запросы, чтобы их можно было структурировано передавать по HTTP.

Заказчику же предстояло принять в эксплуатацию новое приложение-прослойку. Чтобы добавить нужные функции и поля, нужно было адаптировать и бизнес-процессы, и саму прослойку. А после миграции данных — выпилить промежуточное приложение. Всё это дополнительные работы.

Такой вариант реализации содержал много очевидных трудозатрат и минимум скрытого риска. Однако из-за явных накладных расходов и масштабных трудозатрат заказчик предпочёл другой подход.

Подход 3. Синхронизация баз данных

Что, если мы поставим рядом ещё одну такую же систему, но на импортозамещённой базе данных, и синхронизируем их? Может сработать.

Основная проблема относительно двух предыдущих подходов была в том, что мы не контролируем процесс записи программно. Данные пишутся в Oracle напрямую. Мы не можем перехватить их, чтобы записать в другую базу.

Если не получается перехватить «до», возможно, можем перехватить «после?» Можем! В Oracle есть механизм Redo Log — читаемый на программном уровне транзакционный лог. И мы можем к нему подключиться. Ура, есть зацепка.

Какую схему реализации мы предложили

1. Фиксируем дату старта

2. Начинаем переливать данные — они непрерывно меняются

3. Покачиваем транзакции из Redo Log, чтобы долить изменившиеся данные в новую базу

4. Переключаемся на новую систему

Этот вариант и выбрал заказчик, и мы взялись за реализацию.

Несмотря на кажущуюся простоту, в этом подходе было много скрытых рисков. Как мы с ними справлялись, я и расскажу дальше.

Спойлер — у нас всё получилось.

Процесс реализации

Выбираем решение для базы данных

Стоявшая перед нами задача сразу выглядела как челлендж мирового уровня — 200 TB базы данных из Oracle нужно было перевести на другое решение. Оно должно было не только справиться с текущим масштабом данных и потоком вставок и чтений, но и быть готовым к дальнейшему росту.

Единственным решением, похожим на то, что нам было нужно, стала СУБД Shardman от Postgres PRO — вендора и контрибьютора Postgres. Специалисты своего дела, они тогда вывели на рынок свежую разработку Shardman 14 на базе Postgres PRO 14.

Как можно догадаться из названия, основная фишка Shardman — шардирование данных. Не знакомы с концепцией шардирования? Не беда, сейчас поясню.

Бывает, база данных становится слишком объёмной, чтобы поместиться даже на очень большой сервер с сотнями процессоров и терабайтом оперативной памяти, — наш вариант. Что тогда делать? Может быть, разместить базу на нескольких физических серверах, разделив между ними данные и нагрузку? Именно такой подход и называется шардированием.

Но когда мы уходим из уютного и понятного пространства выполнения программного кода на одном сервере, встаёт вопрос: а запишутся ли все данные в рамках транзакции правильно? Большинство кластерных баз данных, которые имеют встроенное шардирование, не предоставляют строгой консистентности при записи и стерилизации транзакций, говоря техническим языком. Это, конечно, большая проблема — нам в рамках системы нужна строгая согласованность на запись.

Именно поэтому для замены базы данных мы выбрали Shardman. Как потомок класси��еской базы данных ACID, она и в распределённом режиме исполнения кода обеспечивала строгую стерилизацию транзакций. Это значит: если мы что-то записываем в базу данных, оно либо точно записалось и тут же доступно для чтения, либо не записалось — и нигде не осталось мусора и несогласованных данных.

Так, с базой определились, к счастью, нашёлся вариант.

Разрабатываем новую систему под Shardman

Решение, на котором остановились мы с заказчиком, подразумевало создание второй развёрстки, полностью переориентированной с Oracle на Shardman.

Для тех, кто мало знаком с тонкостями Oracle, расскажу в двух словах. Это лидер рынка проприетарных (с закрытым исходным кодом) баз данных, которые распространяются исключительно на коммерческой основе и за большие деньги. Создатели Oracle десятилетиями оттачивали свою систему и добавили в неё сотни низкоуровневых оптимизаций, свои особые конструкции языка, десятки специфических утилит.

При создании приложений разработчики вынуждены полагаться на особые конструкции языка, оптимизации и инструменты, которые есть только в Oracle. Поэтому заменить Oracle на Postgres — это не то же самое, что заменить батарейку AA Duracell на батарейку AA GP. Скорее, это как переставить двигатель от Toyota в Москвич. Это возможно, но потребуется болгарка. Много болгарки!

Мы были бы в заметно более простой ситуации, если бы нужно было просто заменить Oracle на Postgres. Это сложно, но не слишком. Но нам нужно было переехать не просто на Postgres — на Shardman. А он имеет кластерную природу и, как следствие, много особенностей, о которых я расскажу дальше.

Адаптируем систему

И��начально идея наскоком заменить Oracle на Shardman базировалась на предположении, что ничего особо переписывать не придётся. «Кое-где поправим немного, и взлетит», — говорил первый архитектор проекта миграции.

Нужно ли говорить, что он заблуждался и насколько сильно? Конечно, в итоге мы адаптировали систему и решили все проблемы, но работать засучив рукава пришлось всем. Включая сотрудников Postgres PRO, которые сопровождали внедрение Shardman в этом крайне амбициозном проекте.

Вместе с сотрудниками Postgres PRO мы рассматривали все сложные и нагруженные сценарии, искали пути оптимизации, пробовали и ошибались. Специалисты из Postgres PRO объясняли, почему в Shardman что-то работает не так, как в Oracle. Рассказывали, как мы можем добиться того же результата, используя другие подходы. Иногда ребята брали паузу и возвращались с доработками, которые помогали добиться кратного роста производительности в том или ином сценарии. Кстати, Oracle начинали точно так же.

Адаптация проходила в несколько этапов. Сначала базовая адаптация очевидных вещей, потом — адаптация по результатам загрузочных тестов. В финале мы запустили адаптированную систему на Shardman в режиме параллельной работы на продуктивном графике. Она обрабатывала запросы на вставку и чтение, но не отдавала результат клиентам.

Последний режим был особенно полезен. Итерация подготовки и исправлений на нём стала ключевой для запуска этой версии системы в продуктивную эксплуатацию.

Мигрируем данные

Мало адаптировать систему и заставить её адекватно работать на новой базе данных. Исторические данные никуда не денешь. Особенно когда это касается оплаты выставленных гражданам начислений за последние 13 лет. Причём некоторые из этих начислений до сих пор не оплачены и ждут своего часа.

А у нас не просто данные — 200 терабайт и миллиарды записей. Базы данных небольших и средних систем весят значительно меньше. База весом 20 гигабайт уже считается большой, а это в 10 000 раз меньше нашей.

Классический подход, при котором «мы сейчас всё остановим на пару часов, мигрируем данные и включим», на таких объёмах просто не применим. Чтобы пойти по этому пути, нам пришлось бы остановить федеральный ресурс не на 2 часа, а на 2 недели. Такой вариант не устроил бы ни нас, ни Федеральное Казначейство.

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

Первая проблема — мы не могли запустить миграцию на полную мощность. Нужно было подобрать сдержанный режим, сбалансировав нагрузку на продуктивную базу. Даже несмотря на то, что миграция запланирована с синхронной реплики (standby), обращаться с ней тоже нужно было аккуратно.

Экспериментальным путём мы выяснили, что для прокачки всех данных в щадящем режиме требуется не 2, а 3 недели.

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

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

Решение 1

Мы можем расставить в коде информационной системы перехват модификаций объектов и отправлять их в брокера очередей. Не знакомы с термином «брокер очередей»? Это такая специфическая база данных для хранения потоков событий, незаменимая часть современных информационных систем.

Вариант отличный, но он не подходит для сложной среды с «коробкой» SmartVista, которую так просто не переделать.

Решение 2

Мы можем навесить триггеры на все таблицы, чтобы они создавали эвент и записывали их в отдельные таблицы, и реализовать тем самым паттерн Transactional Outbox.

Паттерн известный, идея прямая и понятная, но при нашем количестве вставок применение такого подхода «в лоб» почти гарантированно приведёт базу данных в нерабочее состояние. Не подходит.

Решение 3

Мы можем использовать резервное копирование. База данных сама сохраняет все происходящие транзакции, чтобы иметь опорные данные на случай сбоя. Восстановление из резервной копии для всех баз данных происходит одинаково — есть полная резервная копия «снапшот» и лог событий, которые произошли при снятии слепка базы и после него. Вот этот лог нам и нужен.

В Postgres и многих других современных базах данных этот лог называется WAL (Write Ahead Log). Если кому интересно, он называется Ahead (опережающий), потому что транзакция записывается туда до того, как вставляется в тело таблицы в базе данных. Когда транзакция успешно заканчивается, в лог вносится ещё одна запись об этом.

В Oracle есть свой WAL, который называется Redo Log и выполняет ту же функцию. Что самое важное — там сохраняются все-все транзакции. Они-то нам и нужны, чтобы распаковать обновления и донести их до целевой базы, когда прокачка основного массива данных будет завершена.

Здесь есть небольшая проблема в том, что Redo Log пишется на диск в бинарном формате и упакован специфическим образом. Но в Oracle есть удобная утилита Logminer, которая позволяет в простом и понятном SQL-режиме работать с данными из Redo Log.

Именно этот вариант подошёл нам по всем параметрам: мы не модифицируем коробочное решение SmartVista, не меняем код приложений и не создаём дополнительную нагрузку на продуктивную базу. При этом имеем возможность повторить все изменения, которые произошли, пока мы перекачивали наши безумные терабайты данных. Красота!

Обеспечиваем целостность

Вы знаете главную заповедь программистов? «В любой программе есть по крайней мере ещё одна ошибка». Это печальная правда о разработке автоматизированных систем. Ошибки бывают разной степени критичности и влияния на систему и бизнес-процесс. Бывают даже полезные ошибки, которые исправляют последствия других ошибок. Но факт остаётся фактом — ошибки есть всегда, от этого никуда не деться. Если кто-то вам гарантирует, что напишет программный продукт без ошибок, не верьте ему — он вас обманывает.

Знание — сила. Мы знаем, что ошибки будут. Как мы можем к этому подготовиться? Правильно, создать ещё одну программу, которая будет контролировать и выявлять ошибки в работе предыдущей. В ней тоже будут ошибки? Конечно! Но каждый слой контроля снижает количество неправильного поведения и ошибок в данных целевой системы в 20—30 раз.

«А кто будет сторожить сторожей?» Систему проверки целостности глазами контролируют команда разработки и служба эксплуатации, используя свой естественный интеллект.

Для проверки целостности мы решили написать утилиту по методике, которую разработал я, а реализовал Станислав Флусов. Мы развили её, дополнив новыми идеями и подходами (пожалуй, это заслуживает отдельной статьи). К слову, сверка данных между двумя системами в международном сообществе называется «реконсиляцией».

Нам было нужно не только идентифицировать факт расхождения в данных, но и подсветить, в каких полях они обнаружены, чтобы облегчить работу тем, кто будет анализировать расхождения. Эту функциональность мы тоже включили в утилиту проверки расхождений.

Тестируем

Задача амбициозная — тысячи человеко-часов потрачены на то, чтобы адаптировать систему под новую базу данных, отладить и модифицировать процессы и внести изменения в саму базу данных. Написаны миграторы, домиграторы и реконсиляторы. Пора делать тестовый запуск.

Знаете сколько ракет Phalcon взорвалось у Илона Маска, пока не был совершён первый удачный полёт? Девять! Почему? Потому что, даже если каждый отдельный компонент системы создан и на собственных отдельных тестах работает идеально, собранный как часть системы он покажет вам, где вы ошибались.

Запуск за запуском мы отлаживали систему. Пока процессы притирались друг к другу, реконсилятор показал, где мы ошиблись в миграторе, а глаза эксплуатации показали ошибки в реконсиляторе. В результате мы получили весьма стабильную версию всего решения.

Просчитываем риски

Запуск новой системы — это всегда риск. Кто знает, как делается настоящее ИТ-решение, понимает — с первого раза ничего не идёт так, как задумано. Надежды и грёзы на тихий и безмятежный плановый запуск лучше оставить. Нужно ждать шторм. Нужно понимать — что-то точно пойдёт не так. И нужно быть готовым.

Что самое плохое и опасное в критической ситуации? Правильно — паника! Как избежать паники? Держать под рукой чёткие инструкции и чек-листы на все или почти все сценарии развития ситуации.

Перед запуском вся команда проводила репетиции. Мы составили чёткие инструкции для переключения систем и, что самое важное, для отката. Какие показатели системы мы будем считать критическими? Что для нас значит критический? Какие меры мы предпримем, если по своим оценкам попадём в критический диапазон? Ответы на все эти вопросы нужно было дать заранее. И мы их дали.

Идём в прод

Был ли шторм? Конечно, был. Но предупреждён — значит вооружён.

Первый запуск системы был ограниченно успешным. На удивление (на самом деле нет) даже запуск на «параллельном» графике не дал нам всей картины. Но аккуратное первое включение, полная готовность к обратному переключению и максимально возможная подготовка сделали включение эффективной и полезной разведкой боем. Вернулись, учли всю полученную информацию, ушли вносить последние коррективы.

Второй запуск был куда удачнее. Мы запустили и стабилизировали новую систему. Старую переключили на параллельный трафик для поддержания консистентности (вдруг всё-таки придётся откатываться). Реконсилятор настроили на проверку синхронизации систем.

Пост-прод

Говорят, что ремонт нельзя «закончить» — его можно только «приостановить». Так же и со свежим решением — всегда есть что улучшить и «отполировать». Это важный и ценный этап. Да, у системы есть показатели назначения. Да, есть вопросы стабильности, но это не весь ландшафт решения.

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

Выводы

1. Нет ничего невозможного. Если взяться за вопрос, засучив рукава, и немного подумать головой, проблемы превращаются в задачи, а задачи просто решаются одна за другой. И так до победного.

2. В любой программе есть ещё одна ошибка. Если вам ценны данные, нужно реконсилировать миграцию, даже когда это кажется бессмысленной тратой времени и денег, и «мы себе доверяем». Доверяй, но проверяй. Слепое доверие — путь к потере данных.

3. Тяжело в учении, легко в бою. Не стоит недооценивать негативные сценарии и полагаться на авось. Хочется верить, что всё пройдёт идеально, но этого не случится. Будьте готовы к этому.

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

Комментарии (2)


  1. yegreS
    03.12.2025 14:43

    Не рассматривали ydb как альтернативу Postgres?


  1. mishamota
    03.12.2025 14:43

    Большинство кластерных баз данных, которые имеют встроенное шардирование, не предоставляют строгой консистентности при записи и стерилизации транзакций, говоря техническим языком

    ACID, она и в распределённом режиме исполнения кода обеспечивала строгую стерилизацию транзакций. Это значит: если мы что-то записываем в базу данных, оно либо точно записалось и тут же доступно для чтения, либо не записалось — и нигде не осталось мусора и несогласованных данных.

    1. Автопроверка орфографии - зло. "Стериализация" транзакций

    2. Сериализация, видимо, имелась в виду она, не относится к согласованности распред. систем. Это уровень изоляции ACID.

    3. То, что далее описано, это смешение, атомарности (или записалось или нет, без мусора) с согласованностью (тут же доступно для чтения) и это не про сериализацию. Надо б разлепить, а то ничего тут непонятно.

    4. Ну и уровень согласованности, которого добились интересно. Линеаризуемость, секвенчуал или лимитед эвенчуал, там, например?