PostgreSQL cannot find a table or index by its internal Object ID (OID), usually due to dropped tables, replication issues, or system catalog corruption. This critical error indicates missing or inconsistent database objects that require investigation and repair.
The "could not open relation with OID" error occurs when PostgreSQL attempts to access a table or index using its internal Object Identifier (OID) but cannot find it in the system catalog. This indicates a mismatch between cached OID references and actual database objects. The error is serious because it suggests either a table was dropped while other operations were still referencing it, system catalog corruption, replication inconsistency, or concurrent operations that removed objects during critical moments. PostgreSQL maintains OIDs for all relations (tables, indexes, views), and this error signals that the expected object no longer exists or is inaccessible.
The error will include a specific OID number. Note this number:
ERROR: could not open relation with OID 16391You will need this OID to investigate what object it refers to.
Query the pg_class table to see if the OID still exists:
SELECT oid, relname, reltype, relkind
FROM pg_class
WHERE oid = 16391;If this returns no rows, the object has been dropped or the catalog is corrupted. If it returns rows, the object exists but is inaccessible through normal cache mechanisms.
If the OID no longer exists, you need to clear cached references. For a quick fix, restart the PostgreSQL server (if possible) to flush all in-memory caches:
sudo systemctl restart postgresql
# or
sudo service postgresql restartAlternatively, if you cannot restart, disconnect all clients and try again:
-- List current connections
SELECT pid, usename, application_name, state FROM pg_stat_activity
WHERE pid <> pg_backend_pid();
-- Terminate non-critical connections
SELECT pg_terminate_backend(pid) FROM pg_stat_activity
WHERE pid <> pg_backend_pid() AND state = 'idle';If this error occurs in a replication setup, verify replication status:
-- Check replication status on primary
SELECT slot_name, slot_type, active, confirmed_flush_lsn
FROM pg_replication_slots;
-- View replication lag
SELECT client_addr, client_hostname, usename, application_name,
pg_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS bytes_behind
FROM pg_stat_replication;If replication is significantly lagged or inconsistent, consider:
1. Pausing any pg_repack operations on the primary
2. Allowing the secondary to catch up
3. Taking a fresh base backup and reinitializing the replica
For monitoring and system queries, avoid relying on cached OIDs. Use table and schema names directly:
-- Problematic: may fail with OID errors
SELECT schemaname, tablename, pg_size_pretty(pg_total_relation_size(oid))
FROM pg_tables
LEFT JOIN pg_class ON relname = tablename;
-- Better: uses names directly, adds TOAST filter
SELECT schemaname, tablename,
pg_size_pretty(pg_total_relation_size(to_regclass(schemaname||"."||tablename)))
FROM pg_tables
WHERE schemaname NOT IN ("pg_catalog", "information_schema");
-- For disk usage without TOAST issues
SELECT schemaname, tablename
FROM pg_tables
WHERE schemaname NOT IN ("pg_catalog", "information_schema")
AND oid NOT IN (SELECT reltoastrelid FROM pg_class WHERE reltoastrelid != 0);If the OID still exists but remains inaccessible, check for catalog corruption:
-- Look for orphaned references in pg_index
SELECT indexrelid, indrelid FROM pg_index
WHERE indexrelid = 16391 AND indexrelid NOT IN (SELECT oid FROM pg_class);
-- Check pg_depend for broken references
SELECT objid, deptype FROM pg_depend
WHERE objid = 16391 AND objid NOT IN (SELECT oid FROM pg_class);If corruption is found, you may need to restore from backup or use emergency recovery options (running with -P flag to ignore system indexes).
In many cases, this error is transient and disappears after a retry, especially if the query succeeded once before.
-- If your query fails with OID error, simply retry
RETRY QUERY; -- Your original queryIf retrying consistently succeeds, the issue was likely a stale cache that has since been cleared. Monitor for recurrence.
For system administration, understand that PostgreSQL maintains an in-memory relation cache (relcache) to speed up object lookups. Under concurrent operations (especially pg_repack with replication), this cache can become inconsistent with the actual catalog. The -P (skip system indexes) flag is an emergency recovery option that can be used when starting the backend individually for maintenance—it forces PostgreSQL to ignore system indexes and scan system tables sequentially, bypassing cached OID references. This is extremely slow but may allow recovery from severe catalog corruption. Additionally, when using pg_repack in production with streaming replication, ensure reindex operations on the primary complete and fully replicate to secondaries before issuing concurrent DDL operations. Finally, monitoring queries should always use pg_total_relation_size() with the relation name via to_regclass() rather than assuming OIDs remain valid—this is more robust and prevents cascading cache inconsistency issues.
vacuum failsafe triggered
How to fix "vacuum failsafe triggered" in PostgreSQL
PANIC: could not write to file
How to fix PANIC: could not write to file in PostgreSQL
insufficient columns in unique constraint for partition key
How to fix "insufficient columns in unique constraint for partition key" in PostgreSQL
ERROR 42501: must be owner of table
How to fix "must be owner of table" in PostgreSQL
trigger cannot change partition destination
How to fix "Trigger cannot change partition destination" in PostgreSQL