Симптом

В ходе выполнения запроса к таблице сообщается об ошибке "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
CODE

если мы не знаем при запросе к какой таблице возникла ошибка, то узнать можно следующим запросом

test=# select 28317::regclass;
  regclass
------------
 test_toast
CODE

Можно попробовать выполнить команды по переиндексации или по сбору статистики таблицы. Если ошибка повторится то переходим к следующему шагу

REINDEX table pg_toast.pg_toast_28317;
REINDEX table test_toast;
VACUUM ANALYZE test_toast;
CODE

Нам необходимо найти запись с поврежденным значением поля в 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
CODE

но если таблица содержит большой объем записей то это будет очень медленно.

Значительно производительнее будет поиск строки,  с интервалами

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
CODE

которые будем постепенно сокращать 

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
CODE

пока не найдем нужную запись

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
CODE

Таким образом, мы выясняем что ошибка в 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=#
CODE

поля с типом "х" - хранятся в таблице тостов, с типом "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=#
CODE

Это поле можно попробовать обновить или удалить все строку из таблицы

test=# update test_toast set b = null where a = 287;
UPDATE 1
test=# SELECT b FROM test_toast LIMIT 1 offset 252;

CODE

Здесь приложен скрипт на bash который находит первую испорченную запись в таблице с указанием номера строки и столбца с испорченной записью

toasts.sh

В качестве примера, создадим таблицу 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;
CODE

Заполним ее тестовыми данными

insert into test_toast select x, repeat( 'alibaba', 5000 * x ), x * 1.2, repeat( 'abc', 100  ) from generate_series( 1, 3000 ) x;
CODE

Узнаем имя файла в котором хранится информация с 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)
CODE

Создадим скрипт, с помощь. которого "испортим" файл с данными

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;

CODE

256 * 1024 - здесь сдвиг от начала файла, зависит от объема данных

При остановленном сервисе postgresql выполним :

perl ./corrupt_tuple.pl ./tantor-be-16/data/base/16384/28329 12345
CODE

После чего запустить сервис postgresql и провести описанный эксперимент

corrup_toast.pl