Мы — долго запрягаем, быстро ездим, и сильно тормозим.
|
||||||||||||||||||||||||||||
www.lissyara.su
—> статьи
—> FreeBSD
|
|
При попытке импорта пула, zpool(8) вообще сваливался в кору.
Листинг 1.2. Падение zpool(8) в кору
|
В логах стабильно появлялись два сообщения, свидетельствующие о том, что некоторые сектора на ad4 приказали долго жить. Как выяснилось далее, в этих секторах начинались важные для ZFS структуры данных, но первые несколько килобайт этой структуры, удачным образом, не использовались и были помечены в спецификации, как "Blank space" (пустое место).
Примечание.
Здесь и далее под килобайтом понимается 1024 байта, размер сектора так же стандартный, т.е. 512 байт.
Листинг 1.3. Сбойные сектора
|
Попробовал прочитать эти сектора и убедился в том, что они работать не будут.
Листинг 1.4. Чтение сбойных секторов "в ручную"
|
Подключил диск такой же модели и в несколько приёмов (обходя сбойные сектора) скопировал на него содержимое глючившего диска. После чего поставил новый диск вместо глючного и попытался импортировать пул. Но, к моему разочарованию, пул отказался импортироваться и с новым диском. zpool(8) продолжал падать в кору.
Листинг 1.5. Развалившийся пул, после замены диска
|
Официальная документация утверждала то же, что и состояние пула - пора доставать резервную копию. Копия была, но последний раз она выполнялась чуть меньше года назад и была, мягко говоря, "неактуальной". Можно было восстановить данные из этой копии, всё лучше, чем ничего, да и не столь важны они были. Но мысль о том, что данные ещё существуют - их только необходимо извлечь не давала покоя.
2. Восстановление
2.1. Рекогносцировка местности
Внутреннее устройство ZFS я знал плохо - пришлось изучать его на форсаже, с прицелом на возможность восстановления данных. После недолгих поисков нагуглил[1] сообщение некоего Nathan Hand, который утверждал, что смог восстановить работоспособность пула. Так же в сообщении обнаружилась ссылка на черновой вариант спецификации[2] ZFS. Настроение стало улучшаться.
2.2. Некоторые сведения об устройстве ZFS
Тем, кто знаком с устройством ZFS этот раздел будет бесполезен, тем, кто не знаком - этот раздел будет даже вреден, лучше читать оригинальную спецификацию. Но, по-традиции, несколько слов об устройстве ZFS, как понял его я.
Структура данных, с которой начинается разбор бинарного месива диска перед тем как это месиво станет навороченной файловой системой, называется метка (англ. Label). На диске хранится несколько меток в строго определённых позициях.
Схема 2.2.1. Расположение меток на диске
|
L0, L1, L2, L3 - метки ZFS. L0, L1 - располагаются друг за другом без пропусков в начале диска (с нулевого смещения). L2, L3 - располагаются в конце диска, но не обязательно в самых последних секторах, после них может оставаться свободное место (скорее всего из-за выравнивания на 128 Кб). Пространство между L1 и L2 занято вспомогательными структурами и самими данными, но их позиции строго не определены. Метки на одном диске должны быть строго одинаковыми. О том, почему выбрано такое количество меток и каким образом они обновляются хорошо написано в документации[2].
Структура метки достаточно проста, она состоит из 8Кб пустого пространства (англ. Blank Space), 8Кб загрузочного заголовка (англ. Boot Header), 112Кб конфигурационных данных в формате имя - значение (англ. Name/Value Pair List) и 128Кб массива структур Uberblock. Итого получается 256Кб на одну метку.
Схема 2.2.2. Структура метки
|
Список имя-значение, содержит следующие конфигурационные данные (перечислены самые интересные, на мой взгляд):
- version - версия формата хранения;
- name - имя пула;
- state - состояние пула (активен/экспортирован/удалён);
- txg - номер транзакции в которой выполнялась запись (будет описан далее);
- pool_guid - уникальный идентификатор пула;
- guid - уникальный идентификатор виртуального устройства;
- vdev_tree - дерево, описывающее всю конфигурацию входящих в пул виртуальных устройств, само дерево состоит из пар имя-значение, основные поля описывающие отдельное устройство:
- type - тип устройства (файл/блочное устройство/зеркало/raidz/корень);
- path - имя устройства (только для файлов и блочных устройств);
- guid - уникальный идентификатор описываемого виртуального устройства;
- children - массив подчинённых виртуальных устройств.
Более подробное описание самих данных и ссылки на формат их хранения доступны в спецификации.
Примечание.
Следует отметить, что ZFS активно оперирует таким понятием, как виртуальное устройство (англ. vdev), под которым может пониматься, как вполне реальный диск, так и абсолютно абстрактный RAID-Z массив или корень иерархии устройств.
Uberblock (перевода не придумал) по своей природе похож на Superblock UFS - он является началом всей структуры данных на диске. Почему используется целый массив блоков вместо одного? Всё просто: ZFS никогда не пишет данные поверх уже существующих, вместо этого она записывает новую структуру в новую позицию, и только потом допускает возможность модификации уже имеющихся данных (их перезапись). При чтении структуры диска (напр. после перезагрузки) просто находится запись сделанная последней и используется, остальные записи считаются неактуальными. Сам Uberblock в начале содержит пять 64-битных полей:
- ub_magic - "магическое число" идентифицирующее (сигнатура) блок;
- ub_version - версия формата хранения;
- ub_txg - номер транзакции в которой записана данная структура;
- ub_guid_sum - сумма идентификаторов устройств;
- ub_timestamp - UTC метка времени.
Далее следуют указатели на подчинённые структуры. Суммарный объём структуры равен 1Кб. С учётом того, что массив имеет объём 128Кб получаем, что в каждой метке может храниться 128 Uberblock'ов, созданных в разное время.
"Магическое число" всегда равно 0x00bab10c (oo-ba-block) на диск данное значение будет записано следующей последовательностью байт.
Таблица 2.2.1. Запись "Магического числа"
|
Особого внимания заслуживает поле транзакция, в котором хранится номер транзакции, по завершении которой и был записан данный Uberblock. Так же, как было отмечено ранее, номер транзакции отдельно указывается в метке. На основании этого номера и принимается решение, какой Uberblock считается активными. Активным считается Uberblock с максимальным номером транзакции, при этом номер транзакции блока должен быть больше и равен номеру транзакции метки. Если это условие не выполняется, то производится поиск блока с меньшим номером. Т.о. если при выполнении транзакции произошла ошибка и Uberblock записан не был, то ZFS сможет корректно откатиться к предыдущей транзакции.
Для просмотра содержимого меток диска используется опция "-l" утилиты zdb(8).
Дальше начинаются дебри из структур, описывающих расположение блоков на диске. Кому интересно, тот может ознакомиться с ними в документации[2].
2.3. Детальное обследование пациента
Начать было решено с простейших манипуляций - проверить читаемость и корректность меток, с помощью zdb(8).
Листинг 2.3.1. Чтение меток с дисков (показано только начало дампа)
|
Никакого криминала метки считались - придётся вводить в действие тяжёлую артиллерию.
2.4. Тяжёлая артиллерия
Для того, что бы было удобнее копаться в бинарном содержимом меток их необходимо было скопировать с дисков в отдельные файлы. Для этого нужно знать начала меток и их размер на диске. С размером вопросов не было - размер меток постоянен и равен 256Кб. Смещения L0 и L1 равны 0 и 256Кб соответственно. Оставалось выяснить смещения L2 и L3. Объём диска равен 1465138584Кб, вычитая размер метки, я должен был получить смещение метки L3: 1465138328Кб. Но не тут то было. Сделав дамп 256Кб от полученного смещения я решил его проверить, найдя смещение первого Uberblock'а от начала дампа. Это должно было быть ровно 128Кб или 020000h. Uberblock легко определить в куче байт по "Магическому числу", с которого он начинается, но необходимо делать поправку на порядок байт в записи числа.
Листинг 2.4.1. Неправильный выбор смещения
|
Промазал. Первый Uberblock в полученном дампе располагался на нулевом смещении, а это значит, что ошибка составляет минимум 128Кб. Т.к. первый Uberblock должен располагаться на смещении 128Кб от начала метки, а в дампе уже на нулевом смещении я видел один из блоков. Для определения корректного смещения было необходимо определить, сколько Uberbock'ов попало в дамп и сместиться на соответствующее количество блоков + 128Кб.
Листинг 2.4.2. Определение количества Uberblock'ов, попавших в дамп
|
В дампе 104 блока, должно быть 128 - нам надо сместиться влево (к младшим адресам) на: (128 - 104) + 128 = 152 (Кб). Новое смещение: 1465138328 - 152 = 1465138176 (Кб). Делаем дамп и проверяем количество блоков и смещение первого из них.
Листинг 2.4.3. Проверка нового смещения
|
Ага, угадал. Смещение L2 получить элементарно - необходимо вычесть 256Кб из полученного смещения L3: 1465138176 - 256 = 1465137920 (Кб). Получилась небольшая таблица смещений. Т.к. диски одинаковой модели, то и смещения для них будут одинаковыми.
Таблица 2.4.1. Смещения меток
|
Т.к. рутинную работу лучше выполняет машина, я набросал простенький скрипт для дампа всех меток в отдельные файлы.
Листинг 2.4.4. Скрипт для дампа
|
После его выполнения метки были сохранены в файлы adN-labelX.dump (N - номера дисков: 2, 4, 6; X - номер метки: 0, 1, 2, 3). Разбираться стало немного удобнее.
Далее я начал сравнивать конфигурации, записанные в метках, но результата это не принесло - оставалось лезть в код реализации ZFS для того, что бы выяснить какие именно данные были повреждены.
2.5. Поиск ответов в коде реализации
Немного порывшись по коду zpool и libzfs, обнаружил следующие строки в libzfs_status.c, которые и устанавливают статус пула "The pool metadata is corrupted".
Листинг 2.5.1. Код определяющий состояние ZFS-пула
|
Проверяемые поля устанавливаются в модуле ядра - пришлось перебираться в ядро. После недолгих поисков был найден файл spa.c, ответственный за открытие/закрытие, а так же импорт пула. В данном файле было несколько участков кода, в которых поля устанавливались в интересующее состояние. Надо было как-то выяснить что именно не нравиться алгоритму импорта. Для этого насовал кучу отладочного вывода в модуль, пересобрал его и загрузил вместо стандартного, разрешив вывод отладочной информации. Теперь, в момент импорта пула, в логи сыпалось множество отладочной информации, показывающей ход выполнения алгоритма импорта/открытия пула. Среди прочего был найден код проверки активного Uberblock'а.
Листинг 2.5.2. Проверка активного Uberblock'а
|
Примечание.
У zpool(8) есть специальная недокументированная опция "-F", которая позволяет импортировать даже сбойный пул. Работать после этого он конечно не будет, но для отладки и восстановления данных опция полезная.
Добавив перед проверкой вывод номера транзакции активного блока, получил номер транзакции, которую ZFS считает последней удачной. Это была 253277 транзакция.
Немного порывшись в коде, пришёл к выводу, что ошибка где-то глубже чем неправильно записанная метка. Т.к. восстанавливать всю структуру блок за блоком в мои планы не входило (пул содержал несколько сотен гигабайт не такой уж и ценной информации), я решил прибегнуть к грубой силе - заставить ZFS откатиться к предыдущей транзакции.
2.6. Решение грубой силой
Для того, что бы заставить ZFS откатиться к предыдущему состоянию, было необходимо стереть Uberblock последней удачной транзакции (а это была 253277 транзакция). Но прежде, немного переделав предыдущий скрипт, я получил скрипт извлекающий номера транзакций из всех Uberbock'ов всех дампов. Другой вариант поиска необходимого Uberblock'а - найти его по значению в дампе (253277 = 03DD5Dh).
Примечание.
Данный скрипт будет работать только на пулах созданных на узлах с big-endian архитектурой (x86, etc). Для little-endian архитектур необходимо в функции преобразования массива в число убрать цикл, инвертирующий массив.
Примечание.
На платформе i386, функция преобразования массива в число, при больших номерах транзакций, может работать с ошибками. Это вызвано тем, что для представления номера транзакции используется 64-битное число, а expr(1) работает со стандартными типами и может произойти целочисленное переполнение. Для 64-битной платформы (AMD64) такого ограничения быть не должно (но не проверялось).
Листинг 2.6.1. Скрипт извлекающий номера транзакций Uberblock'ов
|
После запуска скрипта и перенаправления вывода в файл, получил список из 1536 записей (3 диска с 4 метками по 128 блоков в каждой). Далее сортируем его по номеру транзакции и определяем наибольший.
Листинг 2.6.2. Поиск Uberblock'а с наибольшим номером
|
Ага. Транзакция 253277 есть только на ad2 и ad4 видимо на ней ad4 и стало плохо. Теперь определим, на каких дисках есть запись о предыдущей транзакции и с каким номером были записаны метки на дисках.
Листинг 2.6.3. Определение номеров транзакций меток
|
Листинг 2.6.4. Поиск Uberblock'ов последней и предпоследней транзакций
|
Итак, текущий активный Uberblock располагается в 93 позиции массива Uberblock'ов на дисках ad2 и ad6. Uberblock предыдущей транзакции расположен в 92 позиции на всех дисках. Остаётся рассчитать смещение в дампе 93 позиции массива: 128 + 93 = 221 (Кб). Проверим расчёт, найдя блок по номеру транзакции: 253277 = 03dd5dh, с учётом порядка байт, нужно найти последовательность: 5d dd 03 00.
Листинг 2.6.5. Поиск Uberblock'а по номеру транзакции
|
Искомая последовательность найдена по смещению 037410h, блок начинается по смещению 037400h, т.к. поле txg расположено по смещению 10h = 16 байт от начала блока. Переведём в килобайты: 037400h / 400h = ddh = 221 - предыдущий расчёт смещения был сделан верно. Остаётся затереть текущие активные блоки и записать метки назад на диск. Но перед этим стоит сохранить оригинальные метки.
Листинг 2.6.6. Удаление Uberblock'ов
|
Было бы неплохо проверить, те ли блоки были затёрты.
Листинг 2.6.7. Проверка содержимого блоков
|
Так, блоки затёрты - остаётся вернуть метки на диск.
Внимание!!!
Я экспериментировал с рабочими дисками потому, что информация, хранимая на них не представляла особой ценности. Если потеря информации недопустима, то предварительно выполняют полное копирование содержимого дисков на другие диски, аналогичной модели. И все операции выполняют на изготовленной копии. В случае недопустимости потери информации, работа (в особенности запись) с рабочими дисками недопустима! И вообще, существуют целые организации, специализирующиеся на восстановлении данных - лучше обратиться к ним, если вы не являетесь экспертом по восстановлению информации с систем хранения, а информация вам дорога (например, как память).
Для записи меток обратно на диски я немного модифицировал первоначальный скрипт и запустил его.
Листинг 2.6.8. Скрипт записи меток на диски
|
Скрестив пальцы, импортирую пул в режиме только для чтения.
Листинг 2.6.9. Импорт пула в режиме только чтение
|
И-и-и-и, о чудо! Пул импортировался и чувствует себя нормально.
Листинг 2.6.10. Состояние пула после импорта
|
Быстренько достав винт подходящего объема, запустил полное копирование всех данных с пула на него, т.к. продолжать работать с пулом, побывавшим в состоянии "FAULTED" - не наш метод. И вот пока бегут в обратную сторону часы, оставшиеся до завершения копирования я и решил записать эту небольшую историю.
Год спустя
Ещё весной задал на форуме вопрос о поддержке "отмотки" транзакций. И был направлен тов. Anon Y Mous на путь истинный, т.е. в последние (на тот момент) правки HEAD-ветки. Немного покопавшись в репозитории, нашёл нужную правку[3], в которой была добавлена поддержка той самой "отмотки" транзакций. Но это всё предисловие.
После обновления, в правке за номером 219089 [3], ZFS до версии 28, в zpool(8) появилась возможность откатываться на несколько транзакций назад при передаче параметра -F. Так что описанный в статье приём восстановления стал обычной рутинной задачей. За подробностями по ссылке на правку или в подсказку, выводимую zpool(8).
Мораль
А мораль проста и давно всем известна - даже самые отказоустойчивые технологии могут дать сбой, а поэтому, систематическое резервное копирование важных данных один из лучших способов чувствовать себя сухо и комфортно.
Список литературы
1. Need Help Invalidating Uberblock // http://mail.opensolaris.org/pipermail/zfs-discuss/2008-December/024427.html
2. ZFS On-Disk Specification // http://opensolaris.org/os/community/zfs/docs/ondiskformat0822.pdf
3. FreeBSD repo revision 219089 // http://svnweb.freebsd.org/base?view=revision&revision=219089
размещено: 2011-02-04,
последнее обновление: 2012-02-02,
автор: BlackCat
kazak, 2011-01-22 в 12:15:07
Статья хорошая, а мог бы ты описать сценарий действий штатными средствами?
BlackCat, 2011-01-23 в 21:53:26
Штатными средствами ZFS сама себя восстанавливает без внешнего вмешательства. Если только диск не вышел из строя. В этом случае меняем диск.
Black Cat, 2011-02-04 в 13:25:17
Слуяайно у нас одинаковые ники
Статья, мега
Проделана серьезнаю работа
Очень было интересно почитатать
Аноним, 2011-02-04 в 14:25:18
Вот и зачем тащить весь этот Ынтырпрайз, к тому же ещё нестабильный, в сохо-сектор? Не понимаю.
BlackCat, 2011-02-04 в 15:07:11
Тов. Аноним есть такое подозрение, что вы меня не совсем правильно поняли. Сама статейка - это доказательство (prof of concept) возможности восстановления руками. О конкретных показателях надёжности я даже не пытался сделать никаких выводов - это сложная тема, требующая большого объёма исследований и выходящая за рамки моей компетенции. В данном конкретном случае ZFS начала сбоить из-за глючного железа и сбоя питания, а не ошибок в коде реализации и просчётов главного архитектора ФС. После замены диска и пересоздания пула и до настоящего момента - ни одной ошибки, но это то же не показатель.
Если же говорить о теоритической возможности выхода из строя, то да, она существует. Но с таким же успехом можно раскритиковать устройство вселенной, т.к. существует теоритическая возможность прекращения её существования.
А если серьёзно. Есть желание убедить сообщество в том, что время ZFS ещё не пришло или ей не место в том или ином классе систем хранения, интересно самому разобраться - создайте тему на форуме, привидите аргументы, там и подискутируем.
GrossHo, 2011-02-04 в 16:36:01
После прочтения я стал чувствовать свою кожу более сухой и шелковистой, что стало дополнительным плюсом чтобы использовать ZFS на своих серверах. Есть одно замечание не касающееся проделанной работы, а одного малоинтересного факта: для x86 и amd64 характерен Little Endian, тогда как для Sparc - Big Endian. Еще раз спасибо за интересную статью.
BAZ Megodriver, 2011-02-04 в 18:34:09
Очень правильная мораль....
Она всегда верна.
MarvinFS, 2011-02-04 в 19:04:54
Мега респект!!!! просто душа поёт за наших отечественных специалистов которые еще могут победить "систему"! Спасибо!
Hubbitus, 2011-02-04 в 19:36:51
Мега-статья!
Еще бы на основе наработок и опыта написать бы какую-то утилитку для более-менее автоматического использования другими. Было бы вообще супер.
Morty, 2011-02-07 в 10:33:58
статья отличная !! понравилось
Автору респект
Bx, 2011-02-25 в 21:18:36
А чем плохи номера транзакций последних, выдаваемых "zdb -l"? Чисто теоретически - мог бы сработать откат на меньшую из, вероятно.
sage444, 2011-03-04 в 9:43:20
Где-то в рассылке встречал упоминания о чем-то похожем, но тут все очень детально и понятно.
автору респект!
BlackCat, 2011-03-11 в 23:30:07
Bx, zdb(8) с параметром -l выдаёт номер транзакции в которой была записана метка (label), а не номер последней транзакции, произведённой в пуле.
Насчёт отката к предыдущей транзакции: так о нём и шла речь.
winterheart, 2011-03-14 в 8:27:09
Статья хорошая, но IMHO, ситуация - суета вокруг рояля. Надо было делать дампы/бекапы изначально. Вспоминаем старую поговорку сисадминов.
playnet, 2011-04-05 в 0:46:04
Выпал 1 диск из 5 рейда (по сути) и смерть всех данных? Как-то оно вообще ненадежно тогда. Смерть при потере 1 диска допустима исключительно для страйпа!
Остальные диски были нормальные или тоже с бэдами?
BlackCat, 2011-04-05 в 0:51:31
playnet, обратите внимание на мой коментарий от 2011-02-04.
Мфынф, 2011-07-09 в 8:53:20
Ахуеть , дайте две.
Тока нах он такое нужен zfs если один диск выпал и куку.
BlackCat, 2011-11-23 в 22:55:57
Обновил статью, добавив раздел "Год спустя". В zpool(8) добавили параметр, позволяющий выполнять откат на несколько транзакций назад.
Пострадавший, 2011-11-25 в 17:39:59
Вы бы оставили свои правки для spa.c :)
BlackCat, 2011-11-26 в 1:44:21
Какие?!! Если вы имеете ввиду отладочный вывод - то в нём нет ничего необычного: перед ключевыми точками ветвления ставиться функция отладочного вывода, через которую выводим значения параметров участвующих в условии перехода.
BlackCat, 2012-02-02 в 12:19:27
Наконец-то понял, какую ошибку имел ввиду GrossHo: были неправильно указаны названия для порядка байт. Правильные названия, как уже было указано: для x86 и amd64 характерен Little Endian, тогда как для Sparc - Big Endian.
GrossHo, спасибо.
chpoqxie, 2012-03-04 в 22:58:17
Я в свое время, когда убедился в бесплодности попыткой поднять упавший raidz на четырех винтах под фряхой, загрузился с инсталл-цд солярки x86, и она прекрасно подцепила пул, пофиксила его, и после этого фряха нормально с ним общается.
имхо, не стоит юзать zfs под fbsd для критичной инфы. по крайней мере, полгода назад точно не стоило. а в конце 2010-начале 2011 - ууу. автор - камикадзе.
Karharot, 2013-09-01 в 0:13:48
Этот информационный блок появился по той простой причине,
что многие считают нормальным, брать чужую информацию не уведомляя автора
(что не так страшно), и не оставляя линк на оригинал и автора — что более существенно.
Я не против распространения информации — только за. Только условие простое — извольте
подписывать автора, и оставлять линк на оригинальную страницу в виде прямой, активной, нескриптовой,
незакрытой от индексирования, и не запрещенной для следования роботов ссылки.
Если соизволите поставить автора в известность — то вообще почёт вам и уважение.
© lissyara 2006-10-24 08:47 MSK
Комментарии пользователей [23 шт.]