Меня зовут Леонид Блынский и я администратор баз данных в Лиге Цифровой Экономики. В этой небольшой статье расскажу, как я делаю резервное копирование кластера ClickHouse размером 20 ТБ.

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

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

Для стенда разработки мы сделали возможность создания локальных бэкапов. В файле настроек /etc/clickhouse-server/config.d/backup_disk.xml

потребовалось прописать разрешенный путь для бэкапа:

<clickhouse>
	<storage_configuration>
    	<disks>
        	<backups>
            	<type>local</type>
                <path>/var/lib/clickhouse/backups</path>
        	</backups>
    	</disks>
	</storage_configuration>
	<backups>
        <allowed_disk>backups</allowed_disk>
    	<allowed_path>/var/lib/clickhouse/backups/</allowed_path>
	</backups>
</clickhouse>

Разработчики и аналитики теперь могли сохранить данные перед какими-либо необратимыми действиями:

backup table main.dim_account TO Disk('backups', ' main.dim_account.zip')

Ключевое слово Disk следует писать с большой буквы:)

Или так, для бэкапа всей БД:

backup database main TO Disk('backups', ' main_dev.zip')

Мы проверили — восстановиться просто, все работает отлично:

restore table main.dim_account AS main.dim_account FROM Disk('backups', ' main.dim_account.zip')

Далее мы настроили хранилище S3 для хранения бэкапа нашей БД. Команда для запуска стала выглядеть так:

backup table main.dim_account to S3('http://10.10.10.10:9000/clickhouse/ main.dim_account.zip','bkp_user','bkp_pwd');

Далее мы попробовали вручную забэкапить все таблицы базы. Все шло хорошо, пока не пришла очередь самой большой таблицы размером около 3 ТБ. В какой-то момент стала выпадать ошибка:

<Fatal> BaseDaemon: Received signal Segmentation fault (11)
<Fatal> BaseDaemon: Address: 0x7f0ba8a00960 Access: write. Address not mapped to object.
<Fatal> BaseDaemon: Stack trace: 0x7f7ab25479e6 ………………….

Ошибка возникала случайным образом: таблица то бэкапилась, то нет. Поиск на форумах ни к чему не привел. 

Так как с другими таблицами проблем не возникало, мы сделали вывод, что проблема именно в размере, потому и надо что-то придумать, чтобы уменьшить размер резервной копии в одной команде.

Оказалось, что таблица секционирована, а ClickHouse замечательно умеет делать резервные копии отдельных партиций.

В нашем кластере 6 узлов и несколько конфигураций баз данных: база allrp (имеет реплики на всех шести узлах), main (3 шарда, и каждый шард имеет две реплики). 

Для бэкапа allrp достаточно снять копию только с одного узла. Для резервного копирования main требуется снять копию c трех узлов. То есть надо зайти на все хосты, где располагаются шарды, сгенерировать  и выполнить команды бэкапа.

Немного вспомнив язык SQL, мы явили на свет скрипт генерации команд бэкапа по партициям:

select distinct 'backup table ' || database || '.' || table || '  partition ''' || partition || ''' to S3(''http://10.10.10.10:9000/clickhouse/' || formatDateTime(now(),
  	'%d-%m-%Y')|| '/main.' || table || '.' || partition || '-shard-' || q2.shard || '.zip'',''bkp_user'',''bkp_pwd'');' as backup_command
FROM
  	system.parts,
  	(
  	select
         	toString(shard_num) as shard
  	from
         	system.clusters
  	where
         	host_address = '127.0.0.1'
         	and cluster = 'main') q2
where active
  	and database = 'main'
  	and table in ('fct_bigtable')
group by
  	database,
  	table,
  	partition,s
  	q2.shard

Все скрипты бэкапа по партициям отрабатывают успешно, таблицу можно восстановить.

Так как остальные таблицы не вызывали проблем, то их бэкапим уже целиком:

select * from
  	(
  	select
         	distinct 'backup table ' || database || '.' || table || '  to S3(''http://10.10.10.10:9000/clickhouse/' || formatDateTime(now(),
         	'%d-%m-%Y')|| '/main.' || table || '-shard-' || q2.shard || '.zip'',''bkp_user'',''bkp_pwd'');'
  	from
         	system.parts,
         	(
         	select
               	toString(shard_num) as shard
         	from
               	system.clusters
         	where
               	host_address = '127.0.0.1'
               	and cluster = 'main') q2
  	where
         	active
         	and database = 'main'
         	and table not like 'fct_bigtable%'
) q1

Данные SQL надо выполнить на каждом узле с уникальными шардами (в нашем примере на трех) и на этих же узлах запустить сгенерированные команды. Для сохранения результата запроса в файл можно последней строчкой дописать:

INTO OUTFILE '/var/lib/clickhouse/backups/backup.sql' FORMAT TabSeparatedRaw;

Для бэкапа базы allrp генерируем команды, которые надо запустить на любом узле:

select
  	distinct 'backup table ' || database || '.' || table || '  to S3(''http://10.10.10.10:9000/clickhouse/' || formatDateTime(now(),
  	'%d-%m-%Y')|| '/allrp.' || table || '.zip'',''bkp_user'',''bkp_pwd'');'
from
  	system.parts
where
  	active
  	and database = 'allrp';

Наблюдать за работой процесса резервного копирования и его статусом можно так:

	select * from system.backups_distributed

После того как нам удалось в ручном режиме проводить полноценный бэкап базы данных, начался этап автоматизации. Мы привлекли разработчика Python — он за пару дней реализовал задачи в Airflow, которые генерируют команды бэкапа, запускают их, отслеживают выполнение и в случае аварии отдельных команд ставят их в очередь снова. То есть бэкап стало возможным пускать по расписание или «по нажатию кнопки» в Airflow.

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

Как результат — вполне стабильный инструмент с интерфейсом, визуализацией работы и мониторингом, который можно полностью контролировать.

Ранее мы рассказывали, как работать со словарями данных и оптимизировать запросы и распределенными таблицами в ClickHouse. Возможно, это тоже будет полезно.