Поставил 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. В моей баз циклические ссылки были и указанный метод помог.

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

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

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


  1. sshikov
    05.11.2021 11:05

    Поставил Linux? Напиши об этом статью.

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


    1. altmf Автор
      05.11.2021 11:24

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


      1. sshikov
        05.11.2021 11:36

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


        1. altmf Автор
          05.11.2021 11:54

          В контексте данного инструмента авторы описывают последний шаг: LOADS constraints that it had backed up (Mock-data can fail at this stage if its not able to fix the constraint violations). При этом в логе отражается, все ли ограничения были восстановлены.
          В целом согласен с вами, что без ссылочной целостности генерация данных в общем-то ценности никакой не несет, даже если рассматривать кейс подобный моему, когда надо было быстро нагенерить данные для проверки работоспособности пользовательского интерфейса.


          1. sshikov
            05.11.2021 12:08

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


            1. altmf Автор
              05.11.2021 12:22

              >Ну, не так уж чтоб совсем никакой
              Если в базе не было внешних ключей (или других ограничений), то, наверное ценность есть) А вот если ключи были, дропнулись при генерации и не восстановились, то это скорее вред)
              С ссылочной целостностью интересен скорее аспект, о котором вы выше говорили. То есть не просто соблюсти ограничения (этого ведь и перебором можно достичь), а попытаться смоделировать связи, распределение которых близко к реальному.


  1. KMU
    06.11.2021 20:14
    -2

    Когда в статье главным драйвом в решении проблемы была Лень, это красный флаг, не читать дальше.


    1. Himura
      07.11.2021 09:06
      +2

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


  1. Himura
    07.11.2021 09:16
    +1

    По предмету статьи всё супер, было очень полезно узнать, что такой инструмент существует. Да и вообще, знать инструменты очень важно, спасибо за статью.

    У меня лишь одно замечание: зачем трогать хост, есть можно всё в контейнерах сделать? Если база всё равно локальная, то почему бы её не поднять в контейнере? Можно написать docker-compose.yml файл с двумя сервисами: базой и этой прогой на go. Они автоматически объединятся в виртуальную сеть и будут доступны друг другу по хостнэйму, совпадающем у с именем контейнера. Таким образом, генерация новой наполненной базы сведётся к выполнению `docker-compose up -d`. И не нужно будет сеть хоста в докер кидать, ибо это не очень хорошо так делать. Кстати, помимо использования сети хоста, конкретно у докера есть ещё пролом с docker.host.internal, чтобы получать доступ к локалхостовым серверам


    1. altmf Автор
      07.11.2021 16:10

      Спасибо за отзыв и дельное замечание про докер. Добавил в статью ссылку на ваш комментарий.