<!--
Les sources pour ce sujet sont :

* https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=78c5e141e

Discussion :

* https://postgr.es/m/CAAhFRxitJv%3DyoGnXUgeLB_O%2BM7J2BJAmb5jqAT9gZ3bij3uLDA%40mail.gmail.com

-->

<div class="slide-content">

  * Fonction de génération des UUID v7 : `uuidv7()`
    + début généré à partir d'un timestamp
    + assure la colocalité des données dans les index B-tree
  * Fonctions d'extraction :
    + `uuid_extract_version()`
    + `uuid_extract_timestamp()`

</div>

<div class="notes">

Un UUID (_Universally Unique IDentifier_) est un nombre encodé sur 128 bits dont
la génération assure son unicité au sein d'un système.

Il y a plusieurs implémentations des UUID, identifiées par leur numéro de
version :

* les versions 1 et 6 se basent sur l'adresse MAC et un timestamp.
  L'ordre des bits de la version 6 permet de trier les UUID par date ;
* la version 2 se base aussi sur une adresse MAC et un timestamp mais ajoute
  le domaine local dans le calcul. Ce type d'UUID est souvent omis dans les
  librairies qui implémentent les UUID ;
* les versions 3 et 5 se basent sur le hachage d'un identifiant de namespace
  et un nom ;
* la version 4 utilise une génération aléatoire ;
* la version 7 est destinée à être utilisée dans les bases de données et systèmes
  distribués. Elle est générée avec un timestamp et une partie aléatoire ;
* la version 8 prévoit une spécification pour créer des UUID spécifiques à une
  application.


L'extension `uuid-ossp` fournit les fonctions de génération pour les versions 1,
3, 4 et 5.

```text
CREATE EXTENSION "uuid-ossp";
\dx+ "uuid-ossp"
```
```text
   Objects in extension "uuid-ossp"
          Object description
--------------------------------------
 function uuid_generate_v1()
 function uuid_generate_v1mc()
 function uuid_generate_v3(uuid,text)
 function uuid_generate_v4()
 function uuid_generate_v5(uuid,text)
 function uuid_nil()
 function uuid_ns_dns()
 function uuid_ns_oid()
 function uuid_ns_url()
 function uuid_ns_x500()
(10 rows)
```

Des fonctions sont également disponibles dans PostgreSQL pour les UUID en
version 4 et 7. La version 17 avait commencé à préparer le terrain pour l'ajout
des UUID v7 en créant un alias à la fonction `gen_random_uuid()` nommé
`uuidv4()`. La version 18 intègre la fonction `uuidv7()`.

La différence entre les deux est visible au premier coup d'œil :

```sql
SELECT uuidv4(), uuidv7() FROM generate_series(1,5);
```
```text
                uuidv4                |                uuidv7
--------------------------------------+--------------------------------------
 b300652d-4f26-482a-a314-22d2d7e8aad2 | 01999604-2ba2-7ea9-853e-657bbcd7596e
 aa0b526d-2893-4418-96f0-80e6d7f82fa6 | 01999604-2ba2-7ec9-b8c3-fd99c01e6447
 bda1ddcf-5c60-4ee6-a0bc-2d2b86881395 | 01999604-2ba2-7ed4-9194-a63b806d4e79
 b6cde31b-5378-4760-99ef-9fa91c69c104 | 01999604-2ba2-7edd-97e4-196d8638b868
 697ec82c-a73a-4e39-bfaf-71d9fd88a9f3 | 01999604-2ba2-7ee6-918c-5503cfa8f647
(5 rows)
```

On voit que les UUID V7 peuvent être triés par ordre de création.

Des fonctions d'extractions sont également fournies pour les UUID, quelle que
soit la fonction utilisée pour les générer :

```sql
SELECT uuid_extract_version(x), uuid_extract_timestamp(x)
FROM (VALUES
        (uuid_generate_v1()),
        (uuid_generate_v3(uuid_generate_v1 (), 'postgres')),
        (uuid_generate_v4()),
        (uuidv4()),
        (uuid_generate_v5(uuid_generate_v1 (), 'postgres')),
        (uuidv7())
) AS F(x);
```
```text
 uuid_extract_version |    uuid_extract_timestamp
----------------------+------------------------------
                    1 | 2025-09-29 17:55:11.98522+02
                    3 | ø
                    4 | ø
                    4 | ø
                    5 | ø
                    7 | 2025-09-29 17:55:48.267+02
(6 rows)
```

Les UUID ont très mauvaise réputation dans le monde des bases de données
L'inconvénient principal des UUID v4 est l'impact désastreux sur la 
colocalité des données dans les index B-tree.

L'exemple suivant crée deux tables, avec chacune un index sur une colonne de
type `uuid`, généré avec des UUID de type v4 pour l'une et v7 pour l'autre.

```sql
-- UUID v4
DROP TABLE IF EXISTS tuuid_v4;
CREATE TABLE tuuid_v4(i uuid, t text);
CREATE INDEX ON tuuid_v4(i);
INSERT INTO tuuid_v4 SELECT uuidv4(), 'id ' || x FROM generate_series(1,1000000) AS F(x);

-- UUID v7
DROP TABLE IF EXISTS tuuid_v4;
CREATE TABLE tuuid_v7(i uuid, t text);
CREATE INDEX ON tuuid_v7(i);
INSERT INTO tuuid_v7 SELECT uuidv7(), 'id ' || x FROM generate_series(1,1000000) AS F(x);

VACUUM ANALYZE tuuid_v4, tuuid_v4;
```

On constate que l'index sur l'UUID v4 est nettement plus volumineux que celui
avec un UUID v7.

```text
\di+ tuuid*
```
```text
                                    List of indexes
 Schema |      Name      | Type  | Owner  |  Table   |…| Access method | Size  |…
--------+----------------+-------+--------+----------+…+---------------+-------+…
 public | tuuid_v4_i_idx | index | benoit | tuuid_v4 |…| btree         | 38 MB |…
 public | tuuid_v7_i_idx | index | benoit | tuuid_v7 |…| btree         | 30 MB |…
(2 rows)
```

Le nombre de pages dans l'index sur les UUID v4 est beaucoup plus important.
Cela s'explique par le fait qu'avec ce genre d'UUID, les lignes sont souvent
insérées dans des pages différentes.

Par contre,
les UUID v7 sont triés grâce au timestamp, et insérés les uns après les autres
dans les pages. Cet ordre entraîne une utilisation plus efficace des pages et
donc du cache.

```sql
SELECT relname, relpages
  FROM pg_class
 WHERE relname IN ('tuuid_v4_i_idx', 'tuuid_v7_i_idx');
```
```text
    relname     | relpages
----------------+----------
 tuuid_v7_i_idx |     3853
 tuuid_v4_i_idx |     4866
(2 rows)
```

Le coût de mise en place des UUID v4 est mineur à côté des gros soucis
qu'ils posent dans la gestion du cache. En effet, deux UUID
générés à la suite seront probablement dans des blocs d'index
différents, à l'inverse des séquences habituelles où les numéros
se suivent. Les UUID v7 corrigent cela.

Les exemples suivants montrent l'impact de la colocalité des données sur
l'utilisation du cache en sélectionnant les deux premières lignes insérées dans
chaque table par leur UUID. On peut observer que pour la table `tuuid_v4` cette
sélection nécessite d'accéder 7 pages, contre 4 pour la table `tuuid_v7`.

```sql
SELECT * FROM tuuid_v4 LIMIT 2 ;
```
```text
                  i                   |  t   
--------------------------------------+------
 1ae3654b-7209-4977-bea1-e78694b3935e | id 1
 2f9d0709-87fe-4fec-ad4b-76eb89008f5b | id 2
```
```sql
--- UUID v4
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT *
  FROM tuuid_v4
 WHERE i IN ('1ae3654b-7209-4977-bea1-e78694b3935e', '2f9d0709-87fe-4fec-ad4b-76eb89008f5b');
```
```text
                                QUERY PLAN
-----------------------------------------------------------------------------
 Index Scan using tuuid_v4_i_idx on tuuid_v4 (actual rows=2.00 loops=1)
   Index Cond: (i = ANY ('{1ae3654b-7209-4977-bea1-e78694b3935e,
                           2f9d0709-87fe-4fec-ad4b-76eb89008f5b}'::uuid[]))
   Index Searches: 2
   Buffers: shared hit=7
```
```sql
SELECT * FROM tuuid_v7 LIMIT 2 ;

--- UUID v7
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT *
  FROM tuuid_v7
 WHERE i IN ('01999644-0a57-7a3a-bc85-b67dcc3061bf', '01999644-0a57-7ed0-ac2d-72a1036387f7');
```
```text
                                QUERY PLAN
-----------------------------------------------------------------------------
 Index Scan using tuuid_v7_i_idx on tuuid_v7 (actual rows=2.00 loops=1)
   Index Cond: (i = ANY ('{01999644-0a57-7a3a-bc85-b67dcc3061bf,
                           01999644-0a57-7ed0-ac2d-72a1036387f7}'::uuid[]))
   Index Searches: 1
   Buffers: shared hit=4
```

L'impact semble mineur, mais l'effet sur le cache est radical
pour des appels répétés : par exemple, un index de clé primaire en UUID v4
aura tendance à se retrouver intégralement dans le cache de PostgreSQL,
même si l'essentiel des lignes ne sont pas utilisées
(voir [cet exemple dans notre formation PERF2](https://dali.bo/s22_html#uuid-2)).

Pour les versions précédentes de PostgreSQL,
il existe des scripts ou extensions pour générer
des versions UUID v7.

Les UUID v7 contiennent leur date de génération :
utilisés comme clé primaire, ils peuvent éviter l'ajout
d'un champ `creation_date`.
Pour la même raison, ils peuvent même faciliter
le [partitionnement temporel](https://fljd.in/2025/06/17/le-partitionnement-par-uuid-v7/).

Pour d'autres détails sur les UUID,
voir aussi [notre module de formation S22](https://dali.bo/s22_html#uuid).

</div>
