Как мы мигрировали критичную БД с Oracle в CockroachDB
… простите, мигрировали куда? Туда!
CockroachDB — PostgreSQL-совместимая (по SQL-синтаксису DML) распределенная СУБД с открытым кодом (ну, почти). Ее название символизирует, что она, как таракан, выживает в любых экстремальных ситуациях. Лично мне крайне импонирует такая СУБД с привычным SQL-интерфейсом, настройка которой занимает 5 минут, которая хранит данные — как Kafka — на нескольких узлах в нескольких ЦОДах сразу, имеет настраиваемый replication factor на уровне конкретных таблиц, легко переживает потерю как одного узла, так и целого ЦОДа, использует для этого механизм распределенного консенсуса Raft и при этом еще и имеет строгую консистентность и уровень изоляции serializable. Разработчики CockroachDB — выходцы из компании Google, которые решили коммерциализировать архитектуру распределенной СУБД Spanner.
Недостатки тоже есть, не переживайте, но про них лучше в другой раз :)
Почему именно CockroachDB?
Среди распределенных SQL-СУБД есть альтернативы в виде Yugabyte и TiDB, и с прошлого месяца YDB. Вопрос «Почему?» связан в первую очередь с тем, зачем вообще нужна БД. Как мне кажется, БД нужна для того, чтобы надежно хранить данные и доставать их через стандартный язык SQL, а удобство ее использования — приятный, но вторичный фактор. Тут надо заметить, что я почти 9 лет проработал в техподдержке Oracle, и видел достаточно случаев порчи БД, как из-за дисковых сбоев и ошибок администраторов, так и из-за багов в приложении и даже в коде самой СУБД.
Ключевыми критериями выбора были:
- многократно проверенный движок хранения данных. В CockroachDB используется RocksDB, в остальных — неизвестно кем, как и сколько тестированные альтернативы;
- регулярное тестирование тестом Jepsen в рамках сборки релиза на наличие проблем безопасного хранения данных в распределенной БД. CockroachDB успешно прошли этот этап еще 5 лет назад;
- совместимость с PostgreSQL для того, чтобы быстро мигрировать туда в случае серьезных проблем. TiDB совместимо с MySQL, YugaByte с Postgres, YDB пока что ни с чем;
- простота настройки, отсутствие сложных decoupled-архитектур. Тут CockroachDB лидер: один бинарный файл, один конфигурационный файл, все изменения проходят либо в онлайне, либо с поочередной перезагрузкой узлов кластера.
Подытоживая, CockroachDB на мой вкус однозначно наиболее зрелая распределенная SQL-СУБД на рынке. YDB основана на тех же принципах, но отстаёт в технологическом развитии на 3–4 года. Я крайне рад отечественной разработке в критически важной отрасли транзакционной обработки данных. К сожалению, все презентации Яндекса по YDB, которые я посетил на конференции HighLoad++, были призваны привлекать в Яндекс разработчиков сложными задачами, а не продвигать YDB как готовое решение.
Зачем так сложно — распределенный SQL?
Суммарная стоимость владения (Total Cost of Ownership) решений по повышению отказоустойчивости PostgreSQL заметно выше CockroachDB. С одной стороны, эти решения (Patroni, Corosync) бесплатные, с другой стороны нет никаких гарантий, что они всегда будут отрабатывать как надо. У нас были прецеденты того, что правильно настроенное решение по автоматическому переключению ролей БД с основной на резервную и обратно приводило к зависаниям и неработоспособности сервиса, чего в нашем случае мы себе позволить не могли. В то же время CockroachDB разворачивается в среде k8s из коробки с минимальными усилиями. Я считаю, за распределенным SQL будущее. Этой же точки зрения придерживаются такие технологические гиганты как Twitter, которые сделали CockroachDB базой для разработки по умолчанию.
Как развивались события
Для первого запуска в промышленную эксплуатацию CockroachDB мы выбрали небольшой новый проект с крайне незначительной нагрузкой. Это позволило нам обкатать нюансы использования новой базы в наших CI/CD-потоках, придумать сценарии перехода на PostgreSQL в случае, если что-то сильно пойдет не так, и опробовать базовые преимущества проведения апгрейда БД и других операций планового обслуживания в онлайне без простоя.
После успешного завершения предыдущего этапа мы нашли mission critical схему данных, которая в Oracle нам сильно мешала, и стали думать над ее переносом в CockroachDB. Подчеркну, что основной причиной для миграции этой БД из Oracle в CockroachDB была отказоустойчивость.
Для миграции необходимо было решить следующие вопросы:
- проверить синтаксис SQL-запросов на соответствие ANSI;
- вынести хранимый код из PL/SQL в сервисы на Java/Kotlin;
- правильно конвертировать схему данных;
- выбрать подходящий способ переноса данных с учетом требований по доступности системы.
Конечно же, в наших SQL-запросах нашлись не входящие в ANSI стандарт конструкции вида "... and rownum <= X»
— и их пришлось переписать. Для подготовки работы бизнес-логики с разными БД у нас уже были запланированы отдельные реализации Java-классов, поэтому система могла спокойно работать как с Oracle, так и с CockroachDB, в зависимости от текущих настроек, поэтому объем работы на данном этапе был незначительный. Из-за характера доступа к данным у нас получилось провернуть перенос данных без остановки, переключив приложение в read-only режим на время синхронизации данных в обеих БД.
Отказаться от хранимого кода тоже пришлось, однако триггеры, заполняющие столбцы стандартными значениями из последовательностей, оказалось возможным перенести на аналогичную конструкцию в синтаксисе CockroachDB, об этом ниже.
Для конвертации схемы данных из Oracle в CockroachDB я выбрал утилиту с открытым исходным кодом Ora2Pg, до этого она была неоднократно проверена в задачах миграции с Oracle на PostgreSQL.
Немножко деталей про использование ora2pg
Мы мигрировали БД в очень важной системе, но позволяющей в тех.окно остановить запись в базу на полчаса, провести работы по переносу данных, после чего просто переключить строку соединения к БД и включить режим добавления данных обратно.
Мы проделали следующие шаги:
1. Инсталлировали Perl, DBI, DBD: Oracle и ora2pg, каждый по соответствующей документации.
2. Создали четыре файла конфигурации для ora2pg.
Помимо параметров коннекта и конвертируемой схемы, в каждом из них были установлены параметры
DISABLE_SEQUENCE = 1
DROP_FKEY = 0
Эти файлы конфигурации отличались параметром TYPE
, установленном соответственно в
SEQUENCE
TABLE
VIEW
COPY
3. Запуск утилиты ora2pg с этими параметрами сгенерировал 4 sql-файла.
После этого определения внешних ключей были перенесены в 5-й файл, с нюансом того, что CockroachDB пока что не поддерживает инструкцию DEFERRABLE
.
grep -v -i "FOREIGN KEY" table.sql > table1.sql
grep -i FOREIGN table.sql| sed -e 's/NOT DEFERRABLE INITIALLY IMMEDIATE//g' > fk.sql
mv table1.sql table.sql
4. Далее в определения таблиц мы внесли изменения, аналогичные триггерам для заполнения полей из sequence. Это достигается использованием синтаксиса »… default nextval ('sequence')», см. ниже.
Еще одной проблемой оказалось отсутствие прямых аналогов функциональных индексов. Нужно было сначала создать вычисляемый столбец по требуемой функции и индексировать уже его.
С учетом вышесказанного про sequence, синтаксис создания таблицы выглядит примерно так:
CREATE TABLE some_table (
id bigint NOT NULL default nextval('some_sequence'),
value varchar(300) NOT NULL,
value_lower VARCHAR(300) NULL AS (lower(value)) STORED
)
После этого вычисляемый столбец можно индексировать:
CREATE UNIQUE INDEX some_uk2 ON some_table (id, value_lower);
5. Пришлось убрать из файлов инструкции set search_path и обработчики ошибок.
Далее я просто применил к базе CockroachDB файлы sequence.sql, table.sql и view.sql и получил требуемую структуру данных.
Следующим шагом была раздача доступов к таблицам для учетных записей приложения.
6. Заполнение таблиц CockroachDB большим количеством данных оказалось не самой тривиальной задачей, и быстрее всего оказалась команда PostgreSQL вида COPY
. Для ее использования пришлось дополнительно инсталлировать клиентский драйвер psql, генерация файлов для команды COPY
также производилась утилитой ora2pg.
Можно было заморочиться с использованием команды IMPORT из csv-файла, однако по скорости и качеству в подходе с ora2pg лично меня всё устраивало, и csv-подход опробован не был. Вариант с генерацией команд INSERT оказался провальным, в какой-то момент после массовой вставки трёх тысяч строк этот процесс зависал.
7.После этого из файла fk.sql были созданы внешние ключи. Это последний технический шаг в миграции БД.
8. Провели тест работоспособности приложения с новой базой.
9. После успешного теста мы просто заменили JDBC-строки подключения к БД на использование CockroachDB и включили режим read/write.
10. Сценарий обратной миграции данных из CockroachDB в Oracle через генерацию INSERT был также отработан на 100%. Обычно такие сценарии не то что не тестируют, а даже не рассматривают, но наша система слишком важна для бизнеса, и эти риски нам нужно было отработать.
Суммарно исследование методов миграции и подготовка к работам заняли неделю, изменения кода Java/Kotlin — два спринта одного разработчика, сами работы были полностью проведены от начала до конца за час. Объем перенесенных данных составлял порядка 10 млн строк, генерация скрипта переноса данных заняла 2 минуты, а вставлял этот скрипт данные в CockroachDB примерно 3 минуты.
Если бы мы не укладывались в наше получасовое тех.окно в режиме read-only, то, скорее всего, мы бы использовали Oracle GoldenGate в связке с Kafka для промежуточной синхронизации данных между Oracle и CockroachDB.
Что в итоге
Прошло уже три месяца с момента миграции, полет нормальный, простоев система не испытывала. Мы планируем расширять этот опыт и добавить возможность разработчикам микросервисов разворачивать кластера CockroachDB в k8s. Мигрировать legacy приложения на CockroachDB мало оправдано с точки зрения трудозатрат. Замена СУБД — наиболее сложная задача рефакторинга, и лучше заранее позаботиться о совместимости ваших приложений с самыми перспективными разработками в этой области.