
Clickhouse никогда не удаляет ваши данные, главное помнить об этом и знать где искать. Эта ��ысль согревала меня в течение всего процесса восстановления.
Наша команда переезжала с Zookeeper на Clickhouse Keeper. Для этого мы конвертировали снепшот зукипера в формат clickhouse-keeper с помощью утилиты clickhouse-keeper-converter, любезно предоставляемой самими разработчиками Clickhouse.
О том, что дела пошли не так, мы узнали вскоре после рестарта clickhouse-server, по одной из таблиц была задержка репликации (absolute_delay). Быстрая проверка по таблице выявила полное отсутствие данных за последние 7 дней.
SELECT toDate(doc_date_time) AS date, count()
FROM db.very_sick_table
WHERE date > today()-20 -- так как таблица всё-таки довольно большая
GROUP BY date
ORDER BY dateПервой мыслью было восстановиться из бекапа в отдельную таблицу и скопировать недостающие за 7 дней данные, но давайте на всякий случай проверим данные за месяц раньше? За предыдущий месяц (ноябрь) данные были лишь за 27 дней из 30, хотя документы поступают ежедневно в большом количестве. Это указывало на значительно большую потерю данных.
Пришло время посмотреть на структуру данных:
CREATE TABLE db.very_sick_table
(
doc_id UUID,
user_id Int64,
region_id Int64,
order_id Int64,
full_code String,
doc_date_time DateTime
)
ENGINE = ReplicatedMergeTree(..)
PARTITION BY (user_id, region_id)
ORDER BY order_id Из структуры данных понятно, что они не упорядочены по дате, и восстановление по диапазону дат нам не подойдет.
Конечно, причина проблемы была быстро установлена: снепшот зукипера, полученный с ноды-лидера и сконвертированный в формат кипера, отставал на 7 дней.
В момент старта кликхаус сверяет локальные парты реплицируемых таблиц с данными, записанными в кипере. И если локально у клика есть парты неизвестные в кипере, то они переносятся в папку detached.
В результате, все парты данных, которые были модифицированы за время отставания снепшота, были утеряны, это и объясняет полную потерю данных за неделю и частичную - за прошлые периоды, вплоть до 2022 года.
Давайте искать, где же наши данные.
SELECT * FROM system.detached_parts
WHERE database = 'db' AND table = 'very_sick_table'Выборка дает нам 85 строк, все имена имеют префикс ignored, а modification_time совпадает со временем старта clickhouse-server. Папки с теми же именами обнаружились в папке detached нашей таблицы. То же самое было найдено на еще 2х репликах.
Данные не утеряны, попробуем восстановить их через временную нереплицируемую таблицу с той же структурой.
CREATE TABLE db.repair_table
(
...
)
ENGINE = MergeTreeЧтобы перенести парты данных на диске, нам надо знать куда их копировать, то есть путь к папке detached таблицы repair_table
SELECT concat(arrayJoin(data_paths), '/', 'detached')
FROM system.tables
WHERE db = 'db'
AND name = 'repair_table'Перенесем данные в папку detached, и после этого нам надо переименовать папки, чтобы убрать префикс ignored. Беглый "дипсикинг/алисинг" решения быстро выдает работающий вариант:
cd ..
find detached -type d -name "ignored_*" -exec sh -c 'd="{}"; mv "$d" "${d%ignored_*}/${d##*/ignored_}"' \;Приаттачим данные в новую таблицу:
ALTER TABLE db.repair_table ATTACH PARTITION ALLУра! Потерянные данные появились в таблице db.repair_table, но прежде чем сливать данные в одну таблицу, желательно убедиться в отсутствии дублей. Это не обязательный пункт программы, но хотелось быть 100% уверенным.
Нам известно, что doc_id является уникальным полем и на его уникальность мы можем полагаться. Для проверки уникальности ReplacingMergeTree достаточно было бы сравнить по ключу сортировки (order by), а для обычного MergeTree правильным решением будет сверить все стобцы.
При помощи JOIN ищем совпадения в левой таблице very_sick_table и правой таблице repair_table:
SELECT count() FROM
(SELECT doc_id FROM db.very_sick_table ) as broken
LEFT SEMI JOIN
(SELECT doc_id FROM db.repair_table) as repairing
USING doc_idНа выходе получается 0 записей, это означает, что данные таблиц не пересекаются.
Копируем данные из восстановленной таблицы в основную. В нашем случае я оставил обе таблицы AS IS, и скопировал данные в отдельную новую реплицируемую таблицу и заменил изначальную db.very_sick_table на нее.
И напоследок, банальные, но полезные советы.
Создавайте актуальный бекап данных перед началом любых ответственных работ. У нас он был.
Сверяйте каунты по таблицам и количество записей в detached_parts до и после работ. У вас не должно быть таблиц в read only, а задержка репликации (absolute_delay) должна быть нулевой.
