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 на нее.

И напоследок, банальные, но полезные советы.

  1. Создавайте актуальный бекап данных перед началом любых ответственных работ. У нас он был.

  2. Сверяйте каунты по таблицам и количество записей в detached_parts до и после работ. У вас не должно быть таблиц в read only, а задержка репликации (absolute_delay) должна быть нулевой.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Про что написать
0%Работа с данными: миграции, Materialized View, агрегации0
60%Tips & Tricks для Clickhouse, полезные SQL-запросы3
80%Исследование проблем производительности, настройка Clickhouse4
Проголосовали 5 пользователей. Воздержались 3 пользователя.