A partir de PostgreSQL 12, les colonnes oid des tables systèmes perdront leur nature “spéciale”, et la clause optionnelle WITH OIDS disparaîtra de CREATE TABLE. Concrètement ça veut dire que quand on fait select * de ces tables, ces colonnes seront maintenant visibles, ainsi que via information_schema.columns, ou encore avec \d sous psql. Jusqu’à présent elles étaient cachées, comme les colonnes systèmes xmin, xmax, etc…

Le commit dans les sources indique la motivation de ce changement:

author Andres Freund <andres (at) anarazel (dot) de>
Wed, 21 Nov 2018 01:36:57 +0200 (15:36 -0800)
[…]
Remove WITH OIDS support, change oid catalog column visibility.
[…]
The fact that the oid column was not an ordinary column necessitated a significant amount of special case code to support oid columns. That already was painful for the existing, but upcoming work aiming to make table storage pluggable, would have required expanding and duplicating that “specialness” significantly.

En résumé, les caractéristiques spéciales des OIDs compliquait des évolutions importantes, en l’occurrence les formats de tables à la carte comme le très attendu zheap.

Si on regarde des années en arrière, ce changement peut être mis en perspective par rapport à d’autres, qui vont aussi dans le sens de l’obsolescence des OIDs:

  • 7.2 (Feb 2002), la colonne oid devient optionnelle.
  • 8.0 (Jan 2005), le paramètre default_with_oids est créé.
  • 8.1 (Nov 2005), default_with_oids passe à false par défaut.

Mais pourquoi ces colonnes spéciales avaient-elles été inventées au départ? A l’origine, l’OID est lié à l’orientation objet.

L’historique orienté-objet de Postgres

Au milieu des années 80, le concept d’orientation objet faisait surface, avec des langages comme C++ qui en était à ses débuts. Dans les bases de données, il y avait cette idée que l’avenir était peut-être à considérer les données en tant qu’objets.

C’est donc assez naturellement que dans les premières versions de Postgres développées à l’Université de Berkeley, l’orientation objet était une composante importante du projet.

Dans les langages de programmation, le concept OO a été un succès avec notamment C++ ou Java par exemple. Mais concernant les bases de données, le concept n’a pas pris, ou en tout cas est resté cantonné à un usage de niche.

Lorsque la communauté de développeurs a repris Postgres au milieu des années 90 pour le faire évoluer en tant que moteur SQL, elle a hérité de fonctionnalités clairement influencées par le paradigme objet, notamment:

  • les tables sont des classes.
  • les lignes des tables sont des instances de classe.
  • une table peut hériter de la structure d’une table parente.
  • les fonctions sont polymorphes (par la surcharge).

Mais la poursuite de la vision orientée objet n’a pas vraiment intéressé les développeurs depuis 1996, suivant en ça les autres communautés des bases de données. Ils ont préféré se concentrer sur d’autres objectifs, comme l’amélioration des performances, de la robustesse, et la conformité au standard SQL, lequel continuant d’ailleurs à évoluer.

Quoiqu’il en soit, la fonctionnalité où une ligne est considérée comme une instance de classe implique l’existence d’un identifiant au-delà des colonnes utilisateur, pour différencier une instance d’une autre. Par analogie avec les langages de programmation, où des classes sont instanciées en mémoire, une instance sera distincte d’une autre au minimum par le fait que leurs adresses en mémoire sont différentes. L’OID, c’est en quelque sorte l’adresse de l’instance de classe sérialisée sous une forme stockable sur disque.

Les anciennes documentations encore en ligne expliquent ce point de vue:

Concepts dans PostgreSQL 6.4 (1998):

The fundamental notion in Postgres is that of a class, which is a named collection of object instances. Each instance has the same collection of named attributes, and each attribute is of a specific type. Furthermore, each instance has a permanent object identifier (OID) that is unique throughout the installation. Because SQL syntax refers to tables, we will use the terms table and class interchangeably. Likewise, an SQL row is an instance and SQL columns are attributes.

Un exemple était donné dans Populating a Class with Instances:

The insert statement is used to populate a class with instances:
INSERT INTO weather VALUES (‘San Francisco’, 46, 50, 0.25, ‘11/27/1994’)
[…]
As previously discussed, classes are grouped into databases, and a collection of databases managed by a single postmaster process constitutes an installation or site.

C’est à partir de la version 7.1, sortie en 2001, que la référence aux classes disparaît, et qu’on parle de “Creating a New Table” au lieu de “Creating a New Class”.

Dans le catalogue il reste quelques vestiges de ce passé, comme la table des tables qui s’appelle toujours pg_class (mais il y a une vue pg_tables).

Les OIDs en PostgreSQL moderne

Les OIDs comme colonnes “normales” restent utilisées dans le catalogue comme clef synthétique primaire partout où c’est utile. Dans PostgreSQL-12, 39 tables ont un champ nommé oid et 278 colonnes sont de type oid (contre 39 et 274 en version 11)

postgres=# SELECT
 count(*) filter (where attname = 'oid') as "OID as name",
 count(*) filter (where atttypid = 'oid'::regtype) as "OID as type"
FROM pg_attribute JOIN pg_class ON (attrelid=oid) WHERE relkind='r';

 OID as name | OID as type 
-------------+-------------
          39 |         278

Par ailleurs, les OIDs restent essentiels dans la gestion des objets larges, qui stockent des contenus binaires segmentés automatiquement, puisque l’API expose ces objets exclusivement via leurs OIDs. Le seul changement visible par les utilisateur en v12 est que pg_largeobject_metadata.oid devient visible directement, mais un utilisateur n’a pas vraiment besoin de requêter cette table s’il utilise l’API.

Les OIDs inférieurs à 16384 sont réservés au système de base comme avant.

Le générateur de valeurs pour les OIDs est un compteur au niveau du cluster, donc les valeurs sont distribuées séquentiellement comme si c’était une séquence commune à toutes les bases.

Ce qui donne par exemple:

postgres=# create database db1;
CREATE DATABASE

postgres=# \lo_import .bashrc
lo_import 16404

postgres=# \c db1
You are now connected to database "db1" as user "daniel".

db1=# \lo_import .bashrc
lo_import 16405

Ce comportement où un doublon d’OID est évité alors que les objets larges des deux bases sont totalement indépendants est possiblement un reliquat de l’époque où chaque OID était “unique throughout the installation” comme cité précédemment dans le passage de documentation de la 6.4.

Cette contrainte globale d’unicité a disparu il y a longtemps, mais le générateur a conservé ce comportement anti-collision qui fait que ça ne pourra arriver qu’après un cycle de plus de 4 milliards de valeurs allouées dans le cluster. (le compteur d’OID étant un entier de 32 bits non signé qui repart à 16384 quand il atteint 2^32).

Les plus curieux peuvent aller consulter les commentaires de la fonction GetNewOidWithIndex() pour voir comment le code gère un possible conflit d’unicité avec un OID pré-existant dans une même table, ainsi que la fonction SQL pg_nextoid qui est volontairement non mentionnée dans la documentation.