Ошибка, возникающая при выполнении запроса к таблице: "missing chunk number %d for toast value %u in pg_toast_XXXX"
Симптом
В ходе выполнения запроса к таблице сообщается об ошибке "missing chunk number %d for toast value %u in pg_toast_XXXX" или "unexpected chunk number %d (expected %d) for toast value %u in pg_toast_XXXX"
Возможные причины
TOAST — это сокращение от The OverSized Attribute Storage Technique (Техника хранения атрибутов большого размера).
Это метод хранения больших значений столбцов в нескольких физических строках. Если таблица содержит большие значения столбцов, ей будет присвоена таблица TOAST.
как выглядит ошибка :
ERROR: unexpected chunk number 1126199148 (expected 1) for toast value 28433 in pg_toast_28317
если мы не знаем при запросе к какой таблице возникла ошибка, то узнать можно следующим запросом
test=# select 28317::regclass;
regclass
------------
test_toast
Можно попробовать выполнить команды по переиндексации или по сбору статистики таблицы. Если ошибка повторится то переходим к следующему шагу
REINDEX table pg_toast.pg_toast_28317;
REINDEX table test_toast;
VACUUM ANALYZE test_toast;
Нам необходимо найти запись с поврежденным значением поля в toast.
Можно попробовать перебрать таблицу по одной записи :
SELECT * FROM test_toast LIMIT 1 offset 0
SELECT * FROM test_toast LIMIT 1 offset 1
SELECT * FROM test_toast LIMIT 1 offset 2
но если таблица содержит большой объем записей то это будет очень медленно.
Значительно производительнее будет поиск строки, с интервалами
SELECT * FROM test_toast LIMIT 100 offset 0
SELECT * FROM test_toast LIMIT 100 offset 100
SELECT * FROM test_toast LIMIT 100 offset 200
ERROR: unexpected chunk number 1126199148 (expected 1) for toast value 28433 in pg_toast_28317
которые будем постепенно сокращать
SELECT * FROM test_toast LIMIT 10 offset 200
SELECT * FROM test_toast LIMIT 10 offset 210
SELECT * FROM test_toast LIMIT 10 offset 220
SELECT * FROM test_toast LIMIT 10 offset 230
SELECT * FROM test_toast LIMIT 10 offset 240
SELECT * FROM test_toast LIMIT 10 offset 250
ERROR: unexpected chunk number 1126199148 (expected 1) for toast value 28433 in pg_toast_28317
пока не найдем нужную запись
SELECT * FROM test_toast LIMIT 1 offset 250
SELECT * FROM test_toast LIMIT 1 offset 251
SELECT * FROM test_toast LIMIT 1 offset 252
ERROR: unexpected chunk number 1126199148 (expected 1) for toast value 28433 in pg_toast_28317
Таким образом, мы выясняем что ошибка в 252 записи в таблице
Далее нам надо посмотреть структуру таблицы что бы выяснить какие поля хранятся в toast
test=# select att.attname as column_name, att.attstorage column_type from pg_attribute att
join pg_class tbl on tbl.oid = att.attrelid
join pg_namespace ns on tbl.relnamespace = ns.oid
where tbl.relname = 'test_toast' and ns.nspname = 'public' and att.attnum > 0 and not att.attisdropped;
column_name | column_type
-------------+-------------
a | p
b | x
c | p
d | x
pk | p
(5 rows)
test=#
поля с типом "х" - хранятся в таблице тостов, с типом "p" - хранятся в основной таблице
Выполнив запросы с явным указанием полей по данной записи, мы увидим, что "испорченна" запись в поле "b"
test=# SELECT a FROM test_toast LIMIT 1 offset 252;
a
-----
287
(1 row)
test=# SELECT b FROM test_toast LIMIT 1 offset 252;
ERROR: unexpected chunk number 1126199148 (expected 1) for toast value 28433 in pg_toast_28317
test=#
Это поле можно попробовать обновить или удалить все строку из таблицы
test=# update test_toast set b = null where a = 287;
UPDATE 1
test=# SELECT b FROM test_toast LIMIT 1 offset 252;
Здесь приложен скрипт на bash который находит первую испорченную запись в таблице с указанием номера строки и столбца с испорченной записью
В качестве примера, создадим таблицу test_toast
create table test_toast( a int, b text, c float, d varchar(1000) );
alter table test_toast add column pk serial primary key;
Заполним ее тестовыми данными
insert into test_toast select x, repeat( 'alibaba', 5000 * x ), x * 1.2, repeat( 'abc', 100 ) from generate_series( 1, 3000 ) x;
Узнаем имя файла в котором хранится информация с toast
test=# select relname, relfilenode, reltoastrelid, pg_relation_filepath( reltoastrelid ) from pg_class where relkind = 'r' and oid = 'test_toast'::regclass;
relname | relfilenode | reltoastrelid | pg_relation_filepath
------------+-------------+---------------+----------------------
test_toast | 28326 | 28329 | base/16384/28329
(1 row)
Создадим скрипт, с помощь. которого "испортим" файл с данными
postgres@db16:~$ cat corrupt_tuple.pl
#!env perl
open my $db_file, "+<", $ARGV[ 0 ]
|| die "Cannot open data file!\n\n";
seek $db_file, ( 256 * 1024 ) + $ARGV[ 1 ], 0;
print { $db_file } "Hello Corrupted Database!";
close $db_file;
256 * 1024 - здесь сдвиг от начала файла, зависит от объема данных
При остановленном сервисе postgresql выполним :
perl ./corrupt_tuple.pl ./tantor-be-16/data/base/16384/28329 12345
После чего запустить сервис postgresql и провести описанный эксперимент