<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0"
  xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
  xmlns:atom="http://www.w3.org/2005/Atom">

<channel>

<title>Блоги: заметки с тегом postgres</title>
<link>https://blogengine.ru/blogs/tags/postgres/</link>
<description>Автоматически собираемая лента заметок, написанных в блогах на Эгее</description>
<author></author>
<language>ru</language>
<generator>Aegea 11.0 (v4079e)</generator>

<itunes:subtitle>Автоматически собираемая лента заметок, написанных в блогах на Эгее</itunes:subtitle>
<itunes:image href="" />
<itunes:explicit>no</itunes:explicit>

<item>
<title>Invalid page in block XXX of relation YYY</title>
<guid isPermaLink="false">123115</guid>
<link>https://bolknote.ru/all/invalid-page-in-block-xxx-of-relation-yyy/</link>
<pubDate>Wed, 20 Sep 2023 23:08:53 +0500</pubDate>
<author>Евгений Степанищев</author>
<comments>https://bolknote.ru/all/invalid-page-in-block-xxx-of-relation-yyy/</comments>
<description>
&lt;p&gt;&lt;a href="https://bolknote.ru/"&gt;Евгений Степанищев&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;Несколько раз в жизни сталкивался с ситуацией, когда «Постгрес» при попытке выполнить запрос, возвращает что-то вроде &lt;tt&gt;invalid page in block 460248 of relation base/16391/23665&lt;/tt&gt;. Это всегда означает какую-то аппаратную проблему на системе хранения, где СУБД хранит свои данные.&lt;/p&gt;
&lt;p&gt;Уже два раза я попадал в ситуацию, когда бакапа у клиента по каким-то причинам нет и надо сдампить то, что имеется, причём всегда на дворе всегда глубокая ночь, поэтому надо бы уже описать этот опыт, пока снова не забылся и не пришлось его в который раз восстанавливать спросонья.&lt;/p&gt;
&lt;p&gt;Первым делом, разумеется надо отключить пользователей от базы данных, чтобы предотвратить её дальнейшее разрушение.&lt;/p&gt;
&lt;p&gt;Второе, что надо сделать — посмотреть какая сущность у нас развалилась. Это можно сделать следующим запросом:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="sql"&gt;SELECT 23665::regclass::text;
    text
-------------
 some_items&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Если это индекс, просто удаляем его и пытаемся снять полный дамп базы данных, записав какой индекс мы удалили. Подобные ошибки могут быть в других частях базы, поэтому повторяем до успеха.&lt;/p&gt;
&lt;p&gt;Всё несколько хуже, есть речь идёт о таблице. Для начала надо определить строки с какими &lt;tt&gt;ID&lt;/tt&gt; теперь недоступны и сдампить таблицу пока без них. Для этого я написал достаточно прямолинейную хранимую процедуру, которая пытается получить каждую строку, которая должна существовать и, если не удаётся, сообщает строка с каким &lt;tt&gt;ID&lt;/tt&gt; недоступна.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;CREATE OR REPLACE FUNCTION check_table(table_name TEXT) RETURNS VOID AS $$
DECLARE
    min_id INT;
    max_id INT;
    current_id INT;
BEGIN
    EXECUTE &amp;#039;SELECT MIN(id), MAX(id) FROM &amp;#039; || table_name INTO min_id, max_id;
    
    current_id := min_id;
    
    WHILE current_id &amp;lt;= max_id LOOP
        BEGIN
            EXECUTE &amp;#039;SELECT * FROM &amp;#039; || table_name || &amp;#039; WHERE id = $1&amp;#039; USING current_id;
        EXCEPTION
            WHEN SQLSTATE &amp;#039;XX001&amp;#039; THEN
                RAISE NOTICE &amp;#039;Ошибка при обработке id: %&amp;#039;, current_id;
        END;
        
        current_id := current_id + 1;
    END LOOP;
END
$$ LANGUAGE plpgsql;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Дальше таблицу надо сдампить, составив &lt;tt&gt;SELECT&lt;/tt&gt;, исключающий сбойные участки. Я, обычно, делаю ряд запросов, соединённых через &lt;tt&gt;UNION ALL&lt;/tt&gt; и подставляю его в &lt;tt&gt;COPY&lt;/tt&gt;, предварительно выключив последовательное сканирование таблицы (&lt;tt&gt;SET enable_seqscan = OFF&lt;/tt&gt;).&lt;/p&gt;
&lt;p&gt;Если этого не сделать, «Постгрес» обязательно займётся сканированием таблицы, так как нужна она почти вся, а там у нас сбойные участки. Отключив этот метод сканирования, мы заставляем обращаться к таблице через первичный индекс, если он, конечно цел. Хуже, когда разрушен и он.&lt;/p&gt;
&lt;p&gt;Я с такой ситуацией ещё не сталкивался, но если столкнусь, буду думать над проверкой строк через &lt;tt&gt;ctid&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;В общем, должен получиться примерно такой запрос:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="sql"&gt;SET enable_seqscan = OFF;

COPY (
SELECT * FROM some_items WHERE id &amp;lt; 100500 UNION ALL
SELECT * FROM some_items WHERE id &amp;gt; 100500 AND id &amp;lt; 100800 UNION ALL
SELECT * FROM some_items WHERE id &amp;gt; 100800
) TO &amp;#039;/tmp/some_items-table.dump&amp;#039;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После того как мы определили и сдампили все сбойные таблицы, можно сдампить остальную базу данных следующей командой:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;pg_dump --exclude-table-data=some_items -j10 -Fd -Z1 -f /dump_directory dbname&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Как видно, мы дампим структуру, но не данные сбойной таблицы (ключ &lt;tt&gt;—exclude-table-data&lt;/tt&gt; можно указать несколько раз), при этом параметр &lt;tt&gt;-Fd&lt;/tt&gt; нужен, чтобы мы могли снимать дамп параллельно сразу несколькими процессами, &lt;tt&gt;-j&lt;/tt&gt; задаёт количество этих процессов, ключ &lt;tt&gt;-f&lt;/tt&gt; указывает директорий, куда будет записан бакап, а &lt;tt&gt;-Z1&lt;/tt&gt; регулирует степень сжатия, —с уровнем &lt;tt&gt;1&lt;/tt&gt; снятие дампа идёт с максимальной скоростью.&lt;/p&gt;
&lt;p&gt;Наконец, успех: основная база лежит единым куском, сбойные таблицы сняты отдельными дампами. Что ещё можно сделать?&lt;/p&gt;
&lt;p&gt;В одном из случаев у меня были несколько реплик, которые развалились по-разному. Из них удалось наскрести недостающие куски. Их можно снять через тот же &lt;tt&gt;COPY&lt;/tt&gt; с запросом &lt;tt&gt;SELECT&lt;/tt&gt; и просто присоединить в конец дампа соответствующей таблицы — там текстовый формат.&lt;/p&gt;
&lt;p&gt;Если реплики нет, можно посмотреть какие индексы есть на сбойной таблице. Не исключено, что критичные столбцы покрыты индексами, которые остались целыми.&lt;/p&gt;
&lt;p&gt;Данные из индексов можно вытащить, сделав запрос так, чтобы он затрагивал только столбцы, которые есть в индексе. Если в интересующем нас индексе нет ключевого столбца, не беда, там есть технический столбец &lt;tt&gt;ctid&lt;/tt&gt;, по которому потом можно соединить данные построчно.&lt;/p&gt;
&lt;p&gt;Если строки восстановить не удалось, а на таблицу ссылаются другие таблицы, надо заполнить недостающее любым мусором, чтобы внешним ключам было куда ссылаться.&lt;/p&gt;
&lt;p&gt;Итак, теперь у нас есть большой дамп почти всей базы данных и один или несколько дампов таблиц. Как же всё соединить на другом сервере? Последовательность следующая.&lt;/p&gt;
&lt;p&gt;Для начала восстанавливаем основную базу данных, но без индексов и внешних ключей:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;pg_restore -d dbname -Fd /dump_directory --section=pre-data --section=data -j10&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Потом заливаем дампы отдельных таблиц, после чего доливаем индексы и внешние ключи:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;pg_restore -d dbname -Fd /dump_directory --section=post-data -j10&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Если всё сделано правильно, должна получиться консистентная база данных. Осталось разобраться с двумя вещами: создать удалённые ранее индексы, если таковые были и, если вы создавали мусорные строки, то надо понять как с ними быть дальше. Например, удалить их или заполнить значениями «данные утеряны».&lt;/p&gt;
</description>
</item>


</channel>
</rss>