Мой первый опыт восстановления базы данных Postgres после сбоя (invalid page in block 4123007 of relatton base/16490)

    Хочу поделиться с вами моим первым успешным опытом восстановления полной работоспособности базы данных Postgres. С СУБД Postgres я познакомился пол года назад, до этого опыта администрирования баз данных у меня не было совсем.



    Я работаю полу-DevOps инженером в крупной IT-компании. Наша компания занимается разработкой программного обеспечения для высоконагруженных сервисов, я же отвечаю за работоспособность, сопровождение и деплой. Передо мной поставили стандартную задачу: обновить приложение на одном сервере. Приложение написано на Django, во время обновления выполняются миграции (изменение структуры базы данных), и перед этим процессом мы снимаем полный дамп базы данных через стандартную программу pg_dump на всякий случай.

    Во время снятия дампа возникла непредвиденная ошибка (версия Postgres – 9.5):

    pg_dump: Oumping the contents of table “ws_log_smevlog” failed: PQgetResult() failed.
    pg_dump: Error message from server: ERROR: invalid page in block 4123007 of relatton base/16490/21396989
    pg_dump: The command was: COPY public.ws_log_smevlog [...]
    pg_dunp: [parallel archtver] a worker process dled unexpectedly

    Ошибка «invalid page in block» говорит о проблемах на уровне файловой системы, что очень нехорошо. На различных форумах предлагали сделать FULL VACUUM с опцией zero_damaged_pages для решения данной проблемы. Что же, попрробеум…

    Подготовка к восстановлению


    ВНИМАНИЕ! Обязательно сделайте резервную копию Postgres перед любой попыткой восстановить базу данных. Если у вас виртуальная машина, остановите базу данных и сделайте снепшот. Если нет возможности сделать снепшот, остановите базу и скопируйте содержимое каталога Postgres (включая wal-файлы) в надёжное место. Главное в нашем деле – не сделать хуже. Прочтите это.

    Поскольку в целом база у меня работала, я ограничился обычным дампом базы данных, но исключил таблицу с повреждёнными данными (опция -T, --exclude-table=TABLE в pg_dump).

    Сервер был физическим, снять снепшот было невозможно. Бекап снят, двигаемся дальше.

    Проверка файловой системы


    Перед попыткой восстановления базы данных необходимо убедиться, что у нас всё в порядке с самой файловой системой. И в случае ошибок исправить их, поскольку в противном случае можно сделать только хуже.

    В моём случае файловая система с базой данных была примонтирована в «/srv» и тип был ext4.

    Останавливаем базу данных: systemctl stop postgresql@9.5-main.service и проверяем, что файловая система никем не используется и её можно отмонтировать с помощью команды lsof:
    lsof +D /srv

    Мне пришлось ещё остановить базу данных redis, так как она тоже исползовала "/srv". Далее я отмонтировал /srv (umount).

    Проверка файловой системы была выполнена с помощью утилиты e2fsck с ключиком -f (Force checking even if filesystem is marked clean):



    Далее с помощью утилиты dumpe2fs (sudo dumpe2fs /dev/mapper/gu2--sys-srv | grep checked) можно убедиться, что проверка действительно была произведена:



    e2fsck говорит, что проблем на уровне файловой системы ext4 не найдено, а это значит, что можно продолжать попытки восстановить базу данных, а точнее вернуться к vacuum full (само собой, необходимо примонтирвоать файловую систему обратно и запустить базу данных).

    Если у вас сервер физический, то обязательно проверьте состояние дисков (через smartctl -a /dev/XXX) либо RAID-контроллера, чтобы убедиться, что проблема не на аппаратном уровне. В моём случае RAID оказался «железный», поэтому я попросил местного админа проверить состояние RAID (сервер был в нескольких сотнях километров от меня). Он сказал, что ошибок нет, а это значит, что мы точно можем начать восстановление.

    Попытка 1: zero_damaged_pages


    Подключаемся к базе через psql аккаунтом, обладающим правами суперпользователя. Нам нужен именно суперпользователь, т.к. опцию zero_damaged_pages может менять только он. В моём случае это postgres:

    psql -h 127.0.0.1 -U postgres -s [database_name]

    Опция zero_damaged_pages нужна для того, чтобы проигнорировать ошибки чтения (с сайта postgrespro):
    При выявлении повреждённого заголовка страницы Postgres Pro обычно сообщает об ошибке и прерывает текущую транзакцию. Если параметр zero_damaged_pages включён, вместо этого система выдаёт предупреждение, обнуляет повреждённую страницу в памяти и продолжает обработку. Это поведение разрушает данные, а именно все строки в повреждённой странице.
    Включаем опцию и пробуем делать full vacuum таблицы:

    VACUUM FULL VERBOSE


    К сожалению, неудача.

    Мы столкнулись с аналогичной ошибкой:

    INFO: vacuuming "“public.ws_log_smevlog”
    WARNING: invalid page in block 4123007 of relation base/16400/21396989; zeroing out page
    ERROR: unexpected chunk number 573 (expected 565) for toast value 21648541 in pg_toast_106070

    pg_toast – механизм хранения «длинных данных» в Postgres, если они не помещаются в одну страницу (по умолчанию 8кб).

    Попытка 2: reindex


    Первый совет из гугла не помог. После нескольких минут поиска я нашёл второй совет – сделать reindex повреждённой таблицы. Этот совет я встречал во многих местах, но он не внушал доверия. Сделаем reindex:

    reindex table ws_log_smevlog



    reindex завершился без проблем.

    Однако это не помогло, VACUUM FULL аварийно завершался с аналогичной ошибкой. Поскольку я привык к неудачам, я стал искать советов в интернете дальше и наткнулся на довольно интересную статью.

    Попытка 3: SELECT, LIMIT, OFFSET


    В статье выше предлагали посмотреть таблицу построчно и удалить проблемные данные. Для начала необходимо было просмотреть все строки:

    for ((i=0; i<"Number_of_rows_in_nodes"; i++ )); do psql -U "Username" "Database Name" -c "SELECT * FROM nodes LIMIT 1 offset $i" >/dev/null || echo $i; done

    В моём случае таблица содержала 1 628 991 строк! По-хорошему необходимо было позаботиться о партициирвоании данных, но это тема для отдельного обсуждения. Была суббота, я запустил вот эту команду в tmux и пошёл спать:

    for ((i=0; i<1628991; i++ )); do psql -U my_user -d my_database -c "SELECT * FROM ws_log_smevlog LIMIT 1 offset $i" >/dev/null || echo $i; done

    К утру я решил проверить, как обстоят дела. К моему удивлению, я обнаружил, что за 20 часов было просканировано только 2% данных! Ждать 50 дней я не хотел. Очередной полный провал.

    Но я не стал сдаваться. Мне стало интересно, почему же сканирование шло так долго. Из документации (опять на postgrespro) я узнал:
    OFFSET указывает пропустить указанное число строк, прежде чем начать выдавать строки.
    Если указано и OFFSET, и LIMIT, сначала система пропускает OFFSET строк, а затем начинает подсчитывать строки для ограничения LIMIT.

    Применяя LIMIT, важно использовать также предложение ORDER BY, чтобы строки результата выдавались в определённом порядке. Иначе будут возвращаться непредсказуемые подмножества строк.
    Очевидно, что вышенаписанная команда была ошибочной: во-первых, не было order by, результат мог получиться ошибочным. Во-вторых, Postgres сначала должен был просканировать и пропустить OFFSET-строк, и с возрастанием OFFSET производительность снижалась бы ещё сильнее.

    Попытка 4: снять дамп в текстовом виде


    Далее мне в голову пришла, казалось бы, гениальная идея: снять дамп в текстовом виде и проанализировать последнюю записанную строку.

    Но для начала, ознакомимся со структурой таблицы ws_log_smevlog:



    В нашем случае у нас есть столбец «id», который содержал уникальный идентификатор (счётчик) строки. План был такой:

    1. Начинаем снимать дамп в текстовом виде (в виде sql-команд)
    2. В определённый момент времени снятия дампа бы прервалось из-за ошибки, но тектовый файл всё равно сохранился бы на диске
    3. Смотрим конец текстового файла, тем самым мы находим идентификатор (id) последней строки, которая снялась успешно

    Я начал снимать дамп в текстовом виде:

    pg_dump -U my_user -d my_database -F p -t ws_log_smevlog -f ./my_dump.dump

    Снятия дампа, как и ожидалось, прервался с той же самой ошибкой:

    pg_dump: Error message from server: ERROR: invalid page in block 4123007 of relatton base/16490/21396989

    Далее через tail я просмотрел конец дампа (tail -5 ./my_dump.dump) обнаружил, что дамп прервался на строке с id 186 525. «Значит, проблема в строке с id 186 526, она битая, её и надо удалить!» – подумал я. Но, сделав запрос в базу данных:
    «select * from ws_log_smevlog where id=186529» обнаружилось, что с этой строкой всё нормально… Строки с индексами 186 530 — 186 540 тоже работали без проблем. Очередная «гениальная идея» провалилась. Позже я понял, почему так произошло: при удалении\изменении данных из таблицы они не удаляются физически, а помечаются как «мёртвые кортежи», далее приходит autovacuum и помечает эти строки удалёнными и разрешает использовать эти строки повторно. Для понимания, если данные в таблице меняются и включён autovacuum, то они не хранятся последовательно.

    Попытка 5: SELECT, FROM, WHERE id=


    Неудачи делают нас сильнее. Не стоит никогда сдаваться, нужно идти до конца и верить в себя и свои возможности. Поэтому я решил попробовать ешё один вариант: просто просмотреть все записи в базе данных по одному. Зная структуру моей таблицы (см. выше), у нас есть поле id, которое является уникальным (первичным ключом). В таблице у нас 1 628 991 строк и id идут по порядку, а это значит, что мы можем просто перербрать их по одному:

    for ((i=1; i<1628991; i=$((i+1)) )); do psql -U my_user -d my_database  -c "SELECT * FROM ws_log_smevlog where id=$i" >/dev/null || echo $i; done

    Если кто не понимает, команда работает следующим образом: просматривает построчно таблицу и отправляет stdout в /dev/null, но если команда SELECT проваливается, то выводится текст ошибки (stderr отправляется в консоль) и выводится строка, содержащая ошибку (благодаря ||, которая означает, что у select возникли проблемы (код возврата команды не 0)).

    Мне повезло, у меня были созданы индексы по полю id:



    А это значит, что нахождение строки с нужным id не должен занимать много времени. В теории должно сработать. Что же, запускаем команду в tmux и идём спать.

    К утру я обнаружил, что просмотрено около 90 000 записей, что составляет чуть более 5%. Отличный результат, если сравнивать с предыдущим способом (2%)! Но ждать 20 дней не хотелось…

    Попытка 6: SELECT, FROM, WHERE id >= and id <


    У заказчика под БД был выделен отличный сервер: двухпроцессорный Intel Xeon E5-2697 v2, в нашем расположении было целых 48 потоков! Нагрузка на сервере была средняя, мы без особых проблем могли забрать около 20-ти потоков. Оперативной памяти тоже было достаточно: аж 384 гигабайт!

    Поэтому команду нужно было распараллелить:

    for ((i=1; i<1628991; i=$((i+1)) )); do psql -U my_user -d my_database  -c "SELECT * FROM ws_log_smevlog where id=$i" >/dev/null || echo $i; done

    Тут можно было написать красивый и элегантный скрипт, но я выбрал наиболее быстрый способ распараллеливания: разбить диапазон 0-1628991 вручную на интервалы по 100 000 записей и запустить отдельно 16 команд вида:

    for ((i=N; i<M; i=$((i+1)) )); do psql -U my_user -d my_database  -c "SELECT * FROM ws_log_smevlog where id=$i" >/dev/null || echo $i; done

    Но это не всё. По идее, подключение к базе данных тоже отнимает какое-то время и системные ресурсы. Подключать 1 628 991 было не очень разумно, согласитесь. Поэтому давайте при одном подключении извлекать 1000 строк вместо одной. В итоге команда преобразилоась в это:

    for ((i=N; i<M; i=$((i+1000)) )); do psql -U my_user -d my_database  -c "SELECT * FROM ws_log_smevlog where id>=$i and id<$((i+1000))" >/dev/null || echo $i; done

    Открываем 16 окон в сессии tmux и запускаем команды:
    1) for ((i=0; i<100000; i=$((i+1000)) )); do psql -U my_user -d my_database  -c "SELECT * FROM ws_log_smevlog where id>=$i and id<$((i+1000))" >/dev/null || echo $i; done
    2) for ((i=100000; i<200000; i=$((i+1000)) )); do psql -U my_user -d my_database  -c "SELECT * FROM ws_log_smevlog where id>=$i and id<$((i+1000))" >/dev/null || echo $i; done
    …
    15) for ((i=1400000; i<1500000; i=$((i+1000)) )); do psql -U my_user -d my_database -c "SELECT * FROM ws_log_smevlog where id>=$i and id<$((i+1000))" >/dev/null || echo $i; done
    16) for ((i=1500000; i<1628991; i=$((i+1000)) )); do psql -U my_user -d my_database  -c "SELECT * FROM ws_log_smevlog where id>=$i and id<$((i+1000))" >/dev/null || echo $i; done
    Через день я получил первые результаты! А именно (значения XXX и ZZZ уже не сохранились):

    ERROR:  missing chunk number 0 for toast value 37837571 in pg_toast_106070
    829000
    ERROR:  missing chunk number 0 for toast value XXX in pg_toast_106070
    829000
    ERROR:  missing chunk number 0 for toast value ZZZ in pg_toast_106070
    146000

    Это значит, что у нас три строки содержат ошибку. id первой и второй проблемной записи находились между 829 000 и 830 000, id третьей – между 146 000 и 147 000. Далее нам предстояло просто найти точное значение id проблемных записей. Для этого просматриваем наш диапазон с проблемными записями с шагом 1 и идентифицируем id:
    for ((i=829000; i<830000; i=$((i+1)) )); do psql -U my_user -d my_database -c "SELECT * FROM ws_log_smevlog where id=$i" >/dev/null || echo $i; done
    829417
    ERROR:  unexpected chunk number 2 (expected 0) for toast value 37837843 in pg_toast_106070
    829449
    for ((i=146000; i<147000; i=$((i+1)) )); do psql -U my_user -d my_database -c "SELECT * FROM ws_log_smevlog where id=$i" >/dev/null || echo $i; done
    829417
    ERROR:  unexpected chunk number ZZZ (expected 0) for toast value XXX in pg_toast_106070
    146911

    Счастливый финал


    Мы нашли проблемные строки. Заходим в базу через psql и пробуем их удалить:

    my_database=# delete from ws_log_smevlog where id=829417;
    DELETE 1
    my_database=# delete from ws_log_smevlog where id=829449;
    DELETE 1
    my_database=# delete from ws_log_smevlog where id=146911;
    DELETE 1

    К моему удивлению, записи удалились без каких-либо проблем даже без опции zero_damaged_pages.

    Затем я подключился к базе, сделал VACUUM FULL (думаю делать было необязательно), и, наконец, успешно снял бекап с помощью pg_dump. Дамп снялся без каких либо ошибок! Проблему удалось решить таким вот тупейшим способом. Радости не было предела, после стольких неудач удалось найти решение!

    Благодарности и заключение


    Вот такой получился мой первый опыт восстановления реальной базы данных Postgres. Этот опыт я запомню надолго.

    Ну и напоследок, хотел бы сказать спасибо компании PostgresPro за переведённую документацию на русский язык и за полностью бесплатные online-курсы, которые очень сильно помогли во время анализа проблемы.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 35

      +1
      ws_smev_log — СМЭВ? НИИ Восход?
        0
        Да, СМЭВ. Но нет, не «Восход».
          0
          Думаю «БАРС ГРУП» :-))
          Я сам такое проделывал когда там работал, и сразу сделал delete дохлым строкам.
          Нужно было хотя бы имя таблицы поменять чтобы не палиться. )
        0

        Эм… А если в этих данных были важные данные? То логичный ход — взять бекап этой таблицы, и сделать следующее: исторические данные взять из бекапа, а хвост из живой базы, склеить — это и будет продакшен версия на текущий момент.
        Если же в базе тупо был лог действий, который был не особо нужен — почему нельзя было тупо таблицу "обнулить" и начать с начала? Или FK мешали бы?
        Ну, что — опыт правда крутой, но статья какая-то недосказанная.
        И, да, первоисточник проблемы нашли? А где гарантия, что база опять не покорраптится?
        +1 поставил, но ждём… Продолжения.

          0
          А если в этих данных были важные данные?

          Я не видел другого варианта. Да и данные эти были уже повреждены, их нельзя было никак восстановить. На мой взгляд, удаление проблемных записей было единственным правильным решением
          То логичный ход — взять бекап этой таблицы

          К сожалению, как это обычно бывает, бекап, который содержал таблицу ws_smev_log, запускался раз в неделю (командой pgdumpall). Но за успешностью его выполнения никто не следил, он просто аварийно завершался.
          В остальные дни бекап запускался с помощью pg_dump, но таблица ws_smev_log исключалась, чтобы сэкономить дисковое пространство. Поэтому бекапов таблицы ws_smev_log не было.
          Если же в базе тупо был лог действий, который был не особо нужен — почему нельзя было тупо таблицу «обнулить» и начать с начала?

          Данные в таблице не были очень критичными, но и просто так удалять их было нельзя. Обнулить не разрешили.
            0
            То-есть у Вас бэкапом называется pumpall?
              0
              да, раз в неделю делаем pg_dumapall, каждый день делается просто pg_dump с исключением таблицы ws_smev_log.
                0
                грустно.
                или это из серии «база особенно не нужна»?
                  0
                  А как нужно правильно? С помощью barman и подобных утилит?
                    +1
                    Например можно вот так: www.postgresql.org/docs/9.5/continuous-archiving.html
                    С логами и восстановлением на нужное время. Это быстрее дампирования.
                      0

                      Ну, это очевидный трейдоф. Либо мы размазываем бекап по времени и получается стриминг. Либо мы забиваем на это и приходится выделять точные моменты времени для полного снапшота, но это "ударная" нагрузка на сервер, да и бекап может долго делаться.

                        +2
                        tradeoff там по объёму данных на бекап-машине (особенно для задачи типа «надо восстановить таблицу за 2017 год»). И по времени разворачивания бекапа тоже. При том в обе стороны в зависимости от условий. Pitr вообще-то штука не очень-то быстрая, т.к. имеет склонность упираться в одно ядро CPU и крайне критичная к латентности io. Тогда как pg_restore можно и параллелить.
                        Ну и ещё такой фокус: по-моему мне за несколько лет работы нужно было восстанавливать именно целиком инстанс только один раз, а обычно задача «вытащить несколько строк из одной таблицы на любую дату до Х». Что куда быстрее делается из custom/directory форматов pg_dump.

                        Поэтому pg_dump или basebackup с архивом wal — выбирать надо по ситуации. Можно выбирать оба и комбинировать подходящим для именно ваших требований способом.
          0
          Теперь вы наверняка поняли, зачем нужна реплика. Что, если бы проблемных строк было не три, а тридцать тысяч и в каждой — какие-нибудь финансовые данные клиента, не отражённые до сих пор в бэкапе?
            0

            Реплика, к сожалению, не спасет от drop database. От логического разрушения файлов БД — да

              +1
              Она и не должна, в общем-то. Но в целом — можно же добавить вторую, с задержкой применения на пару часов.
            +3

            Наглядное пособие, для чего нужна была задача от гугла про небоскреб и два стеклянных шара.

              +2
              В моём случае таблица содержала 1 628 991 строк! По-хорошему необходимо было позаботиться о партициирвоании данных

              Нет, не надо было. Всего лишь полтора миллиона строк — это где от партицирования проблем больше чем пользы.

              При тихом повреждении проверяйте диски и RAM. Даже если она ECC. Тихое повреждение страницы по вине сбоя ECC плашки памяти наблюдал сам.

              Затем я подключился к базе, сделал VACUUM FULL (думаю делать было необязательно),

              Обязательно. Но не всей базы, а только этой таблицы.

              Ну а в остальном подход верный:
              — сделать физическую копию и экспериментировать на копии
              — проверить железо
              — сверить данные с репликой если есть
              — определить повреждённые данные. Если повреждён toast как у вас — попытаться прочитать строки не select *, а добавляя поля переменной длины по одному. Возможно повреждено только одно из полей. reindex — если сломан индекс. По системному каталогу путь base/16490/21396989 можно сопоставить с логическим объектом
              — удалить повреждённые либо insert… select'ом создать копию таблицы рядом и удалить старую повреждённую таблицу.

              zero_damaged_pages лучше не трогать.

              PS: ну и имхо некорректно писать про postgresql и ссылаться на документацию стороннего форка.
                0

                PostgresPro переводят самую обычную официальную документацию. Плюс расширенные версии для двух своих форков (Pro Standard и Enterprise). Итого доступны три перевода. Там даже можно открывать параллельно рус и англ версии.

                  0

                  Так ссылки и цитаты же как раз именно на форк приведены. Именно к тому и мой ps.

                  0
                  Нет, не надо было. Всего лишь полтора миллиона строк — это где от партицирования проблем больше чем пользы.

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

                    +2
                    Так и не может быть точных данных.

                    Проблемы — смотрите хотя бы на planning time запроса. Партицированием не сложно сделать так, что запрос планируется дольше чем собственно выполняется. partition pruning сам по себе не бесплатный.
                    Или вот например какой-нибудь простой where user_id =? order by created_at limit 30 — это один составной индекс для плоской таблицы и весьма развесистый план для партицированной по дате (потому что 30 записей можно не набрать со всех разделов вовсе и надо предполагать идти во все разделы).

                    in real world о партицировании чаще думают в контексте «нам не нужны эти сотни гигабайт данных старше 3 лет в основной базе, надо их заархивировать и удалить». В плоской таблице это интересное мероприятие с разными подводными камнями (если кратко: не надо делать это одним куском delete from, только разбивать на части вменяемого размера и удалять частями в разных транзакциях с паузами между ними). А для партиции — drop table
                    Вариации задачи «данные нужны в этой же базе, но сильно реже и можно на более медленные и дешёвые диски перенести» — сюда же.
                      0
                      Дак наоборот же, сейчас все начали напирать на партиционирование для «быстродействия». Пробовать, честно говоря, лень на значимых объемах.
                      В принципе доля истины в том есть, большой индекс будет работать медленней, но с другой стороны есть частичные индексы, которыми оперативный период можно умаслать в усмерть и реиндексировать хоть каждый день.

                      Касательно pruning: он не нивелируется prepared запросами? Я давненько так использую persistent connection в связке с prepared, посему на время планирования вообще перестал смотреть.
                        +1
                        Касательно pruning: он не нивелируется prepared запросами?

                        Вы СУБД, получаете parse message с запросом where user_id = $1. Видите что таблица партицирована по user_id. Есть идеи как сделать partition pruning? Ммм? Вы не знаете какие bind с execute будут дальше, а значит вы обязаны проигнорировать возможные оптимизации и предполагать посетить каждую партицию.

                        Только относительно недавно (в postgresql11) завезли runtime partition pruning — который срабатывает в executor'е в дополнении к планировщику. executor может не обращаться к таблице есть понимает что нужных данных там всё равно не будет опираясь на реальные переданные параметры.

                        большой индекс будет работать медленней

                        работать будет нормально, формально поиск по btree конечно от глубины дерева зависит, но фактически большой индекс печальнее из-за того что он большой и надо больше буфера/памяти под него.
                        Вот только… Если вы нарежете таблицу по условному hash(user_id) — меньше данных вам в буфере держать не получится, запрашивать-то будут данные разных пользователей.
                        Если же партицировать по дате — то да, горячие данные обычно недавние. Но это опять же вариация задачи «нам не нужны эти старые данные так часто»

                        Партицированием улучшить производительность тоже можно, но с пониманием как именно используются данные этой большой таблицы. И самое важное потом — не забывать указывать условие на отсечение партиций.
                  0

                  Чтоб не ждать так долго, можно было бы использовать метод половинного деления, каждый раз делая INSERT FROM SELECT во временную таблицу. Табличка небольшая, всего 1.6 миллиона записей.
                  Делим диапазон ID пополам (1..800K; 800K..1.6M) и копируем данные во временную таблицу. Запрос для одной из половинок завершится с ошибкой. Берём эту половину диапазона и выполняем два запроса для четвертинок (напр, 800K..1.2M;1.2M..1.6M) и так далее. Несколько минут — и вы получили бы ответ.

                    0
                    Запрос для одной из половинок завершится с ошибкой.

                    Нет. Запрос для обоих половинок имеет шанс завершится с ошибкой.
                      0
                      И это будет означать что у нас не одна проблемная строка, а несколько. В этом случае мы проходим по обеим ветвям, которые, в свою очередь, могут также породить новые ветви. Написать простенький скрипт с рекурсией на Bash, Python, PL/pgSQL и т.п. займет минут 15. Запустить, подождать несколько минут — и вуаля.
                        +1
                        Разумеется. Я лишь за точность формулировок. По изначальной формулировке вторая половина не будет проверяться, в случае если ошибка нашлась в первой, — это следствие очевидной оптимизации.
                        0

                        Их тоже разбить на половинки) бинарный поиск же

                      +1
                      А что, разве в Postgres нет штатной утилиты для восстановления целостности БД с потерей информации, такой как gfix в Inetrbase и его потомках (или DBCC CHECKDB в MS SQL, или eseutil /p для БД на Extensible Storage Engine от MS — Exchange и системные БД компонентов Windows Server)? Упущение, однако, IMHO.
                      Ибо в Interbase в некие древние времена (ещё когда Delphi было модным средством разработки приложений) мне как-то пришлось воспользоваться gfix на боевой БД — и это было сильно проще, чем описанные в статье мучения.
                        0
                        Использовал функцию отсюда которая работала несколько минут для миллиона строк и позволяла удалить нужную строку даже если не было своего уникального поля-идентификатора. (восстанавливал таблицы 1C)
                          +1
                          Мне тоже подумалось, что вызывать 1 500 000 раз консольную утилиту с выводом с консоль же не очень хорошая затея.
                          +1

                          Замечу, что всех описанных в статье страданий можно было избежать, просто посмотрев битую страницу через расширение pageinspect (https://www.postgresql.org/docs/current/pageinspect.html)

                            +1

                            Угу, и дополню:


                            • Можно было-бы увидеть и понять что было в испорченных строках. В результате (вероятно) восстановить данные полностью (либо ввести вручную), а также пронять причину ошибки (аппаратный сбой или баг в коде postgres).
                            • Первый шаг автора не верный. Всё-таки стоило делать копию всех данных (ибо они не разрушены физически, т.е. нет проблем на HDD).
                            0
                            Вопрос к автору — когда снимали текстовый дамп, он случайно не обрывается на проблемной строчке?
                              0
                              Нет, про это написано в пункте «Попытка 4: снять дамп в текстовом виде».

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое