Скрипт на bash для копирования данных с одного кластера Clickhouse на другой
В данном примере будет рассмотрен конкретный случай копирования данных с одного кластера Clickhouse на другой. С некоторыми модификациями, вы можете адаптировать сценарий под себя.
Мы будем выполнять задачу с помощью скрипта bash.
Работа скрипта и требования к запуску
Скрипт для импорта данных
Описание строк кода
Запуск скрипта
Как определить окончание процесса импорта
Дополнительные материалы
Как будет работать скрипт
Запускать данных сценарий необходимо на одной из нод кластера, куда будут импортироваться данные. Он будет выполнять удаленное подключения к источнику данных, читать список таблиц и создавать их на целевом кластере, а после переносить данные с помощью запросов INSERT + SELECT * FROM remote().
В самом скрипте мы не будем хранить конкретных данных для подключения и указания источника. Все это будем передавать с аргументами.
Условия:
- Использование таблиц типа Distributed. Именно они будет использоваться для перекачки данных.
- В момент работы скрипта необходимо, чтобы с данными не происходило никаких изменений. Это важно для сохранения целостности информации.
- Оба кластера должны быть настроены одинаково. А именно, иметь одинаковые названия, иметь то же число шард и реплик. Это важно, так как подразумевается использование таблиц типа Distributed, в которых хранятся настройки размещения данных между участниками кластера. С модификацией данных параметров вы можете изменить под себя перенос данных.
Предварительно необходимо:
- Выбрать два сервера. Один на стороне кластера источника данных, второй — целевого, куда будем отправлять данные. Назовем их сервер-источник и сервер-цель.
- С сервера-цели необходимо обеспечить сетевое подключение для клиента Clickhouse к серверу-источнику. При необходимости, создать учетную запись с соответствующими правами. Ссылки на дополнительные материалы приведены ниже.
- На самом сервере-целе, где будет запускаться скрипт, настроить возможность беспарольного подключения к локальному серверу Clickhouse (поведение по умолчанию для системной записи root).
Ниже данные вопросы рассмотрим подробнее.
Пример скрипта
Напомним, что скрипт должен запускаться на сервере, куда будут импортироваться данные. Поэтому хорошей идеей будет создать сам скрипт на нем.
Создадим каталог, где будем хранить скрипт:
mkdir /scripts
Создадим bash-сценарий для копирования данных Clickhiuse:
vi /scripts/clickhouse-import-data.sh
- #!/bin/bash
- PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
- while getopts :d::h::p::u::c: arg
- do
- case ${arg} in
- d)
- db="${OPTARG}"
- ;;
- h)
- db_host="${OPTARG}"
- ;;
- p)
- db_port="${OPTARG}"
- ;;
- u)
- db_user="${OPTARG}"
- ;;
- c)
- db_cluster="${OPTARG}"
- ;;
- :)
- echo -e "\033[0;31mОшибка: для опции -${OPTARG} требуется значение.\033[0m"
- exit 1
- ;;
- *) exit 1
- esac
- done
- if [ -z "${db}" ] || [ -z "${db_host}" ] || [ -z "${db_user}" ] || [ -z "${db_cluster}" ]
- then
- echo -e "\033[0;31mОшибка. Указаны не все обязательные аргументы.\033[0m"
- echo "Синтаксис: $0 -d 'Имя базы данных' -h 'Хост источника' -u 'Имя учетной записи для подключения к хосту clickhouse' -c 'Имя кластера clickhouse' [ -p 'Порт для подключения к хосту clickhouse' ]"
- exit 1
- fi
- db_port=${db_port:-9000}
- echo -e "\nВведите пароль для Clickhouse-пользователя ${db_user}:"
- read -s db_pass
- remote_connect_args="-u${db_user} --password ${db_pass} -h${db_host} --port ${db_port}"
- clickhouse-client -q "SELECT 1" &>/dev/null
- if [ $? -ne 0 ]
- then
- echo -e "\033[0;31mОшибка. Не удалось подключиться к локальному серверу баз данных.\033[0m"
- echo "Проверьте подключение командой:"
- echo "clickhouse-client -q 'SELECT 1'"
- exit 1
- fi
- clickhouse-client ${remote_connect_args} -q "SELECT 1" &>/dev/null
- if [ $? -ne 0 ]
- then
- echo -e "\n\033[0;31mОшибка. Не удалось подключиться к серверу-источнику. Проверьте введенные аргументы\033[0m"
- echo "Используемые данные:"
- echo "Хост: ${db_host}"
- echo "Порт: ${db_port}"
- echo "Пользователь: ${db_user}"
- echo "Пароль: ${db_pass}"
- exit 1
- fi
- check_db=`clickhouse-client ${remote_connect_args} -q "SHOW databases LIKE '${db}'"`
- if [[ "${check_db}" != "${db}" ]]
- then
- echo -e "\n\033[0;31mОшибка. Базы ${db} не существует на удаленном хосте\033[0m"
- exit 1
- fi
- clickhouse-client -q "CREATE DATABASE IF NOT EXISTS ${db} ON CLUSTER '${db_cluster}'" &>/dev/null
- for table in `clickhouse-client ${remote_connect_args} -nmq "USE ${db} ; SHOW tables;"`
- do
- create_table_query=`clickhouse-client ${remote_connect_args} -q "SHOW CREATE TABLE ${db}.${table}" --format=TSVRaw | sed -e "s/CREATE TABLE ${db}.${table}/CREATE TABLE IF NOT EXISTS ${db}.${table} ON CLUSTER '${db_cluster}'/"`
- echo -e "\nВосстановление структуры таблицы ${table}"
- clickhouse-client -q "${create_table_query}" &>/dev/null
- done
- for table in `clickhouse-client -q "SELECT name FROM system.tables WHERE database='${db}' AND engine='Distributed'"`
- do
- table_count=`clickhouse-client -q "SELECT COUNT(*) FROM ${db}.${table}"`
- if [ "${table_count}" != "0" ]
- then
- echo -e "\n\033[0;31mВнимание!\033[0m Таблица ${table} содержит данные. Если желаете продолжить, введите ее имя:"
- read table_confirm
- if [ "${table_confirm}" != "${table}" ]
- then
- echo -e "Восстановление данных для таблицы ${table} прервано. Переходим к следующей."
- continue
- fi
- fi
- echo -e "\nВосстановление данных для таблицы ${table}"
- clickhouse-client -nmq "SET throw_on_max_partitions_per_insert_block = false; INSERT INTO ${db}.${table} SELECT * FROM remote('${db_host}:${db_port}', ${db}, ${table}, '${db_user}', '${db_pass}');"
- [[ $? -ne 0 ]] && exit 1
- echo "Данные для таблицы ${table} скопированы."
- done
Описание скрипта
Стоит обратить внимание на следующие пункты:
4 - 28 | Указываем, как нужно обработать аргументы, передаваемые скрипту. Подразумевается использование следующих параметров: d — имя базы данных, к которой мы подключимся для импорта информации. h — адрес сервера-источника, куда нужно подключиться. p — порт, на котором слушает clickhouse на сервере-источнике. Если не указать, будет использоваться порт по умолчанию 9000. u — учетная запись пользователя, под которым мы будем подключаться к серверу-источнику. c — имя кластера clickhouse, для которого будут выполняться действия. Данные аргументы принимают значения, которые используются для объявления переменных. |
30 - 35 | Выполняем наличие обязательных аргументов. Если один из них не был передан, скрипт вернет ошибку. |
39 - 40 | Запрашиваем пароль у пользователя в момент запуска скрипта. Это пароль для подключения к clickhouse сервера-источника под пользователем, которого мы передаем с аргументом -u. |
42 | Создаем переменную с указанием параметров подключения к clickhouse-server. Она нам нужна для удобства. |
44 - 51 | Делаем проверку подключения к локальному серверу баз данных clickhouse. Если подключение не выполнено, завершаем работу скрипта. |
53 - 63 | Пытаемся подключиться к серверу-источнику clickhouse. Выдаем ошибку и завершаем работу, если данное подключение не вернуло корректный код. |
65 - 70 | Проверяем, есть ли база данных, которую мы передали аргументом -d на сервере-источнике. |
72 | На сервере-целе создаем базу данных, если ее нет. |
74 - 79 | Создаем таблицы на целевом сервере. Для этого мы подключаемся к серверу-источнику и получаем список таблиц, после в цикле для каждой таблицы получаем запрос на ее создание и немного его модифицируем, добавив IF NOT EXISTS и ON CLUSTER, чтобы создать таблицы не на конкретном хосте, а на всех нодах кластера. |
81 - 100 | Получаем список таблиц типа Distributed и копируем данные с сервера-источника с помощью запроса INSERT INTO + SELECT * FROM remote. |
83 | Обратите внимание, что в данной строке мы делаем проверку таблицы на сервере-целе. Если по каким-то причинам там есть данные, сценарий предложит пропустить импорт информации. |
Запуск скрипта
Прежде чем начать перенос, стоит убедиться в сетевой доступности серверов Clickhouse. На одной из нод кластера, куда мы будем импортировать данные вводим:
clickhouse-client -h db_host --port db_port -u db_user --ask-password
* где:
- db_host — адрес сервера, к которому мы будем подключаться. Это должен быть сервер-источник данных.
- db_port — порт подключения. По умолчанию 9000.
- db_user — имя пользователя, под которым мы будем подключаться к хосту db_host.
Система запроси пароль. Необходимо ввести секрет для пользователя db_user. Если подключение прошло успешно, можно переходить к переносу данных.
Разрешим запуск скрипта на выполнение:
chmod +x /scripts/clickhouse-import-data.sh
При запуске сценария нам нужно передать следующие аргументы:
- d — имя базы данных, к которой мы подключимся для импорта информации.
- h — адрес сервера-источника, куда нужно подключиться.
- p — порт, на котором слушает clickhouse на сервере-источнике. Если не указать, будет использоваться порт по умолчанию 9000.
- u — учетная запись пользователя, под которым мы будем подключаться к серверу-источнику.
- c — имя кластера clickhouse, для которого будут выполняться действия.
Например:
/scripts/clickhouse-import-data.sh -d portal_db -h 192.168.1.10 -u click_usr -c dm_cluster -p 9000
* в данном примере мы будем подключаться к базе portal_db на сервере 192.168.1.10 под пользователем click_usr. Имя кластера dm_cluster, а порт подключения 9000.
В консоли мы должны увидеть запрос пароля для подключения (пользователя click_usr, в нашем примере). Вводим его.
Должна начаться процедура копирования таблиц, а после — самих данных. В зависимости от объема, скрипт может работать долго.
Проверка импорта данных
Даже, завершение работы скрипта по переносу данных не является окончанием процесса импорта. Дело в том, что кластер заносит данные на keeper, после чего последний занимается перераспределением информации между его нодами. Чтобы понять, когда процесс завершился, можно сравнивать количество строк на целевом сервере и сервере-источнике.
Для удобства, предлагаю небольшой скрипт. Сохраним его по тому же пути, где и скрипт импорта:
vi /scripts/clickhouse-import-check.sh
- #!/bin/bash
- PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
- while getopts :d::h::p::u::c: arg
- do
- case ${arg} in
- d)
- db="${OPTARG}"
- ;;
- h)
- db_host="${OPTARG}"
- ;;
- p)
- db_port="${OPTARG}"
- ;;
- u)
- db_user="${OPTARG}"
- ;;
- :)
- echo -e "\033[0;31mОшибка: для опции -${OPTARG} требуется значение.\033[0m"
- exit 1
- ;;
- *) exit 1
- esac
- done
- for table in `clickhouse-client -q "SELECT name FROM system.tables WHERE database='${db}' AND engine='Distributed'"`
- do
- source_count=`clickhouse-client ${remote_connect_args} -q "SELECT COUNT(*) FROM ${db}.${table}"`
- target_count=`clickhouse-client -q "SELECT COUNT(*) FROM ${db}.${table}"`
- if [ ${source_count} -eq ${target_count} ]
- then
- color="\033[0;32m"
- else
- color="\033[0;31m"
- fi
- echo -e "\nПроверка таблицы ${table}:"
- echo -e "Строк в источнике: ${color}${source_count}\033[0m"
- echo -e "Строк в сервере назначения: ${color}${target_count}\033[0m"
- done
Также разрешим запуск скрипта на выполнение:
chmod +x /scripts/clickhouse-import-check.sh
При запуске сценария нам нужно передать аргументы:
- d — имя базы данных, к которой мы подключимся для импорта информации.
- h — адрес сервера-источника, куда нужно подключиться.
- p — порт, на котором слушает clickhouse на сервере-источнике. Если не указать, будет использоваться порт по умолчанию 9000.
- u — учетная запись пользователя, под которым мы будем подключаться к серверу-источнику.
В нашем примере это:
/scripts/clickhouse-import-check.sh -d portal_db -h 192.168.1.10 -u click_usr -p 9000
Потребуется ввести пароль пользователя для подключения к базе данных.
В результате мы получим информацию по количеству строк. Оно должно совпадать для новой и старой баз.
Важно отметить, что кластер Clickhouse помещает данные в очередь, и окончательное копирование данных в базу назначения выполняется не сразу. Таким образом, запустив проверку мы можем увидеть несоответствие количества строк для множества таблиц. Такая ситуация является допустимой. Необходимо раз в 10 или более минут делать повторные проверки. Миграция данных может считаться законченной, когда все таблицы будут совпадать по количеству строк.
Читайте также
Другие инструкции, которые могут оказаться полезными: