TL;DR: The Quick Fix
Panic over. If your queries are crashing with a TOAST error, you likely have a broken pointer in your storage. The fastest way back to stability is to find the specific corrupted row and either delete it or nullify the broken column. Before you delete anything, try a reindex—it’s the only 'zero data loss' path.
-- 1. Try the safe fix first: reindexing the TOAST table
REINDEX TABLE pg_toast.pg_toast_67890;
-- 2. If the error persists, you'll need to find the broken ID
-- (Jump to 'Surgical Identification' below for the script)
Why This Happens
PostgreSQL uses a mechanism called TOAST to handle oversized fields. When a row exceeds the default 2KB page threshold, Postgres moves large values like JSONB or TEXT to a side table. It leaves a tiny 'pointer' in the main table so it knows where to look later.
The missing chunk number 0 error is essentially a broken link. Your main table says 'the data is over there,' but when Postgres checks, the data is gone or unreadable. In 90% of cases, this stems from hard reboots, disk bit-rot, or file system bugs that occurred during a write operation.
Step 1: Unmask the Corrupted Table
The error message gives you a cryptic OID like pg_toast_67890. You need to know which application table this actually belongs to before you can start fixing it.
SELECT
c.relname AS main_table
FROM
pg_class c
JOIN
pg_class toast_c ON c.reltoastrelid = toast_c.oid
WHERE
toast_c.relname = 'pg_toast_67890';
Step 2: Try a Reindex (The Safe Bet)
Sometimes the data itself is perfectly healthy, but the index used to find those data chunks has become scrambled. Reindexing is your best first move because it involves zero data loss.
REINDEX TABLE pg_toast.pg_toast_67890;
Run your failing query again. If it works, you’re done. If it still crashes, the corruption is deeper, and you'll need to get surgical.
Step 3: Finding the Needle in the Haystack
A standard SELECT * will crash the moment it hits the bad row. To find the specific culprit, you need a script that probes each row individually. Use this block to identify the exact Primary Keys that are causing the failure.
DO $$
DECLARE
rec record;
bad_count int := 0;
BEGIN
-- Replace 'my_table' and 'id' with your actual names
FOR rec IN SELECT id FROM my_table LOOP
BEGIN
-- Replace 'large_column' with your TEXT/JSONB/BYTEA column
PERFORM (SELECT large_column FROM my_table WHERE id = rec.id);
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'Found corrupted row at ID: %', rec.id;
bad_count := bad_count + 1;
END;
END LOOP;
RAISE NOTICE 'Scan complete. Total corrupted rows: %', bad_count;
END;
$$;
Step 4: Repairing the Damage
Once you have the bad IDs, you have to decide how much data you can afford to lose. You generally have two options:
Option A: Nullify the Column
Use this if you want to keep the row but can live without that specific large field value. It replaces the broken pointer with a NULL.
UPDATE my_table
SET large_column = NULL
WHERE id = 'THE_BAD_ID';
Option B: Delete the Row
If the row is useless without its TOASTed data, just wipe the entire record.
DELETE FROM my_table WHERE id = 'THE_BAD_ID';
Step 5: Clean Up the Debris
Fixed the rows? Great. Now you need to clear out the orphaned data chunks still sitting in the TOAST table to prevent future errors during routine maintenance.
VACUUM ANALYZE my_table;
Verify the fix by running a full count on that column. If it returns a number instead of an error, your database is healthy again.
How to Prevent a Repeat Performance
Software can't stop a hard drive from failing, but you can catch it early. If you are setting up a new database, always enable data_checksums. This won't stop corruption, but it will alert you the moment a bit flips on the disk rather than waiting for a query to fail. Additionally, ensure you are running logical backups with pg_dump; they act as an accidental 'integrity check' because they will fail if they encounter corruption.

