Быстрый экспорт данных с джойнами из MySQL в Clickhouse

Подготовка и импорт данных в Clickhouse достаточно обширная тема. В данной заметке приведу рецепт быстрого экспорта данных с джойнами из MySQL в CSV с последующим импортом этих CSV в Clickhouse. В MySQL есть очень полезная директива: INTO OUTFILE, которая в контексте SELECT запросов сохраняет данные в файл на сервере, а не отдаёт клиенту. Важно, что выполнить SELECT с опцией INTO OUTFILE сможет только пользователь имеющий привилегию FILE.

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

Конечно, делать это ручками ещё более неприемлемо, поэтому я подготовил такой скрипт:

<?php
 $host = 'mariadb';
 $db = 'mydb';
 $user = 'appuser';
 $pass = 'passforapp';
 $charset = 'utf8';
$dsn = "mysql:host={$host};dbname={$db};charset={$charset}";
$pdo = new PDO($dsn, $user, $pass);
 $table = 'mytable';
 $file_prefix = 'old';
 $pk = 'id';
 $limit = 100000;
 $stmt = $pdo->query("SELECT MIN({$pk}) min, MAX({$pk}) max FROM {$table};");
 $extr = $stmt->fetchAll(PDO::FETCH_ASSOC)[0];
 $range = range($extr['min'], $extr['max'], $limit);
 $k = 0;
 foreach($range as $i => $v) {
 $n = $v + $limit - 1;
 $k++;
$sql = "SELECT ....
FROM {$table}
JOIN ...
WHERE log_id BETWEEN {$v} AND {$n}
INTO OUTFILE '/tmp/{$file_prefix}-{$k}.csv'
FIELDS TERMINATED BY ','
ENCLOSED BY '\"'
LINES TERMINATED BY '\n';";
echo $sql . "\n\n";
 }

В скрипте нужно указать данные для доступа к базе на чтение, а также название таблицы для экспорта $table, префикс файла с дампом $file_prefix, название поля с первичным ключём таблицы $pk, количество строк экспортируемых за раз: $limit. Данная реализация использует значения первичного ключа для разбиения запроса на небольшие пачки, это гораздо производительнее, чем делать LIMIT и OFFSET.

Запускаем скрипт для генерации SQL запросов для экспорта:

php dump.php > dump.sql

Выполняем запросы из файла на сервере MySQL:

cat dump.sql | mysql -u root -D mydb -p

На всякий случай проверяем сколько строк было экспортировано:

cat /tmp/data* | wc -l

Перед экспортом данных в Clickhouse смотрим сколько строк в таблице имеется сейчас:

clickhouse client -q "select count(*) from mydb.mytable;"

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

cat /tmp/data* | clickhouse client --query="INSERT INTO myapp.mytable FORMAT CSV

Снова проверяем количество записей в таблице:

clickhouse client -q "select count(*) from mydb.mytable;"

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