Импорт структуры таблиц и данных из источников JDBC

Введение

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

Импорт данных осуществляется в следующем порядке:

  1. Утилита импорта подключается к БД-источнику, и определяет состав таблиц и SQL-запросов для импорта.
  2. Производится формирование структуры целевых таблиц YDB, которые опционально могут быть сохранены в виде YQL-скрипта.
  3. Над БД-получателем производится проверка наличия там целевых таблиц, с созданием недостающих таблиц и с опциональным пересозданием уже существующих таблиц.
  4. Осуществляется импорт данных путём чтения из БД-источника с помощью SQL-запроса и вставки в БД-получатель YDB с помощью механизма Bulk Upsert.

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

Установка утилиты импорта

Для работы утилиты импорта требуется JDK 8 или более поздней версии, исполняемый файл java должен быть доступен в путях поиска программ. Сборка и тестирование утилиты выполнялись с использованием OpenJDK 8.

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

В состав дистрибутива включены примеры файлов настроек, пример скрипта запуска и набор архивов lib/*.jar, содержащих собственно исполняемый код утилиты и её зависимости, включая YDB Java SDK.

Перед запуском утилиты импорта драйверы JDBC для исходных баз данных следует разместить в виде файлов *.jar в подкаталоге lib каталога инсталляции утилиты.

Порядок использования утилиты импорта

При запуске утилита считывает настройки из файла в формате XML, указанного в качестве аргумента командной строки. Имя файла настроек передается в командной строке при запуске утилиты импорта. В настройках указывается:

  • тип БД-источника и параметры подключения к источнику;
  • параметры подключения к БД-получателю YDB;
  • (опционально) имя файла для сохранения YQL скрипта со структурой таблиц для YDB;
  • правила отбора таблиц для импорта из БД-источника;
  • перечень явно указанных SQL запросов на выборку данных из БД-источника (может использоваться для импорта результата запроса вместо конкретных таблиц);
  • правила формирования имён импортируемых таблиц в БД-получателе;
  • степень параллелизма операций (размер пула рабочих потоков, количество соединений с источником и получателем).

Файл настроек необходимо подготовить перед запуском утилиты на основе представленных примеров.

Пример команды запуска утилиты импорта приведён в файле ydb-importer.sh, который можно использовать для запуска, как показано ниже:

./ydb-importer.sh my-import-config.xml

Особенности и ограничения импорта

На текущий момент протестирована работа утилиты импорта со следующими видами источников данных:

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

Импорт вторичных индексов не осуществляется.

Некоторые типы данных и виды табличных структур не поддерживаются, включая:

  • вложенные таблицы Oracle Database,
  • пространственные типы данных Microsoft SQL Server,
  • все объектные типы данных Informix.

Особенности импорта таблиц без первичного ключа

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

В случае отсутствия в исходной таблице явно определённого первичного ключа состав его колонок можно определить явным образом в настройках импорта соответствующей таблицы в виде элементов key-column раздела table-ref (см. далее пример в разделе с описанием формата файла настроек).

Если первичный ключ явно не указан в настройках, и отсутствует в определении исходной таблицы в БД-источнике, то утилита импорта автоматически добавляет в целевую таблицу колонку ydb_synth_key и создаёт первичный ключ на её основе. Значения колонки синтетического ключа вычисляются как хеш-код по алгоритму "SHA-256" над всеми значениями всех колонок строки таблицы, за исключением колонок типа BLOB.

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

Особенности импорта больших объектов (BLOB, XML)

Для каждого поля BLOB на источнике создаётся дополнительная таблица YDB следующей структуры:

CREATE TABLE `blob_table`(
    `id` Int64,
    `pos` Int32,
    `val` String,
    PRIMARY KEY(`id`, `pos`)
)

Формат имени дополнительной таблицы определяется настройкой table-options / blob-name-format.

Данные из исходного BLOB-поля сохраняются в виде последовательности записей созданной дополнительной таблицы.
Для каждого исходного BLOB-значения генерируется уникальное число, хранимое в поле id.
Ссылка на значение сохраняется в основной таблице в поле, имя которого соответствует имени исходного BLOB-поля.
Каждая запись хранит не более 64 Кбайт данных в поле val, последовательность блоков данных определяется значением поля pos.

Для PostgreSQL работа с данными BLOB в варианте EXTENSION lo может требовать явного предоставления специальных полномочий для учётной записи, используемой для подключения к БД-источнику.
Альтернативный вариант - установка режима совместимости на уровне базы данных PostgreSQL:

ALTER DATABASE dbname SET lo_compat_privileges TO on;

Формат файла настроек

Примеры файлов настроек с комментариями:

Пример и описание формата файла настроек приведены ниже.

<?xml version="1.0" encoding="UTF-8"?>
<ydb-importer>
    <workers>
        <!-- Количество рабочих потоков (целое число от 1 и выше).
             Также соответствует максимальному устанавливаемому количеству соединений с источником
             и максимальному количеству сессий с получателем.
         -->
        <pool size="4"/>
    </workers>
    <!-- Параметры подключения к БД-источнику.
         type - обязательный атрибут, влияющий на логику взаимодействия с источником
      -->
    <source type="generic|postgresql|mysql|oracle|mssql|db2|informix">
        <!-- Имя основного класса драйвера JDBC. Типичные значения:
              org.postgresql.Driver
              com.mysql.cj.jdbc.Driver
              org.mariadb.jdbc.Driver
              oracle.jdbc.driver.OracleDriver
              com.microsoft.sqlserver.jdbc.SQLServerDriver
              com.ibm.db2.jcc.DB2Driver
              com.informix.jdbc.IfxDriver
        -->
        <jdbc-class>driver-class-name</jdbc-class>
        <!-- URL JDBC для подключения к источнику. Примеры значений:
              jdbc:postgresql://hostname:5432/dbname
              jdbc:mysql://hostname:3306/dbname
              jdbc:mariadb://hostname:3306/dbname
              jdbc:oracle:thin:@//hostname:1521/serviceName
              jdbc:sqlserver://localhost;encrypt=true;trustServerCertificate=true;database=AdventureWorks2022;
              jdbc:db2://localhost:50000/SAMPLE
              jdbc:informix-sqli://localhost:9088/stores_demo:INFORMIXSERVER=informix
        -->
        <jdbc-url>jdbc-url</jdbc-url>
        <username>username</username>
        <password>password</password>
    </source>
    <!-- Параметры подключения к БД-получателю. -->
    <target type="ydb">
        <!-- Выгрузить скрипт создания таблиц для YDB в указанный файл. 
             Может использоваться в том числе при отсутствии указания
             connection-string для генерации схемы без фактического создания
             таблиц.  -->
        <script-file>sample-database.yql.tmp</script-file>
        <!-- Строка подключения: protocol + endpoint + database. Примеры значений:
            grpcs://ydb.serverless.yandexcloud.net:2135?database=/ru-central1/b1gfvslmokutuvt2g019/etn63999hrinbapmef6g
            grpcs://localhost:2135?database=/local
            grpc://localhost:2136?database=/Root/testdb
         -->
        <connection-string>ydb-connection-string</connection-string>
        <!-- Режим аутентификации: 
            ENV      использование переменных окружения для настройки аутентификации
            NONE     анонимное подключение к БД - не для продуктивных систем
            STATIC   аутентификация по логину и паролю
            SAKEY    Аутентификация по ключу сервисного аккаунта (управляемый сервис YDB)
            METADATA Аутентификация по метаданным виртуальной машины облака (управляемый сервис YDB)
        -->
        <auth-mode>ENV</auth-mode>
        <!-- 
            В режиме ENV данные аутентификации необходимо установить в переменных окружения,
            как описано в документации: https://ydb.tech/ru/docs/reference/ydb-sdk/auth#env
            Если аутентификация по ключу сервисного аккаунта (явно либо через указание переменной
            окружения YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS), то файл ключа надо
            генерировать как написано здесь:
            https://cloud.yandex.ru/docs/iam/operations/authorized-key/create
        -->
        <!-- Логин и пароль для auth-mode: STATIC -->
        <static-login>username</static-login>
        <static-password>password</static-password>
        <!-- Удалять ли уже существующие таблицы с теми же именами, что предполагается заливать -->
        <replace-existing>false</replace-existing>
        <!-- Заливать ли данные в таблицы после их создания или пересоздания -->
        <load-data>true</load-data>
        <!-- Максимальная порция заливки обычных данных, в строках
             (используется при записи данных в основную таблицу) -->
        <max-batch-rows>1000</max-batch-rows>
        <!-- Максимальная порция заливки BLOB-данных, в строках
             (используется при записи данных в дополнительные таблицы для BLOB-полей) -->
        <max-blob-rows>200</max-blob-rows>
    </target>
    <!-- Настройки преобразования структуры исходных таблиц.
         Устанавливаются централизованно с присвоением имени,
         затем используются для конкретной группы отбираемых таблиц.   -->
    <table-options name="default">
        <!--  Возможные значения case-mode: ASIS (по умолчанию), LOWER, UPPER.
              Влияет на регистр подставляемых в шаблоны имён.  -->
        <case-mode>ASIS</case-mode>
        <!-- Шаблон полного имени таблицы, включая каталог размещения.
             Используются подстановочные имена ${schema} и ${table},
             соответствующие схеме и имени копируемой исходной таблицы. -->
        <table-name-format>oraimp1/${schema}/${table}</table-name-format>
        <!-- Шаблон полного имени таблицы, включая каталог размещения.
             Используются подстановочные имена ${schema}, ${table} и ${field},
             соответствующие схеме, имени копируемой исходной таблицы и имени
             поля типа BLOB. -->
        <blob-name-format>oraimp1/${schema}/${table}_${field}</blob-name-format>
        <!-- Возможные значения: DATE (по умолчанию), INT, STR.
             Вариант DATE не позволяет сохранить даты ранее 1 января 1970 года,
                 при попытке импорта неподдерживаемых значений будут возникать ошибки
             Вариант INT хранит дату как 32-битное целое в формате ГГГГММДД,
                 а для меток времени сохраняет количество миллисекунд как 64-битное целое
             Вариант STR хранит дату как строку в формате ГГГГ-ММ-ДД,
                 а для меток времени в формате "ГГГГ-ММ-ДД чч:мм:сс.xxx"
         -->
        <conv-date>INT</conv-date>
        <conv-timestamp>STR</conv-timestamp>
        <!-- Если указано значение true, колонки таблиц неподдерживаемых типов пропускаются
             с выводов в лог соответствующего предупреждения. В противном случае генерируется
             ошибка импорта, и соответствующая таблица пропускается целиком. -->
        <skip-unknown-types>true</skip-unknown-types>
    </table-options>
    <!-- Фильтр для отбора копируемых таблиц с источника -->
    <table-map options="default">
        <!-- Включаемые схемы -->
        <include-schemas regexp="true">.*</include-schemas>
        <!-- Исключаемые схемы -->
        <exclude-schemas>SOMESCHEMA</exclude-schemas>
        <!-- Также могут присутствовать include-tables и exclude-tables, 
             для явного указания фильтра по именам таблиц
             и/или регулярным выражениям над именами таблиц. -->
    </table-map>
    <!-- Конкретный запрос или указание ссылки на конкретную таблицу -->
    <table-ref options="default">
        <schema-name>ora$sys</schema-name>
        <table-name>all_tables</table-name>
        <!-- Если указан запрос, он выполняется как написано -->
        <query-text>SELECT * FROM all_tables</query-text>
        <!-- Для запроса желательно явно определить ключевые колонки.  -->
        <key-column>OWNER</key-column>
        <key-column>TABLE_NAME</key-column>
    </table-ref>
</ydb-importer>
Следующая