В очередной рабочий день поступила задача обновить Gitlab. Задача в общем-то не сложная, несмотря на то, что там он установлен в докере из многим знакомого образа от sameersbn, что впоследствии было переделано на Omnibus (что бы это ни значило), т.к. по моему опыту Omnibus версия (установка на чистый линукс) гораздо проще и предсказуемей в эксплуатации. Впрочем статья совсем не об этом.

Но как можно понять из наличия этой статьи, что-то пошло не так...

Предисловие

Если с грубым бекапом перед обновлением в целом проблем не было - данных в данном Gitlab не много и можно просто скопировать папки с данными, то с необходимостью сделать полный дамп базы Gitlab для обновления версии Postgres возникли проблемы.

Началось всё с этого:

Скрытый текст
pg_dump: error: query failed: ERROR:  MultiXactId 1598374738 has not been created yet -- apparent wraparound
pg_dump: error: query was: SELECT c.tableoid, c.oid, c.relname, (SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM (SELECT acl, row_n FROM pg_catalog.unnest(coalesce(c.relacl,pg_catalog.acldefault(CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END::"char",c.relowner))) WITH ORDINALITY AS perm(acl,row_n) WHERE NOT EXISTS ( SELECT 1 FROM pg_catalog.unnest(coalesce(pip.initprivs,pg_catalog.acldefault(CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END::"char",c.relowner))) AS init(init_acl) WHERE acl = init_acl)) as foo) AS relacl, (SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM (SELECT acl, row_n FROM pg_catalog.unnest(coalesce(pip.initprivs,pg_catalog.acldefault(CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END::"char",c.relowner))) WITH ORDINALITY AS initp(acl,row_n) WHERE NOT EXISTS ( SELECT 1 FROM pg_catalog.unnest(coalesce(c.relacl,pg_catalog.acldefault(CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END::"char",c.relowner))) AS permp(orig_acl) WHERE acl = orig_acl)) as foo) as rrelacl, NULL AS initrelacl, NULL as initrrelacl, c.relkind, c.relnamespace, (SELECT rolname FROM pg_catalog.pg_roles WHERE oid = c.relowner) AS rolname, c.relchecks, c.relhastriggers, c.relhasindex, c.relhasrules, 'f'::bool AS relhasoids, c.relrowsecurity, c.relforcerowsecurity, c.relfrozenxid, c.relminmxid, tc.oid AS toid, tc.relfrozenxid AS tfrozenxid, tc.relminmxid AS tminmxid, c.relpersistence, c.relispopulated, c.relreplident, c.relpages, am.amname, CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, d.refobjid AS owning_tab, d.refobjsubid AS owning_col, (SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, tc.reloptions AS toast_reloptions, c.relkind = 'S' AND EXISTS (SELECT 1 FROM pg_depend WHERE classid = 'pg_class'::regclass AND objid = c.oid AND objsubid = 0 AND refclassid = 'pg_class'::regclass AND deptype = 'i') AS is_identity_sequence, EXISTS (SELECT 1 FROM pg_attribute at LEFT JOIN pg_init_privs pip ON (c.oid = pip.objoid AND pip.classoid = 'pg_class'::regclass AND pip.objsubid = at.attnum)WHERE at.attrelid = c.oid AND ((SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM (SELECT acl, row_n FROM pg_catalog.unnest(coalesce(at.attacl,pg_catalog.acldefault('c',c.relowner))) WITH ORDINALITY AS perm(acl,row_n) WHERE NOT EXISTS ( SELECT 1 FROM pg_catalog.unnest(coalesce(pip.initprivs,pg_catalog.acldefault('c',c.relowner))) AS init(init_acl) WHERE acl = init_acl)) as foo) IS NOT NULL OR (SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM (SELECT acl, row_n FROM pg_catalog.unnest(coalesce(pip.initprivs,pg_catalog.acldefault('c',c.relowner))) WITH ORDINALITY AS initp(acl,row_n) WHERE NOT EXISTS ( SELECT 1 FROM pg_catalog.unnest(coalesce(at.attacl,pg_catalog.acldefault('c',c.relowner))) AS permp(orig_acl) WHERE acl = orig_acl)) as foo) IS NOT NULL OR NULL IS NOT NULL OR NULL IS NOT NULL))AS changed_acl, pg_get_partkeydef(c.oid) AS partkeydef, c.relispartition AS ispartition, pg_get_expr(c.relpartbound, c.oid) AS partbound FROM pg_class c LEFT JOIN pg_depend d ON (c.relkind = 'S' AND d.classid = c.tableoid AND d.objid = c.oid AND d.objsubid = 0 AND d.refclassid = c.tableoid AND d.deptype IN ('a', 'i')) LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid AND c.relkind <> 'p') LEFT JOIN pg_am am ON (c.relam = am.oid) LEFT JOIN pg_init_privs pip ON (c.oid = pip.objoid AND pip.classoid = 'pg_class'::regclass AND pip.objsubid = 0) WHERE c.relkind in ('r', 'S', 'v', 'c', 'm', 'f', 'p') ORDER BY c.oid
pg_dumpall: error: pg_dump failed on database "gitlabhq_production", exiting

Я еще не знал, каков путь меня ждёт и сразу скажу, что я не являюсь DBA и могу как-то описывать тут неправильные выводы или использовать неправильные термины.

Восстановление было растянуто во времени и что бы статья была максимально подробная, буду стараться вставлять реальные ошибки из логов моего любимого терминального клиента SecureCRT. Пользуясь случаем, крайне рекомендую сам клиент и его функцию ведения логов вывода консоли. Сам пользуюсь им давно и даже купил лицензию и логов у меня с 2008 года уже накопилось 24 гига в том или ином виде.

Самое интересное при всём при этом, что сам Gitlab успешно работал и использовался, проблем с ним никаких не было, просто пришло время обновить, но база не дампилась. Стоит пару слов сказать о самом сервере. Сам Gitlab, как я и говорил, запущен в докере, докер в Debian, Debian на виртуалке в Proxmox, само железо арендованное. Вспомнилось, что некоторое время назад с этим физическим сервером были проблемы - на нём самопроизвольно перезагружалась виртуалка с Gitlab. Тогда сервер по заявке в поддержку хостера заменили, проблема ушла и с тех пор про неё забыли. Сама виртуалка бекапится с помощью Proxmox Backup Server и как-то внутренним бекапам Gitlab никто не придал значения, а уже тогда (проблема железом была больше года назад) Gitlab перестал бекапить базу в своём автоматическом бекапе. К слову, что интересно - бекапы сами делались, но с пустой базой. Что с этим делать и как мониторить - отдельная тема, но вот такой вот интересный факт.

Понимание масштабов ситуации

Наверное стоит упомянуть версии ПО: sameersbn/gitlab:14.9.1 и sameersbn/postgresql:12-20200524, с которыми я и проводил работы, но сути сильно это не меняет. Долгое и упорное гугление ничего не давало. Ситуацию еще осложняла ошибка MultiXactId, которая по факту никакого отношения к проблеме не имела, а была лишь следствием. Всякие советы по вакууму и реиндексу не помогали. Пока даже не понятно было что случилось - но было очевидно, имеется фатальное повреждение базы, которое однако почему-то не сломало Gitlab тогда, когда эти сбои происходили - быть может тогда бы восстановили из встроенного бекапа Gitlab, который ещё не успел сротироваться и не было бы повода для статьи. Эта ошибка с MultiXactId выскакивала почти при каждом запросе. VACUUM так же не работал. Разумеется я попытался запустить этот запрос вручную. Я в целом с таким вообще никогда не сталкивался, а в интернете было информации ничтожно мало.

Начал я с того, что сделал beautify для SQL запроса из ошибки и понемногу стал удалять из него элементы, что бы найти сбойную таблицу.

Скрытый текст
SELECT c.tableoid, c.oid, c.relname, (SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM (SELECT acl, row_n FROM pg_catalog.unnest(coalesce(c.relacl,pg_catalog.acldefault(CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END::"char",c.relowner))) WITH ORDINALITY AS perm(acl,row_n) WHERE NOT EXISTS ( SELECT 1 FROM pg_catalog.unnest(coalesce(pip.initprivs,pg_catalog.acldefault(CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END::"char",c.relowner))) AS init(init_acl) WHERE acl = init_acl)) as foo) AS relacl, (SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM (SELECT acl, row_n FROM pg_catalog.unnest(coalesce(pip.initprivs,pg_catalog.acldefault(CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END::"char",c.relowner))) WITH ORDINALITY AS initp(acl,row_n) WHERE NOT EXISTS ( SELECT 1 FROM pg_catalog.unnest(coalesce(c.relacl,pg_catalog.acldefault(CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END::"char",c.relowner))) AS permp(orig_acl) WHERE acl = orig_acl)) as foo) as rrelacl, NULL AS initrelacl, NULL as initrrelacl, c.relkind, c.relnamespace, (SELECT rolname FROM pg_catalog.pg_roles WHERE oid = c.relowner) AS rolname, c.relchecks, c.relhastriggers, c.relhasindex, c.relhasrules, 'f'::bool AS relhasoids, c.relrowsecurity, c.relforcerowsecurity, c.relfrozenxid, c.relminmxid, tc.oid AS toid, tc.relfrozenxid AS tfrozenxid, tc.relminmxid AS tminmxid, c.relpersistence, c.relispopulated, c.relreplident, c.relpages, am.amname, CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, d.refobjid AS owning_tab, d.refobjsubid AS owning_col, (SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, tc.reloptions AS toast_reloptions, c.relkind = 'S' AND EXISTS (SELECT 1 FROM pg_depend WHERE classid = 'pg_class'::regclass AND objid = c.oid AND objsubid = 0 AND refclassid = 'pg_class'::regclass AND deptype = 'i') AS is_identity_sequence, EXISTS (SELECT 1 FROM pg_attribute at LEFT JOIN pg_init_privs pip ON (c.oid = pip.objoid AND pip.classoid = 'pg_class'::regclass AND pip.objsubid = at.attnum)WHERE at.attrelid = c.oid AND ((SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM (SELECT acl, row_n FROM pg_catalog.unnest(coalesce(at.attacl,pg_catalog.acldefault('c',c.relowner))) WITH ORDINALITY AS perm(acl,row_n) WHERE NOT EXISTS ( SELECT 1 FROM pg_catalog.unnest(coalesce(pip.initprivs,pg_catalog.acldefault('c',c.relowner))) AS init(init_acl) WHERE acl = init_acl)) as foo) IS NOT NULL OR (SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM (SELECT acl, row_n FROM pg_catalog.unnest(coalesce(pip.initprivs,pg_catalog.acldefault('c',c.relowner))) WITH ORDINALITY AS initp(acl,row_n) WHERE NOT EXISTS ( SELECT 1 FROM pg_catalog.unnest(coalesce(at.attacl,pg_catalog.acldefault('c',c.relowner))) AS permp(orig_acl) WHERE acl = orig_acl)) as foo) IS NOT NULL OR NULL IS NOT NULL OR NULL IS NOT NULL))AS changed_acl, pg_get_partkeydef(c.oid) AS partkeydef, c.relispartition AS ispartition, pg_get_expr(c.relpartbound, c.oid) AS partbound FROM pg_class c LEFT JOIN pg_depend d ON (c.relkind = 'S' AND d.classid = c.tableoid AND d.objid = c.oid AND d.objsubid = 0 AND d.refclassid = c.tableoid AND d.deptype IN ('a', 'i')) LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid AND c.relkind <> 'p') LEFT JOIN pg_am am ON (c.relam = am.oid) LEFT JOIN pg_init_privs pip ON (c.oid = pip.objoid AND pip.classoid = 'pg_class'::regclass AND pip.objsubid = 0) WHERE c.relkind in ('r', 'S', 'v', 'c', 'm', 'f', 'p') ORDER BY c.oid

Относительно быстро я нашёл её, это оказалась системная таблица pg_depend (на тот момент я вообще не знал, о существовании системных таблиц в пользовательской базе).

Скрытый текст
gitlabhq_production=# select * from pg_depend;
ERROR:  MultiXactId 1598374738 has not been created yet -- apparent wraparound
gitlabhq_production=#

Наверное, можно было бы пробежаться с select по всем таблицам и было бы проще, но как есть. В целом я так и сделал в основной базе и даже нашёл еще пару-тройку битых таблиц, но про системные не знал. Простой select * from table к слову, как я понял, не обращается особо к другим таблицам или данным, поэтому если select * from table выполняется, значит скорее всего с таблицей всё ок, но это не точно. Далее еще всякие танцы с бубном, не приводившие ни к чему полезному и вот следующий этап, который мне сильно помог:

Скрытый текст
gitlabhq_production=# \set FETCH_COUNT 1
gitlabhq_production=# \pset pager off
Pager usage is off.
gitlabhq_production=# select * from pg_depend;
 classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype 
---------+-------+----------+------------+----------+-------------+---------
       0 |     0 |        0 |       1259 |     1247 |           0 | p
       0 |     0 |        0 |       1259 |     1249 |           0 | p
       0 |     0 |        0 |       1259 |     1255 |           0 | p
....
    2606 | 30268 |        0 |       2606 |    29964 |           0 | P
    2606 | 30268 |        0 |       1259 |    30256 |           0 | S
    2620 | 30269 |        0 |       2606 |    30268 |           0 | i
ERROR:  MultiXactId 1598374738 has not been created yet -- apparent wraparound
gitlabhq_production=#

Вот оно! Таблица читается, но не вся. К слову о таблице - данная таблица влияет на взаимосвязи между таблицами, CONSTRAINT и прочие вещи. Полного её назначения я не знаю, но как и все системные таблицы, она крайне важна.

Далее, я знаю что в базах некие системные rowid, это айди записи, как потом выяснилось - конкретный адрес записи в файле данных. В Postgres это ctid, он состоит из номера блока (page number) и номер строки (tuple index), записанные через запятую, например: (207,4), где 207 - блок, 4 - строка. Забегая вперёд, блоки в Postgres состоят в основном из 8192 байт, но поговаривают, что они могут быть и больше, но это нужно самостоятельно настроить для всей базы при создании.

Построчная вычитка с помощью SELECT ctid FROM pg_depend приводит к тому, что мы узнаём последний номер ctid который успешно читается, это (206,136). Однако пока не понятно, что за блок не читается. Далее было много лишних телодвижений, но в итоге с помощью нехитрого Bash скрипта

  #!/bin/bash
  for x in `seq 0 1000`; do
    for y in `seq 0 200`; do
      echo "select ctid, * from pg_catalog.pg_depend where ctid='($x,$y)';" | psql -t -A -U postgres gitlabhq_production
    done
  done

вычитываем построчно всё что можем вычитать. Максимальные значения были выбраны почти "по ощущениям" и это дало нам очень полезный результат!

Скрытый текст
(206,132)|2606|30268|0|1259|17572|1|n
(206,133)|2606|30268|0|1259|22228|0|n
(206,134)|2606|30268|0|2606|29964|0|P
(206,135)|2606|30268|0|1259|30256|0|S
(206,136)|2620|30269|0|2606|30268|0|i
(207,1)|0|0|0|0|0|0|
(207,2)|375904|0|-221437903|3199483378|376064||
(207,3)|0|0|0|0|0|0|
(207,4)|373704|0|-100282108|2438198836|374440||
(207,5)|||752222208|101427677|||
(207,9)|384528|0|-2015093411|1151039455|384792||
(207,11)|375808|0|176834693|3193612013|375904||
(207,12)|1869229892|375808|0|176834693|3193612013|375904|
(207,13)|374440|0|-358853698|66464485|374632||
(207,14)|2304||-221437903|3199483378|376064|0|
(207,17)|||||||
(207,19)|107|0|-1176052746|1299764244|8988144|2304|
(207,20)|381760|0|1526214711|3556947514|955880|2304|
(207,21)|3103653888||702744189|3537949769||5|
(207,22)|373376|0|-211852572|1035775843|373536||
(207,23)|3917554967||-762286818|5|3103653888||}
(207,24)|372168|0|1390896631|3068173583|372312||
(207,25)|||648216576|2669248653|||
(207,26)|370280|0|1311310176|1810887090|371216||
(207,30)|960280|0|1281066324|1642208305|960392|2304|
(207,38)|1398734848|1129469263|1163026245|1230261313|1415529805|1397050697|T
(207,39)|375904|0|-221437903|3199483378|376064||
(207,40)|245831680|0|246022144|0|0|0|
(207,41)|374912|0|1179030283|1583091848|375000||
(207,43)|371680|0|-2082285372|420775742|371800||
(207,44)|3187671040|41589|989855744|1659572635|1358956005|672164266|^D
(207,45)|||||||
(207,47)|||||||
(207,48)|||||||
(207,50)|380024|0|1762844205|3509088649|380128||
(207,51)|5|308740096|1945549489||3462415183|5|
(207,52)|373048|0|-619517725|4000679893|373376||
(207,54)|||||||
(207,55)|372040|0|1158210242|1292651287|372168||
(207,56)|420775742|371800|0|951012362|2363409229|371920|
(207,57)|369696|0|-1957095774|311830979|370280||
(207,58)|3170893824|41589|-1090519040|1659572634|3573548517|672164265|^D
(207,67)|||||||
(207,68)|378176|0|-2129042726|1227044554|955880|2304|
(207,70)|373536|0|-1214886098|1851543104|373704||
(207,72)|372312|0|2084924075|3987929448|372448||
(207,74)|371216|0|-384242661|1064179263|371536||
(207,75)||2915585292|||5||
(207,76)|3|0|496|0|10646972|2304|
(207,77)|2332033033|1659572633|-1593834011|672164264|1778384900|1870269687|^T
(207,78)|1163026245|1230261313|1415529805|1397050697|1347240276|892743997|9
(207,79)|6|0|56|0|963552|2304|
(207,81)|372904|0|882956738|4129247745|373048||
(207,82)|||14|2911633408|||
(207,83)|371920|0|-61377230|780583400|372040||
(207,84)|0|4194685188|-1856768460|955880|0|-1913277790|^Q
(207,85)|370808|0|288496579|3468725590|370944||
(207,86)|1328549888|2592719621|95385612|0|3911074304|1548724696|@
(207,87)|369696|0|-1957095774|311830979|369840||
(207,89)|1259|30317|0|1259|18190|3|
(207,98)|2604|30328|0|1259|29655|8|a
(207,99)|1259|30329|0|1259|29655|7|a
(207,108)|1259|30336|0|1259|18318|2|a
(207,109)|1259|30336|0|1259|18318|14|a
(207,110)|1259|30336|0|1259|18318|4|a
(207,111)|1259|30337|0|1259|18318|14|a

Становится очевидным, что вот они - повреждённые данные и это блок 207, но до примерно 98 строки. Судя по логам SecureCRT, я пытался что-то сделать средствами БД, но по факту всё безуспешно. Например обнаружил для себя полезный запрос:

Скрытый текст
SELECT c.oid::regclass as table_name,
       greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as age
FROM pg_class c
LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
WHERE c.relkind IN ('r', 'm');

Чатгопота говорит про этот запрос следующее:

Результатом этого запроса будет список всех обычных таблиц и материализованных представлений в базе данных с указанием их имени и возраста транзакций. Эти данные важны для отслеживания необходимости выполнения команды VACUUM FREEZE, чтобы избежать риска переполнения транзакционных идентификаторов (wraparound).

Далее я делаю VACUUM freeze по-таблично, т.к. на всю базу он не срабатывает и этот запрос очевидно показывает, с какими таблицами проблемы. Это: pg_depend, ci_pipelines_config, ci_builds_metadata.

Я провёл еще какие-то исследования, но пришёл к не утешительному выводу - надо чинить pg_depend. Выполнил простой запрос, который выдаёт по сути имя файла и взглянул на сам файлик в текстовом формате и случайно нашёл источник проблемы:

Скрытый текст

Эта область сильно отличается от остальных данных таблицы, однако без знания структуры лезть туда hex-редактором нет смысла и еще много часов гугления приводят к неутешительным выводам, что средствами БД восстановить таблицу не получится. Друг DBA сказал, что шансов мало и он это никогда не делал.

Вообще я нашёл статейку неплохую https://pgconf.ru/media/2023/04/20/PGConf-2023_Data-Recovery_EBrednya.pdf примерно про это, но не сильно подробную и понятную.
Так же я наткнулся на проект pg_hexedit на гитхабе, меня он сильно заинтересовал, однако немного пугала сырость вообще всего ПО, необходимость собрать кучу всего, отсутствие желание делать это на рабочем компе. В общем вопрос отложился на несколько месяцев - нужен был еще один подход.

Низкоуровневое восстановление повреждённых таблиц

С новыми силами, я приступил к восстановлению. Установил Ubuntu с GUI (хоть GUI в линуксе я не люблю, но тут исключительный случай) в виртуалке, начал собирать это ПО.

Суть данного ПО в том, что pg_hexedit - это средство для разметки файла согласно структуре данных, а WxHexEditor умеет с этой разметкой работать и всё. Выглядит в конечном виде это примерно так:

Требования для установки в Ubuntu:

  • Должен быть собран из исходников и установлен wxHexEditor из мастера - это версия 0.25 beta

    при сборке закомментить #LIBS += -lgomp в Makefile

    установить кроме всего прочего python-is-python3 libtool build-essential autoconf gettext gcc

    может быть еще что-то надо.

  • Должен быть собран из исходников и установлен postgres необходимой версии

    добавить путь PATH=$PATH:/usr/local/pgsql/bin

  • Должен быть собран из исходников и установлен pg_hexedit

  • Должен быть запущен чистый Gitlab проблемной версией с чистым Postgres проблемной версии - это нужно, чтоб сдампить структуры сломанных таблиц, т.к в дальнейшим их придётся переливать с помощью хитростей.

Должен быть запущен чистый Postgres проблемной версии - для основных работ.

Начинаем восстановление таблиц pg_depend, ci_pipelines_config, ci_builds_metadata.

  1. pg_depend:

    Получаем номер файла для проблемной таблицы
    SELECT relfilenode FROM pg_class WHERE relname = 'pg_depend';
    2608
    Исходя из ошибок Postgres проблемы в 207 блоке.
    Полный скан pg_hexedit 2608 > 2608.tags это подтверждает. Оставлю тут огромную простыню, чтоб было понятно, как в целом работает pg_hexedit - больше так делать не буду :)

    Скрытый текст
    root@ubuntu24-test:/opt# pg_hexedit 2608 > 2608.tags 
    pg_hexedit error: computed header length not equal to header size.
    computed: 32 header: 150
    pg_hexedit error: computed header length not equal to header size.
    computed: 32 header: 150
    pg_hexedit error: lp_len 49 from (207,1) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 231
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 231
    pg_hexedit error: lp_len 49 from (207,3) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 56 header: 222
    pg_hexedit error: computed header length not equal to header size.
    computed: 56 header: 222
    pg_hexedit error: lp_len 49 from (207,5) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 32 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 32 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 168 header: 95
    pg_hexedit error: computed header length not equal to header size.
    computed: 168 header: 95
    pg_hexedit error: lp_len 49 from (207,7) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 88 header: 61
    pg_hexedit error: computed header length not equal to header size.
    computed: 88 header: 61
    pg_hexedit error: lp_len 49 from (207,10) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 32 header: 52
    pg_hexedit error: computed header length not equal to header size.
    computed: 32 header: 52
    pg_hexedit error: lp_len 49 from (207,12) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 136 header: 188
    pg_hexedit error: computed header length not equal to header size.
    computed: 136 header: 188
    pg_hexedit error: lp_len 49 from (207,14) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 48 header: 56
    pg_hexedit error: computed header length not equal to header size.
    computed: 48 header: 56
    pg_hexedit error: lp_len 49 from (207,15) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 200 header: 84
    pg_hexedit error: computed header length not equal to header size.
    computed: 200 header: 84
    pg_hexedit error: lp_len 49 from (207,18) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 192 header: 30
    pg_hexedit error: computed header length not equal to header size.
    computed: 192 header: 30
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 208 header: 130
    pg_hexedit error: computed header length not equal to header size.
    computed: 208 header: 130
    pg_hexedit error: lp_len 49 from (207,23) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 56 header: 222
    pg_hexedit error: computed header length not equal to header size.
    computed: 56 header: 222
    pg_hexedit error: lp_len 49 from (207,25) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 96 header: 76
    pg_hexedit error: computed header length not equal to header size.
    computed: 96 header: 76
    pg_hexedit error: lp_len 49 from (207,27) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 51
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 51
    pg_hexedit error: lp_len 49 from (207,28) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 32 header: 150
    pg_hexedit error: computed header length not equal to header size.
    computed: 32 header: 150
    pg_hexedit error: lp_len 49 from (207,38) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 231
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 231
    pg_hexedit error: lp_len 49 from (207,40) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 83
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 83
    pg_hexedit error: lp_len 49 from (207,42) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 5
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 5
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 176 header: 182
    pg_hexedit error: computed header length not equal to header size.
    computed: 176 header: 182
    pg_hexedit error: lp_len 49 from (207,49) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 264 header: 90
    pg_hexedit error: computed header length not equal to header size.
    computed: 264 header: 90
    pg_hexedit error: lp_len 49 from (207,51) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 64 header: 92
    pg_hexedit error: computed header length not equal to header size.
    computed: 64 header: 92
    pg_hexedit error: lp_len 49 from (207,54) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 32 header: 20
    pg_hexedit error: computed header length not equal to header size.
    computed: 32 header: 20
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 5
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 5
    pg_hexedit error: computed header length not equal to header size.
    computed: 200 header: 84
    pg_hexedit error: computed header length not equal to header size.
    computed: 200 header: 84
    pg_hexedit error: lp_len 49 from (207,66) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 280 header: 96
    pg_hexedit error: computed header length not equal to header size.
    computed: 280 header: 96
    pg_hexedit error: lp_len 49 from (207,69) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 176 header: 31
    pg_hexedit error: computed header length not equal to header size.
    computed: 176 header: 31
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 32 header: 134
    pg_hexedit error: computed header length not equal to header size.
    computed: 32 header: 134
    pg_hexedit error: lp_len 49 from (207,73) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 136 header: 150
    pg_hexedit error: computed header length not equal to header size.
    computed: 136 header: 150
    pg_hexedit error: lp_len 49 from (207,75) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 77
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 77
    pg_hexedit error: lp_len 49 from (207,77) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 70
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 70
    pg_hexedit error: lp_len 49 from (207,78) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 83
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 83
    pg_hexedit error: lp_len 49 from (207,80) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 200 header: 154
    pg_hexedit error: computed header length not equal to header size.
    computed: 200 header: 154
    pg_hexedit error: lp_len 49 from (207,82) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 236
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 236
    pg_hexedit error: lp_len 49 from (207,84) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 239
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 239
    pg_hexedit error: lp_len 49 from (207,86) is undersized
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 24 header: 0
    pg_hexedit error: computed header length not equal to header size.
    computed: 96 header: 76
    pg_hexedit error: computed header length not equal to header size.
    computed: 96 header: 76
    pg_hexedit error: lp_len 49 from (207,88) is undersized
    pg_hexedit notice: PostgreSQL frontend program return code is 1 (failure)
    root@ubuntu24-test:/opt# 

    Делаем pg_hexedit -R 205 208 2608 > 2608.tags
    Чтобы видно было соседние блоки, но не все блоки - т.к. из-за этого сильно тормозит WxHexEditor
    Открываем в WxHexEditor и удаляем блок 207 (зануляем его содержимое) согласно размеру 8192

    Хочу отдельно упомянуть, почему я решил удалять (а точней занулять). Я документацию особо не читал, чатгопота особо ничего полезного про это не сказала и изначально я хотел как-то зачистить только повреждённые данные, но не убивать весь блок 207. Сначала пробовал просто зачистить область данных, затем пометить заголовки, что данные LP_DEAD (как мне казалось - это значит что данные удалены), т.к. в основном строчек было LP_NORMAL, но что-то всё равно файл не нравился ни Postgres, ни при повторном считывании в pg_hexedit. Ну и пришлось принять в итоге единственное верное решение - занулить весь блок (не помню где я вычитал, что так вообще можно, но в итоге сработало).

    Тут хочу дать пару подсказок по WxHexEditor. Блок, в целом, неплохо размечен, начинается с 00 00 00 00 и имеет строгий размер 8192, как и говорил ранее. Выделяем мышкой блок, так чтоб цифра справа снизу была именно такой идём в меню Edit и там Fill selection - зануляем. Однако чтоб меню открылось (во всяком случае у меня такая проблема была), надо мышкой выйти на область, у которой нет разметки, чтоб не было подсказки, тогда меню откроется - иначе ничего не происходит при нажатии на меню.

    Штош, таблица ожила в postgres и вообще почти все остальные таблицы стали успешно читаться pg_dump. Это успех! А успех нужно закрепить.

  2. Далее я добавляю в pg_depend оставшиеся живые данные из блока 207, которые мне удалось ранее считать с помощью скрипта. Помним - что каждая строчка в этой таблице важна, даже если это не очевидно.

    Скрытый текст
      INSERT INTO pg_depend (classid, objid, objsubid, refclassid, refobjid, refobjsubid, deptype)
      VALUES
      (2604, 30328, 0, 1259, 29655, 8, 'a'),
      (1259, 30329, 0, 1259, 29655, 7, 'a'),
      (1259, 30336, 0, 1259, 18318, 2, 'a'),
      (1259, 30336, 0, 1259, 18318, 14, 'a'),
      (1259, 30336, 0, 1259, 18318, 4, 'a'),
      (1259, 30337, 0, 1259, 18318, 14, 'a'),
      (1259, 30337, 0, 1259, 18318, 4, 'a'),
      (1259, 18326, 0, 1259, 18318, 14, 'a'),
      (2604, 30343, 0, 1259, 18318, 14, 'a'),
      (2604, 30343, 0, 1259, 18326, 0, 'n'),
      (2606, 30345, 0, 1259, 18318, 14, 'a'),
      (1259, 30335, 0, 2606, 30345, 0, 'i'),
      (1259, 30347, 0, 1259, 18494, 3, 'a'),
      (1259, 30347, 0, 1259, 18494, 10, 'a'),
      (1259, 30347, 0, 1259, 18494, 7, 'a'),
      (1259, 18502, 0, 1259, 18494, 10, 'a'),
      (2604, 30353, 0, 1259, 18494, 10, 'a');

  3. ci_pipelines_config:

    Аналогично пункту 1, однако pg_hexedit не видит вообще блок 5, потому что его заголовки куда-то пропали, однако сам Postgres на это ругается и прямо указывает что проблема с 5 блоком.
    SELECT relfilenode FROM pg_class WHERE relname = 'ci_pipelines_config';
    18412
    pg_hexedit -R 4 6 18412 > 18412.tags
    зануляем блок 5

  4. ci_builds_metadata

    Аналогично пункту 1.
    SELECT relfilenode FROM pg_class WHERE relname = 'ci_builds_metadata';
    18247
    pg_hexedit -R 0 1 18247 > 18247.tags
    удалить блок 0 (прям в этом случае вырезаем так же через меню edit -> cut, потому что он в самом начале и можно просто удалить его)

  5. Подкидываем восстановленные файлы в папку Postgres, не забываем про права и доступы у файлов, запускаем Postgres и радуемся, что всё было не зря.

Успех мы закрепили, больше поделать с файлами мы ничего не можем. Повреждения остались, но лишь в виде отсутствующих данных, которые мы никак не можем восстановить. Но Postgres уже работает адекватно, хоть и с небольшими ошибками и дальнейшие действия можно уже проводить при помощи него и его инструментов.

Восстановление базы Gitlab

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

    Скрытый текст
    pg_dump: error: query failed: ERROR:  cache lookup failed for index 0
    pg_dump: error: query was: SELECT t.tableoid, t.oid, t.relname AS indexname, inh.inhparent AS parentidx, pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, i.indnkeyatts AS indnkeyatts, i.indnatts AS indnatts, i.indkey, i.indisclustered, i.indisreplident, c.contype, c.conname, c.condeferrable, c.condeferred, c.tableoid AS contableoid, c.oid AS conoid, pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, (SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, t.reloptions AS indreloptions, (SELECT pg_catalog.array_agg(attnum ORDER BY attnum)   FROM pg_catalog.pg_attribute   WHERE attrelid = i.indexrelid AND     attstattarget >= 0) AS indstatcols,(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum)   FROM pg_catalog.pg_attribute   WHERE attrelid = i.indexrelid AND     attstattarget >= 0) AS indstatvals FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) LEFT JOIN pg_catalog.pg_constraint c ON (i.indrelid = c.conrelid AND i.indexrelid = c.conindid AND c.contype IN ('p','u','x')) LEFT JOIN pg_catalog.pg_inherits inh ON (inh.inhrelid = indexrelid) WHERE i.indrelid = '30306'::pg_catalog.oid AND (i.indisvalid OR t2.relkind = 'p') AND i.indisready ORDER BY indexname

    В отличие от первоначальной ошибки, видим небольшую деталь запроса i.indrelid = '30306'::pg_catalog.oid

    Спрашиваем чатгопоту, как найти таблицу, с которой проблема и как ни странно, это делается элементарным запросом SELECT relname FROM pg_class WHERE oid = 30306;

    Так находим еще несколько проблемных таблиц, постепенно добавляя их в --exclude-table

  2. Делаем дамп таблиц, кроме тех на которых возникает ошибка - с ними разберёмся потом.

        pg_dump -hlocalhost -Upostgres -dgitlabhq_production \
        --exclude-table='detached_partitions' \
        --exclude-table='gitlab_partitions_dynamic.incident_management_pending_alert_escalations_202210'  \
        --exclude-table='events' \
        --exclude-table='ci_job_artifacts' \
        -f 6_gitlabhq_production.sql
    
    • gitlab_partitions_dynamic.incident_management_pending_alert_escalations_202210 - пересоздадим в пункте 6 (по аналогии с 202209)

    • detached_partitions - пустая, пересоздадим на основе чистого образа Gitlab той самой версии и Postgres и дампа базы

    • на events ссылается push_event_payloads

    • на ci_job_artifacts ссылается ci_build_trace_metadata ci_job_artifact_states
      ссылки важны, но в целом проблемы с ними нет.

  3. Решаем вопрос с gitlab_partitions_dynamic.incident_management_pending_alert_escalations_202210. Добавить в дамп после строки:

    ALTER TABLE gitlab_partitions_dynamic.incident_management_pending_alert_escalations_202209 OWNER TO gitlab;

    Cтроки

    --
    -- Name: incident_management_pending_alert_escalations_202210; Type: TABLE; Schema: gitlab_partitions_dynamic; Owner: gitlab
    --
    
    CREATE TABLE gitlab_partitions_dynamic.incident_management_pending_alert_escalations_202210 (
        id bigint DEFAULT nextval('public.incident_management_pending_alert_escalations_id_seq'::regclass) NOT NULL,
        rule_id bigint NOT NULL,
        alert_id bigint NOT NULL,
        process_at timestamp with time zone NOT NULL,
        created_at timestamp with time zone NOT NULL,
        updated_at timestamp with time zone NOT NULL
    );
    ALTER TABLE ONLY public.incident_management_pending_alert_escalations ATTACH PARTITION gitlab_partitions_dynamic.incident_management_pending_alert_escalations_202210 FOR VALUES FROM ('2022-10-01 00:00:00+00') TO ('2022-11-01 00:00:00+00');
    
    
    ALTER TABLE gitlab_partitions_dynamic.incident_management_pending_alert_escalations_202210 OWNER TO gitlab;
    
  4. Сохраняем схему detached_partitions, events, ci_job_artifacts на чистом Postgres

     pg_dump -hlocalhost -Upostgres -dgitlabhq_production -t 'detached_partitions' --schema-only > 5_detached_partitions.sql
     pg_dump -hlocalhost -Upostgres -dgitlabhq_production -t 'events' --schema-only > 3_events.sql
     pg_dump -hlocalhost -Upostgres -dgitlabhq_production -t 'ci_job_artifacts' --schema-only > 1_ci_job_artifacts.sql
    
  5. Копируем данные из таблиц на восстанавливаемом Postgres, которые не дампились из-за сломанных зависимостей

    CREATE TABLE copy_ci_job_artifacts AS SELECT * FROM ci_job_artifacts;
    CREATE TABLE copy_events AS SELECT * FROM events;
  6. Дампим скопированные таблицы

    pg_dump -hlocalhost -Upostgres -dgitlabhq_production -t copy_ci_job_artifacts --data-only -f 2_copy_ci_job_artifacts.sql
    pg_dump -hlocalhost -Upostgres -dgitlabhq_production -t copy_events --data-only -f 4_copy_events.sql

    Нужно заменить в дампах copy_ на пусто

  7. Итоговое описание файлов:

    • 1_ci_job_artifacts.sql - структура таблицы ci_job_artifacts. В файле нужно убрать последние строчки про CONSTRAINT и TRIGGER в 7_post

    • 2_copy_ci_job_artifacts.sql - данные таблицы ci_job_artifacts

    • 3_events.sql - структура events, убрать последние строчки про CONSTRAINT и TRIGGER в 7_post

    • 4_copy_events.sql - данные events

    • 5_detached_partitions.sql - структура detached_partitions

    • 6_gitlabhq_production.sql - основной дамп

    • 7_post.sql - то что убирали

  8. Переименовываем старую базу и создаём новую чистую, рабочую

    ALTER DATABASE gitlabhq_production RENAME TO gitlabhq_production_broken; create database gitlabhq_production;
    
  9. Заливаем получившиеся данные в чистую таблицу

    psql -hlocalhost -Upostgres -dgitlabhq_production -f 1_ci_job_artifacts.sql 2>log_1.txt
    psql -hlocalhost -Upostgres -dgitlabhq_production -f 2_copy_ci_job_artifacts.sql 2>log_2.txt
    psql -hlocalhost -Upostgres -dgitlabhq_production -f 3_events.sql 2>log_3.txt
    psql -hlocalhost -Upostgres -dgitlabhq_production -f 4_copy_events.sql 2>log_4.txt
    psql -hlocalhost -Upostgres -dgitlabhq_production -f 5_detached_partitions.sql 2>log_5.txt
    psql -hlocalhost -Upostgres -dgitlabhq_production -f 6_gitlabhq_production.sql 2>log_6.txt
    psql -hlocalhost -Upostgres -dgitlabhq_production -f 7_post.sql 2>log_7.txt
  10. Делаем финальный рабочий дамп для заливки в рабочий Postgres:

    pg_dump -hlocalhost -Upostgres -dgitlabhq_production -f final_gitlabhq_production.sql
    

Ну а дальше кажется должно быть понятно, что делать. Успешный успех достигнут, можно обновлять Gitlab. Данные конечно всё равно там частично повреждены, но какие - покажет время. С виду кажется, что повреждение не сильно критичное. Возможно где-то есть пару не состыковок, но каких за всё время эксплуатации повреждённого Gitlab выявлено не было. В случае, если бы совсем ничего не вышло бы (с восстановлением pg_depend), пришлось бы вручную пересоздавать всех пользователей, либо делать какие-то действия аналогичные CREATE TABLE copy_events AS SELECT * FROM events; только для всех таблиц или вообще как-то через csv это восстанавливать. Но с учётом зависимостей, это было бы той еще задачкой.

Выводы

Ну разумеется - делайте бекапы. Даже если бекапы настроены - не забывайте их мониторить. Даже если мониторинг есть - надо по возможности не забывать их тестировать, ведь бекапы могут делаться, но например без маленькой детали - дампа базы данных.

Ну и не бойтесь браться за сомнительные задачи, которые кажутся не выполнимыми. Особенно в целом если есть надежда на успешное решение.

В целом понравился опыт и даже задумывался, как можно переложить на более крупные базы данных - всё то же самое, но возможно аккуратно вырезать повреждённый участок, чтоб было проще работать, а затем вернуть обратно. Например тем же dd. Быть может есть какие-то способы выявления повреждённых блоков, т.к. изначально Postgres ничего не говорил, что фатально повреждён именно блок 207 в pg_depend.

upd. при обновлении Gitlab на опредеделённую версию, всплыла проблема наличия SEQUENCE ci_stages_id_seq в
SELECT * FROM pg_sequences WHERE sequencename = 'ci_stages_id_seq';
но отсутствия в
SELECT * FROM postgres_sequences WHERE seq_name = 'ci_stages_id_seq';
Это как раз проблема повреждения pg_depend. Смог разобраться при помощи пересоздания (чатгопота помогла):

-- Шаг 1: Убираем значение по умолчанию для колонки id
ALTER TABLE ci_stages ALTER COLUMN id DROP DEFAULT;

-- Шаг 2: Удаляем внешний ключ из таблицы p_ci_builds
ALTER TABLE p_ci_builds DROP CONSTRAINT fk_3a9eaa254d;

-- Шаг 3: Удаляем последовательность
DROP SEQUENCE IF EXISTS ci_stages_id_seq;

-- Шаг 4: Создаём новую последовательность
CREATE SEQUENCE ci_stages_id_seq
  START WITH 14876  -- или другое значение, большее максимального id
  INCREMENT BY 1
  NO MINVALUE
  NO MAXVALUE
  CACHE 1;

-- Шаг 5: Привязываем последовательность к колонке id
ALTER TABLE ci_stages ALTER COLUMN id SET DEFAULT nextval('ci_stages_id_seq');

-- Шаг 6: Восстанавливаем внешний ключ
ALTER TABLE p_ci_builds
ADD CONSTRAINT fk_3a9eaa254d FOREIGN KEY (stage_id) REFERENCES ci_stages(id) ON DELETE CASCADE;

-- Шаг 7: Сбрасываем значение последовательности на максимальный id
SELECT setval('ci_stages_id_seq', (SELECT MAX(id) FROM ci_stages));

ALTER SEQUENCE ci_stages_id_seq OWNER TO gitlab;
ALTER SEQUENCE ci_stages_id_seq OWNED BY ci_stages.id;

Комментарии (2)


  1. gsl23
    25.10.2024 09:25

    Вроде более удобным способом можно удалять гланды - https://postgreshelp.com/postgresql-checksum/?source=post_page-----33806f44b1c4-------------------------------
    Хотя, честно говоря, сам таким еще не страдал не пробовал - т.к. бэкапы, enabled pg_checksum, и standby db для критичных данных.


    1. mafet Автор
      25.10.2024 09:25

      Спасибо за статью, действительно очень интересно и похоже чем-то. чексумы полезно по идее, когда standby есть. Хотя может если бы тут чексумы были включены, то база просто бы подняла лапки вверх и мы бы заметили проблему раньше.
      А расчёт более удобного - сомнительно. Наверное лучше сначала через мой способ пройти - когда наглядно видно блоки, а потом уж зачищать их через dd.

      delete from check_corruption where ctid='(0,6)’;

      у меня кстати не срабатывало, я пробовал. Какие-то строчки удалились, какие-то - нет.

      скриптик который в статье там описывается find_bad_row() - по сути я на баше +- написал, бонусом получил не только последнюю прочитавшуюся ctid, но и чуть больше

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

      кстати на одной из таблиц, при попытке селекте, постгрес вообще падал. не уверен, что помог бы zero_damaged_pages и что-то еще