Привет, Хабр! Меня зовут Евгений Абрамкин, я руководитель поддержки третьего уровня в направлении омниканальных решений Лиги Цифровой Экономики. Моя команда — последняя «инстанция» во флоу по решению инцидентов. Мы пишем доработки и фиксы, чтобы победить проблему клиента, а еще предоставляем оптимальную конфигурацию для системы, которая передана в эксплуатацию или требует масштабирования. Это может быть кластер 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}"