Поставил Linux? Напиши об этом статью. Нашел на Github интересный проект? Напиши об этом статью. Примерная такая логика привела к написанию этой статьи.

Недавно мне потребовалось наполнить базу данных из более чем 300 таблиц со сложной структурой и кучей внешних ключей тестовыми данными. Требования к наполнению минимальны, нужны просто таблицы с заполненными полями, семантика пока не важна. Сделать надо быстро, с минимальными усилиями и как можно ближе к уровню СУБД.

Итак, что мы имеем: база на Postgres, которую надо быстро, дешево и сердито наполнить данными, достаточными для того, чтобы проверить корректную работу пользовательского интерфейса. Интернет открывает перед нами просто прорву инструментов для генерации mock-объектов: DBeaver, Datanamic Data Generator, Mockaroo, Faker.js, в общем, тысячи их.

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

Проект называется mock-data, написан на Go, развивается с 2017 года. Звезд и контрибьюторов не много, гуглится плохо, зато Readme обещает, что достигнуть нужного эффекта можно будет вызовом буквально пары команд.

Заявлено, что mock-data работает с PostgreSQL и Greenplum Database. Разработчики предупреждают, что в случае наличия кастомных типов и ограничений ссылочной целостности на таблицах возможны ошибки. В моем случае проблемы были, но не критичные.

Mock-data может генерировать данные для всей базы, выбранной схемы или отдельной таблицы. Можно указать количество генерируемых записей, оно одинаково для всех таблиц и по умолчанию равно 10. Еще можно включить вывод отладочных сообщений и пропустить восстановление удаляемых ограничений ссылочной целостности.

Работа mock-data состоит из следующих шагов:

  • Разбираются аргументы командной строки, проверяется подключение к базе и наличие таблиц.

  • Создается и сохраняется в специальной директории бэкап всех ограничений ссылочной целостности.

  • Все ограничения ссылочной целостности удаляются.

  • Производится заполнение таблиц.

  • Ограничения ссылочной целостности восстанавливаются, производится проверка того, что сгенерированные данные удовлетворяют ограничениям целостности.

Что ж, начнем. Поскольку статья заявлена как туториал, постараюсь описат�� все максимально детально.

Разработчики предупреждают, что использовать mock-data на проде нельзя. Поэтому создаем локальную копию базы. Поскольку кое-какие данные у меня уже есть, то для создания локальной копии я воспользовался pg_dump с флагом --schema-only. При загрузке тестовых данных обратно это позволит избежать проблемы с дублированием первичных ключей.

Здесь стоит сказать, что настоятельная рекомендация не использовать mock-data на проде связана с тем, что, как было сказано выше, перед генерацией данных удаляются все ограничения ссылочной целостности. Конечно, потом они будут восстановлены, но каждое ограничение удаляется в отдельной транзакции и если что-то при удалении идет не так, то работа программы останавливается и вы остаетесь с базой, где часть ограничений удалена, а часть осталась. Хорошего в этом мало. Будьте готовы к тому, что базу (или отдельные схемы) придется удалять и создавать заново.

Локальная копия развернута. Пора заняться самим mock-data. Нам предлагается либо скачать бинарник, либо воспользоваться докер образом. Я выбрал докер.

Скачиваем образ:

docker pull ghcr.io/pivotal-gss/mock-data:latest

Создаем для него тэг:

docker image tag ghcr.io/pivotal-gss/mock-data mock

Cоздаем локальную директорию для временных файлов:

mkdir /tmp/mock

Когда я проделал эти действия, мои ладошки вспотели от предвкушения того, как моя база наполнится моками. Но не тут-то было, пришлось пройтись по кое-каким граблям. Спешу поделиться, по каким.

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

listen_addresses = '*'

Напомню, что соответствующие настройки надо делать и в pg_hba.conf.

Не забываем, что в таком случае нам надо сделать родительский хост доступным для контейнера (UPD: в комментариях подсказывают более изящные решения), то есть при запуске докера указать параметр --network="host".

Раз уж мы начали править postgresql.conf, то настоятельно рекомендую в качестве языка сообщений установить английский:

lc_messages = 'en_US.UTF-8'

Зачем это делать? А затем, что если оставить язык сообщений, например, русским, то в какой-то момент mock-data упадет с сообщением примерно следующего вида:

Encountered error when removing constraints for table Ошибка запроса: ERROR: отношение "Имя ограничения" не существует.

Что же происходит? Все просто. Как уже говорилось, перед созданием тестовых данных каскадно удаляются все ограничения ссылочной целостности. Поэтому в какой-то момент mock-data пытается удалить уже не существующие отношения. Если посмотреть в исходники, то видно, что разработчики так и задумали. Но если посмотреть внимательнее, то становится понятно, что игнорируются только те ошибки, текст которых содержит подстроку "does not exist". Логично, что если язык сообщений у вас русский, то сообщение будет несколько другим.

Также рекомендую проверить длину имени всех ограничений. Хотя Postgres позволяет задавать длину имени до 63 символов, в моем случае mock-data радостно упал на имени длиной 54 символа. Пришлось укоротить, благо, копия локальная и проблемным было всего одно имя.

Выполняем команду:

docker run --network="host" -v /tmp/mock:/home/mock mock schema -n ИМЯ_СХЕМЫ --uri postgres://ПОЛЬЗОВАТЕЛЬ:ПАРОЛЬ@ХОСТ:ПОРТ/БД

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

Заполнение данными началось
Заполнение данными началось

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

Успешное завершение
Успешное завершение

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

Пример данных в таблице
Пример данных в таблице

Конечно, данные не реалистичны, но для первичного тестирования вполне подойдут.

Чтобы залить полученные данные на условный прод (или удаленную базу данных) сделаем дамп локальной базы. Естественно, с флагом --data-only. Если вдруг в базе есть циклические ссылки или есть опасения в том правильно ли загрузятся данные, рекомендую в файле, полученном pg_dump, установить SET CONSTRAINTS ALL DEFERRED. И провести загрузку файла с флагом --single-transaction. В моей баз циклические ссылки были и указанный метод помог.

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

Будет здорово, если в комментариях вы поделитесь своим опытом создания моков на уровне СУБД.