Postgres OID VS Relfilenode

When you create a table, PostgreSQL assigns it an OID (Object Identifier). This is just a logical identifier for the table. it is stored in the system catalog pg_class and remains constant for the lifetime of the table.
But under the hood in physical file on disk is identified by the relfilenode, which is also stored in system catalog pg_class. But the good news is that most of the time OID and Relfilenode are same. But they can diverge, and understanding when and why is probably the most crucial.
Try This Experiment
CREATE TABLE demo(
id INT,
data TEXT
);
SELECT oid, relfilenode FROM pg_class WHERE relname = 'demo';
Again
TRUNCATE demo;
SELECT oid, relfilenode FROM pg_class WHERE relname = 'demo';
Now you might see the different oid and relfilenode from those commands. The OID stayed the same but why relfilenode changed ?
When you truncate a table, PostgreSQL doesn't actually delete all the rows from the existing file. Instead, it creates a brand new file with a new relfilenode and updates the catalog to point to it. The old file is deleted. This is much faster than scanning through the file and marking every tuple as deleted, and it's safer from a crash-recovery perspective—the old file exists until the transaction commits.
The same thing happens with VACUUM FULL, CLUSTER, and REINDEX (for indexes). These operations rewrite the entire table or index, giving it a new physical file. But the OID never changes. This separation between logical identity (OID) and physical storage (relfilenode) allows PostgreSQL to reorganize data without breaking foreign key constraints, views, or permissions, all of which reference the OID.




