Привет, Хабр! Меня зовут Евгений Абрамкин, я руководитель поддержки третьего уровня в направлении омниканальных решений Лиги Цифровой Экономики. Моя команда — последняя «инстанция» во флоу по решению инцидентов. Мы пишем доработки и фиксы, чтобы победить проблему клиента, а еще предоставляем оптимальную конфигурацию для системы, которая передана в эксплуатацию или требует масштабирования. Это может быть кластер Elasticsearch, балансировщики nginx или что поинтереснее — распределенная NoSQL СУБД Apache Cassandra.

В материале я расскажу про такую важную и необходимую функциональность Apache Cassandra, как Snapshots (они же снапшоты, они же снимки): как пользоваться, как восстанавливать и переносить данные. И совсем немного — про бэкап.

Я пропущу объяснение, что такое Apache Cassandra, и перейду сразу к использованию стандартной консольной утилиты, ориентируясь на то, что читатель уже верхнеуровнево знаком с Cassandra.

Что такое snapshot

Snapshot (далее по тексту «снимок») в Cassandra — это процесс создания копии данных из существующих SSTable файлов и записи их в отдельную директорию на диске. Это позволяет сохранить состояние данных в определенный момент времени и использовать эту копию для их восстановления в случае потери или повреждения, а также переноса из одного кластера Cassandra в другой. У вас должно быть достаточно свободного дискового пространства на узле кластера, чтобы можно было создавать снимки файлов данных. Их постоянное снятие может привести к тому, что диск будет со временем быстрее заполняться: снимок предотвращает удаление устаревших файлов данных. После того как snapshot сделан, вы можете переместить файлы резервных копий в другое место, если это необходимо, или удалить.

Снимок — это набор файлов, которые содержат данные из определенного кейспейса. Эти файлы хранятся в директории snapshot в формате SSTable. Создание снимка — относительно быстрый процесс, который не блокирует запись или чтение данных из кластера. Он может быть выполнен в любое время, и копия данных будет доступна для восстановления.

Важно отметить, что snapshot — не полное резервное копирование кластера Cassandra. Для этого необходимо использовать другие методы: инкрементное резервное копирование или репликацию данных на другой кластер.

Как создать snapshot в Cassandra

Для создания снимка в Cassandra используется стандартная команда nodetool snapshot.

Например, чтобы создать snapshot для кейспейса "phoenix", можно выполнить следующую команду в терминале:

nodetool snapshot phoenix

 [cassandra@cassandra-09 ~]$ nodetool snapshot phoenix
 Requested creating snapshot(s) for [phoenix] with snapshot name [1681564715891] and options {skipFlush=false}
 Snapshot directory: 1681564715891

Она создаст новый снимок для кейспейса "phoenix".

Проверить, что снимок создан и Cassandra может с ним работать, можно командой ниже:

nodetool listsnapshots

 [cassandra@cassandra-09 ~]$ nodetool listsnapshots | grep phoenix
 1681564715891                              phoenix   	tag_views                  	0 bytes	1.43 KiB
 1681564715891                              phoenix   	tag_scanning               	0 bytes	879 bytes
 1681564715891                              phoenix   	metadata                   	0 bytes	903 bytes
 1681564715891                              phoenix   	offsetstore                	0 bytes	981 bytes
 1681564715891                              phoenix	   messages                   	0 bytes	1.42 KiB
 1681564715891                              phoenix       tag_write_progress         	0 bytes	1002 bytes

Сам снимок создается в каталоге data_directory/phoenix/table_name-UUID/snapshots/snapshot_name. Каждый каталог снимка содержит множество файлов .db, которые включают в себя данные на момент создания снимка. При выполнении команды можно использовать опцию --tag, чтобы было проще в дальнейшем работать со снимком. Листинг директории со снимком будет выглядеть так:

[cassandra@cassandra-09 messages]$ ll
 -rw-r--r--. 1 cassandra cassandra        694 Apr 14 12:59 manifest.json
 -rw-r--r--. 1 cassandra cassandra         43 Mar 20 15:12 me-4459-big-CompressionInfo.db
 -rw-r--r--. 1 cassandra cassandra      16784 Mar 20 15:12 me-4459-big-Data.db
 -rw-r--r--. 1 cassandra cassandra         10 Mar 20 15:12 me-4459-big-Digest.crc32
 -rw-r--r--. 1 cassandra cassandra         72 Mar 20 15:12 me-4459-big-Filter.db
 -rw-r--r--. 1 cassandra cassandra       1965 Mar 20 15:12 me-4459-big-Index.db
 -rw-r--r--. 1 cassandra cassandra       5603 Mar 27 11:13 me-4459-big-Statistics.db
 -rw-r--r--. 1 cassandra cassandra        164 Mar 20 15:12 me-4459-big-Summary.db
 -rw-r--r--. 1 cassandra cassandra         92 Mar 20 15:12 me-4459-big-TOC.txt
 -rw-r--r--. 1 cassandra cassandra       1783 Apr 14 12:59 schema.cql

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

nodetool snapshot phoenix ‑table messages

Эта команда создаст снимок только для таблицы "messages" для кейспейса "phoenix".

Восстановление данных с помощью команды sstableloader

SSTableLoader — это инструмент командной строки в Cassandra, используемый для загрузки SSTable (Sorted String Table) в кластер. Последний — это отсортированный набор данных, который представляет собой неизменяемый файл на диске. SSTable состоит из двух файлов: файла данных и файла индекса. Это основной механизм хранения данных в Cassandra.

Инструмент SSTableLoader обычно применяется для восстановления данных из снимков, а также для их переноса между кластерами (например, чтобы обогатить тестовый кластер продуктивными данными для нагрузочного тестирования).

Для восстановления данных с помощью команды sstableloader необходимо выполнить следующие шаги:

1. Создайте снимок при помощи команды nodetool snapshot:

nodetool snapshot <keyspace>

где <keyspace> — название кейспейса, для которого создается снимок.

2. Скопируйте все SSTable-файлы из директории

<data_directory>/<keyspace>/<table_name-UUID>/snapshots/<snapshot_name> на новый узел кластера или на текущий узел кластера <data_directory>/<keyspace>/<table_name-UUID>, в зависимости от задачи - восстановление или перенос данных.

3. Запустите команду sstableloader:

sstableloader -d <destination_node> <path_to_sstable_directories>

где <destination_node> — IP-адрес узла кластера, на который загружаются данные, а <path_to_sstable_directories> — путь к директориям, содержащим SSTable-файлы, скопированные из снимка.

[cassandra@cassandra-09 ]$ sstableloader -d cassandra-09 /var/lib/cassandra/ssd/data/phoenix/messages/
 Established connection to initial hosts
 Opening sstables and calculating sections to stream
 Streaming relevant part of /var/lib/cassandra/ssd/data/phoenix/messages/md-20-big-Data.db /var/lib/cassandra/ssd/data/phoenix/messages/md-20-big-Data.db /var/lib/cassandra/ssd/data/phoenix/messages/me-9-big-Data.db /var/lib/cassandra/ssd/data/phoenix/messages/md-48-big-Data.db /var/lib/cassandra/ssd/data/phoenix/messages/me-21-big-Data.db /var/lib/cassandra/ssd/data/phoenix/messages/me-18-big-Data.db to [/10.10.10.07, /10.10.10.08, /10.10.10.09]
 progress: [/10.78.222.233]0:1/18 0  % total: 0% 13.845KiB/s (avg: 0.045KiB/s)
 progress: [/10.78.222.233]0:2/18 25 % total: 0% 27.346MiB/s (avg: 27.798KiB/s)
 progress: [/10.78.222.233]0:3/18 30  % total: 0% 8.729MiB/s (avg: 33.436KiB/s)
 progress: [/10.78.222.233]0:4/18 35  % total: 0% 12.431MiB/s (avg: 41.715KiB/s)
 progress: [/10.78.222.233]0:5/18 40  % total: 0% 82.531MiB/s (avg: 306.882KiB/s)
 progress: [/10.78.222.233]0:6/18 45 % total: 12% 28.052MiB/s (avg: 4.930MiB/s)
 progress: [/10.78.222.233]0:7/18 45 % total: 16% 8.972MiB/s (avg: 5.508MiB/s)
 progress: [/10.78.222.233]0:8/18 50 % total: 20% 9.683MiB/s (avg: 5.996MiB/s)
 progress: [/10.78.222.233]0:9/18 55 % total: 87% 184.800KiB/s (avg: 8.640MiB/s)
 progress: [/10.78.222.233]0:10/18 60 % total: 87% 42.059MiB/s (avg: 8.650MiB/s)
 progress: [/10.78.222.233]0:11/18 65 % total: 91% 9.853MiB/s (avg: 8.694MiB/s)
 progress: [/10.78.222.233]0:12/18 70 % total: 95% 758.657MiB/s (avg: 9.053MiB/s)
 progress: [/10.78.222.233]0:13/18 75 % total: 98% 27.475MiB/s (avg: 9.294MiB/s)
 progress: [/10.78.222.233]0:14/18 80 % total: 99% 12.327MiB/s (avg: 9.319MiB/s)
 progress: [/10.78.222.233]0:15/18 85 % total: 99% 28.579MiB/s (avg: 9.322MiB/s)
 progress: [/10.78.222.233]0:16/18 90 % total: 99% 2.056MiB/s (avg: 9.321MiB/s)
 progress: [/10.78.222.233]0:17/18 95% total: 100% 4.485MiB/s (avg: 9.321MiB/s)
 progress: [/10.78.222.233]0:18/18 100% total: 100% 0.000KiB/s (avg: 8.697MiB/s)

 Summary statistics:
    Connections per host	: 1
    Total files transferred : 18
    Total bytes transferred : 262.110MiB
    Total duration      	: 30138 ms
    Average transfer rate   : 8.697MiB/s
    Peak transfer rate  	: 9.322MiB/s

4. Дождитесь завершения загрузки данных. На листинге команды выше — это вывод Summary statistics.

5. Проверьте, что ваши данные загрузились через консольную утилиту cqlsh:

cassandra@cqlsh> select * from phoenix.messages;

После выполнения команды sstableloader данные из снимка будут загружены в кластер. Если на его узле уже существуют данные для кейспейса, то из снимка они будут добавлены к уже существующим.

Далее после восстановления данных или переноса на новый кластер необходимо выполнить nodetool repair:

nodetool repair <keyspace_name>

Резервное копирование кластера

Цель статьи — рассказать про стандартный инструмент снятия снимков и работы с восстановлением или переносом данных, но я хочу подсветить и тему резервного копирования (бэкап). Для такого действия с кластером Cassandra можно использовать несколько подходов:

1. Использовать nodetool snapshot и выполнять его с помощью cron. Бюджетно и «из коробки», но не идеальный способ, так как необходимо регулярно создавать снимки данных и сохранять их в отдельное хранилище.


2. Применять Incremental Backups в Cassandra. Тоже работает «из коробки». Для создания инкрементных резервных копий нужно выставить true для incremental_backups в файле конфигурации Cassandra (cassandra.yaml), либо выполнив nodetool enablebackup для включения и nodetool disablebackup для выключения. Процесс восстановления данных такой же, как и при nodetool snapshot, но нужно тщательно следить за местом на дисках!


3. Прибегать к различным инструментам резервного копирования, таким как rsync, scp, tar. Вместе с обвязкой скриптами они будут копировать все файлы данных Cassandra, а именно SSTable, в отдельное хранилище. Снова не идеальное решение, но имеет место быть.


4. Использовать [Medusa for Apache Cassandra], комплексный инструмент резервного копирования и восстановления как одной ноды, так и целого кластера. Есть возможность интеграции с S3 совместимыми хранилищами, например с Minio. Настройка Medusa, перечисление всех плюсов и минусов — тема для отдельной статьи.

Выводы

  • Восстановление данных из cнимка (snapshot) — самый быстрый и простой способ вернуть данные в случае частичной потери или аварии в кластере.

  • Не забывайте создавать резервные копии для предотвращения потери данных в будущем.

P.S.

Бонус: скрипт для клонирования кейспеса, подходит для работы с тестовым окружением:
#!/bin/bash
 cloneKeyspace()
 {
 	local username="${1}"
 	local start_host="${2}"
 	local hostnames="${3}"
 	local keyspace_source="${4}"
 	local keyspace_destination="${5}"
 	local data_dir="/var/lib/cassandra/ssd/data"​
 	echo "Drop ${keyspace_destination} keyspace"
 	ssh ${username}@${start_host} "cqlsh -u cassandra -p password ${start_host} -e 'DROP KEYSPACE ${keyspace_destination}'"
 	for hostname in $(echo $hostnames | sed "s/,/ /g")
 	do
   	echo "Delete ${keyspace_destination} keyspace directory"
   	ssh ${username}@${hostname} "sudo rm -rf ${data_dir}/${keyspace_destination}"
 	done
 	if ssh ${username}@${start_host} "echo 2>&1"; then
  	ssh ${username}@${start_host} "cqlsh -u cassandra -p password ${start_host} -e 'DESCRIBE KEYSPACE ${keyspace_source}' > ${keyspace_source}.txt"
  	ssh ${username}@${start_host} "sed -i 's/${keyspace_source}/${keyspace_destination}/g' ${keyspace_source}.txt"
  	ssh ${username}@${start_host} "cqlsh -u cassandra -p password ${start_host} -f '${keyspace_source}.txt'"
  	ssh ${username}@${start_host} "rm ${keyspace_source}.txt"     
  	echo "Success on Keyspace Schema clone: "${start_host}
 	else
   	echo "Failed on Keyspace Schema clone: "${start_host}
 	fi
 	
 	for hostname in $(echo $hostnames | sed "s/,/ /g")
 	do
    	if ssh ${username}@${hostname} "echo 2>&1"; then
     	echo "Clear snapshot copy on ${hostname}"
     	ssh ${username}@${hostname} "nodetool clearsnapshot -t copy"
     	echo "Create new snapshot copy on ${hostname}"
     	ssh ${username}@${hostname} "nodetool snapshot -t copy ${keyspace_source}"
     	echo "Success on Cassandra snapshot: "${hostname} ${keyspace_source}
     	
     	sleep 20
     	
  	   ssh ${username}@${hostname} "sudo mv ${data_dir}/${keyspace_source}/metadata-*/snapshots/copy/* ${data_dir}/${keyspace_destination}/metadata-*/"
     	ssh ${username}@${hostname} "sudo mv ${data_dir}/${keyspace_source}/snapshots-*/snapshots/copy/* ${data_dir}/${keyspace_destination}/snapshots-*/"
     	ssh ${username}@${hostname} "sudo mv ${data_dir}/${keyspace_source}/messages-*/snapshots/copy/* ${data_dir}/${keyspace_destination}/messages-*/"
     	ssh ${username}@${hostname} "sudo mv ${data_dir}/${keyspace_source}/tag_scanning-*/snapshots/copy/* ${data_dir}/${keyspace_destination}/tag_scanning-*/"
     	
     	ssh ${username}@${hostname} "nodetool clearsnapshot -t copy"
    	
     	echo "Success on Cassandra mv of snapshot files to destination: "${keyspace_destination}
     	ssh ${username}@${hostname} "nodetool refresh ${keyspace_destination} snapshots"
     	ssh ${username}@${hostname} "nodetool refresh ${keyspace_destination} messages"
     	ssh ${username}@${hostname} "nodetool refresh ${keyspace_destination} metadata"
     	ssh ${username}@${hostname} "nodetool refresh ${keyspace_destination} tag_scanning"
     	
     	echo "Success on Cassandra nodetool refresh on destination keyspace: "${keyspace_destination}
    	else
     	echo "Failed on Cassandra snapshot: "${hostname}
    	fi
 	done
 	
 	if ssh ${username}@${start_host} "echo 2>&1"; then
  	ssh ${username}@${start_host} "nodetool repair -full ${keyspace_destination}"
  	echo "Success on Keyspace repair: "${start_host} ${keyspace_destination}
 	else
  	echo "Failed on Keyspace repair : "${start_host} ${keyspace_destination}
 	fi
 	
 	echo "Script processing completed!"
 }
 ​
 cloneKeyspace "${1}" "${2}" "${3}" "${4}" "${5}"

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


  1. therb1
    22.04.2023 17:58

    А почему решили все делать руками? Есть же проект medusa. https://github.com/thelastpickle/cassandra-medusa