Этот сайт — моя персональная записная книжка. Интересны мне, по большей части, программирование, история и события из моей жизни.

«Invalid page» и «тост»

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

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

Долго разбирался в чём дело, разобрался. Принцип работы моей функции такой — она вынимает каждую строку, если получает ошибку, то сообщает об этом. Но оказалось, что если какие-либо разрушенные данные лежат в «тостах» (это механизм автоматического сжатия и выноса больших значений полей в «Постгресе»), то этого недостаточно. Видимо «Постгрес» в реальности не достаёт эти данные, пока они не понадобятся для чего-либо и поэтому ошибки не возникает.

В итоге я переделал хранимку — передаю всё строку целиком в функцию ROW_TO_JSON, чтобы прочитались и значения из «тостов». Это помогло, функция стала работать как и задумывалось.

CREATE OR REPLACE FUNCTION check_table(table_name TEXT)
RETURNS void AS $$
DECLARE
    rec RECORD;
    row_data RECORD;
BEGIN
    FOR rec IN EXECUTE format('SELECT ctid::text AS ctid_str, id FROM %I', table_name)
    LOOP
        BEGIN
            EXECUTE format('SELECT * FROM %I WHERE ctid = %L::tid', table_name, rec.ctid_str)
            INTO STRICT row_data;

        row_data := ROW(ROW_TO_JSON(row_data)); /* to read the toasts */

        EXCEPTION WHEN others THEN
            RAISE WARNING 'CTID: %, ID: %, Error: %', rec.ctid_str, rec.id, SQLERRM;
        END;
    END LOOP;
END;
$$ LANGUAGE plpgsql;
1 комментарий
Ринат 11 дн

Вместо `digital_sign` должно быть `table_name` ?

Евгений Степанищев 11 дн

Да, всё верно, спасибо!