diff --git a/source/concept/bdd/generic.md b/source/concept/bdd/generic.md
index ef080a4ca5f92ec6be5e58bf9392b29969bb3bf5..eca984b9001ef4e38e37b9ece2f0021fc04cee2e 100644
--- a/source/concept/bdd/generic.md
+++ b/source/concept/bdd/generic.md
@@ -370,6 +370,129 @@ PostgreSQL propose plusieurs méthodes d'indexation pour améliorer les performa
 
 Chaque méthode d'indexation a ses propres avantages et inconvénients, et la sélection de la méthode appropriée dépend du type de données, des opérations de requête fréquentes et des objectifs de performance spécifiques. PostgreSQL permet également de combiner différentes méthodes d'indexation pour optimiser les performances dans des scénarios plus complexes.
 
+### Comment bien indexer une table ?
+
+Avant de commencer à réfléchir à l'indexation d'une table, il est essentiel de comprendre comment elle va être interrogée (la structure globale des requêtes SELECT sur la table, l'utilisation qui va être faite de la donnée) et comment fonctionne le mécanisme interne du SGBD.
+
+Sans indexation, pour une requête donnée, le SGBD devra récupérer l'ensemble des enregistrements pour vérifier la valeur correspondant à la colonne demandée dans la clause WHERE, GROUP BY ou ORDER BY. Ces clauses n'interviennent pas nécessairement dans les mêmes étapes du moteur SGBD (c'est pourquoi un LIMIT n'a plus d'effet sur les performances dès lors qu'un ORDER BY est utilisé, car ce dernier dégrade les performances puisqu'il intervient après un premier parcours de la table pour la clause WHERE).
+
+Pour illustrer ce mécanisme, imaginons qu'une table est un livre, et chaque enregistrement une page. Il faut lire chaque page pour vérifier que l'élément recherché est présent, puis relire les pages concernées pour les trier.
+
+Un index agit comme une table des matières pour le livre, c'est-à-dire une page supplémentaire liée au livre qui permet de retrouver plus rapidement une information spécifique.
+
+Plus une colonne sera récurrente dans les requêtes, plus l'indexation de cette dernière sera pertinente **sous certaines conditions**.
+
+La colonne doit être à la fois suffisamment discriminante sans trop l'être, il faut évaluer la cardinalité moyenne. Autrement dit, si une colonne comporte *n* valeurs (où *n* est bien inférieur au nombre d'enregistrements) avec une répartition égale pour chaque valeur, l'index sera très efficace. À l'inverse, si une valeur représente 95 % de la répartition dans la table, alors l'impact de l'index pourrait être discutable (cela dépendra principalement de la différence de volumétrie entre la table et l'index, ainsi que dans quelle portion se trouve votre zone de travail).
+
+Si la colonne est régulièrement mise à jour dans la vie de l'enregistrement, l'index peut représenter un coût significatif à maintenir (par exemple, un état). D'autant plus que, dans ce cas, la colonne est souvent peu discriminante, car il n'y a que quelques états finaux. Pour reprendre l'exemple du livre, c'est comme si la table des matières devait se mettre à jour constamment au fil de la recherche, ce qui est contre-productif pour le lecteur.
+
+Dans le cas de colonnes utilisées pour du tri, il peut être utile de prévoir un index intégrant le sens de tri préféré.
+
+Le moteur du SGBD aura des difficultés à travailler avec plusieurs index en même temps. Imaginez un lecteur devant croiser trois tables des matières pour trouver une information, c'est quasi impossible. Il en va de même pour le moteur SGBD. En réalité, le moteur est capable d'utiliser plusieurs index, mais il fera parfois le choix de limiter cet usage, ce que l'on peut observer avec les instructions **EXPLAIN ANALYZE**. D'où l'intérêt d'utiliser des index composites (ou multi-colonnes).
+
+En réutilisant le même exemple, il faut voir un index composite comme une table des matières de ce type : 
+- Col1 - Val1
+  - Col2 - Val1 ................. 0x0000...
+  - Col2 - Val2 ................. 0x0000...
+  - Col2 - Val3 ................. 0x0000...
+- Col1 - Val2
+  - Col2 - Val1 ................. 0x0000...
+  - Col2 - Val2 ................. 0x0000...
+  - Col2 - Val3 ................. 0x0000...
+
+Dans le cas d'une table interrogée de manière automatique (par exemple les demandes GTF ou les workflows GTF par les moteurs), il est facile de poser une structure pour un index composite, puisque les requêtes sont toujours construites de la même manière. Cela devient plus complexe lorsque la donnée est interrogée via l'API et où l'utilisateur peut personnaliser la requête.
+
+Le volume de la table peut également favoriser l'ajout d'index. Dès que vous avez une colonne lourde (habituellement byte array, text, jsonb), il est pertinent d'indexer les colonnes régulièrement utilisées dans les filtres. Ces colonnes étant parfois très volumineuses, sur une requête non indexée, le SGBD devra lire l'ensemble de cette donnée, même si elle n'est pas directement utilisée dans la requête (toujours avec l'exemple du livre, c'est comme lire 90 % de la page pour rien systématiquement).
+
+Un cas concret : un livre de cuisine. Je peux chercher un type de plat (dessert, plat principal, entrée, ...), un sous-type (gâteau, plat mijoté, soupe, salade, ...), un ingrédient, un temps de préparation, etc.
+
+Des index qui pourraient être pertinents seraient : 
+- Index monocolonne sur le type, le sous-type, le temps de préparation, la difficulté.
+- Index composite sur le type et le sous-type.
+
+### Cas pratique : les workflows de GTF
+
+Il y a plusieurs tables (wf_iteration, wf_task, workflow), la table la plus interrogée par les moteurs étant wf_task. Sur une application Dtnet de production, après un an d'exécution, la table contient 17 Go de données et inclut une colonne de type jsonb.
+
+Au niveau de l'interface, on peut afficher le contenu des trois tables, lister les workflows, les itérations et les tasks d'une itération.
+
+Le moteur récupère les tasks liées à un workflow_id, triées par priorité et date de création. Il cherche aussi parfois à savoir si la task est dans une chaîne de traitement synchrone (colonne chain). Il y a aussi un état sur la tâche.
+
+1. Identification des requêtes problématiques :
+
+PgAdmin permet de voir en temps réel les requêtes en cours d'exécution. Il faut alors analyser les temps de traitement au bon moment, ce qui peut être assez complexe. AWS RDS permet de visualiser les requêtes les plus coûteuses en CPU sur un intervalle de temps.
+
+```sql
+SELECT * FROM s_gtf.wf_task WHERE status_id=$1 AND chain IS NULL AND workflow_id IN ($2, $3) AND wf_step_id IS NOT NULL AND wf_iteration_id IS NOT NULL AND archived IS NOT true ORDER BY priority DESC NULLS LAST, creation_date ASC LIMIT ?;
+
+SELECT * FROM s_gtf.wf_task WHERE status_id = ? AND workflow_id IN (?, ?) AND wf_step_id IS NOT NULL AND wf_iteration_id IS NOT NULL AND archived IS NOT true ORDER BY priority DESC NULLS LAST, creation_date ASC LIMIT ?
+```
+
+2. EXPLAIN ANALYZE des requêtes concernées : 
+
+La charge sur le serveur (plusieurs processus moteur se réveillant en même temps pour interroger la table) associée au volume de données à interroger provoque l'apparition de traitements parallèles par le moteur du SGBD, ce qui provoque une surcharge du CPU sur la base.
+
+3. Recoupement des requêtes problématiques pour identifier les colonnes indexables : 
+
+Les colonnes revenant le plus souvent dans les requêtes: 
+- workflow_id (clause WHERE)
+- priority (clause ORDER BY)
+- creation_date (clause ORDER BY)
+- chain (clause WHERE)
+- status_id (clause WHERE)
+- archived (clause WHERE)
+
+Certaines colonnes utilisées dans le WHERE étant NOT NULL mais interrogées avec l'instruction IS NOT NULL n'ont pas de sens. Il faut donc modifier la requête dans le moteur pour l'alléger.
+
+4. Estimation de la répartition de données pour indexation : 
+
+**workflow_id :** 
+
+C'est une clé étrangère sur une autre table.
+
+L'application contient plusieurs centaines de workflows (relativement à plusieurs millions de tasks), avec une répartition d'environ 75 % répartie de manière égale sur trois workflows et 25 % sur les autres workflows.
+
+**priority :**
+
+Colonne de type integer, les valeurs sont supérieures à 0 avec une infinité de possibilités théoriquement. Dans la pratique, il y a environ 4-5 valeurs, principalement 1 et 8, avec une répartition de 75 % pour la valeur 1 et de 25 % pour 8 (les autres valeurs représentant moins de 5 % du volume total, elles sont considérées comme négligeables).
+
+**creation_date :**
+
+Colonne de type timestamp.
+Il y a peu de chance que la valeur soit suffisamment discriminante pour impacter la requête. En revanche, le fait de parcourir les blocs complets (soit 17 Go) pour réaliser le tri final rend l'apport de l'index non négligeable. Il pourrait donc être pertinent de l'inclure dans un index composite. D'autant plus que la colonne n'évolue pas au cours de la vie de l'enregistrement, l'index ne sera donc pas lourd à maintenir.
+
+**chain :**
+
+Colonne de type integer, qui permet de définir si une task est exécutée de manière synchrone avec une autre task. En gros, si c'est null, c'est que c'est une task à traiter pour une nouvelle exécution des moteurs. À l'inverse, la task est traitée par un thread existant du moteur en réponse à une autre task traitée.
+Il existe donc les valeurs suivantes : NULL, 0, et tous les entiers supérieurs à 0.
+Dans la pratique, lorsque cette colonne est utilisée dans le WHERE, c'est la valeur NULL qui est recherchée, représentant environ 5-15 % des tasks. Il pourrait donc être pertinent d'indexer ce champ pour ce cas bien précis, puisqu'il éliminerait la majorité des données à analyser.
+
+Cette colonne n'étant pas dans toutes les requêtes, il faudra probablement envisager de doubler certains index, en mono-colonne et multi-colonne.
+
+**status_id :**
+
+L'état d'une task contient que quelques valeurs, mais la répartition est majoritairement sur un seul état. L'état changeant au cours du cycle de vie de l'enregistrement, cet index aura un coût supérieur aux autres à maintenir pour le SGBD.
+
+**archived :**
+
+Booléen qui définit si une task est terminée et archivée. Actuellement, plus de 90 % des tasks sont archivées. L'impact de l'index est donc discutable, cependant, si la recherche se fait du côté le plus petit de la répartition, l'index peut se justifier.
+
+5. Création des index
+
+Il faut évaluer l'impact d'un index et mettre en place les index les plus significatifs pour nos requêtes spécifiques.
+
+Un premier index composite sur la clause ORDER BY semble plus pertinent : 
+```sql
+CREATE INDEX idx_wf_task_engine_priority_creation ON s_gtf.wf_task (priority DESC NULLS LAST, creation_date ASC); -- 526 Mo
+```
+
+Un index sur l'identifiant du workflow revenant le plus régulièrement devrait également avoir un impact significatif : 
+```sql
+CREATE INDEX idx_wf_task_engine_workflow_id ON s_gtf.wf_task (workflow_id); -- 116 Mo
+```
+
+Dans un second temps, il sera possible d'ajouter des index sur chain, status_id, archived, mais dans ce cas-là, il faudra vérifier le comportement de la requête avec plusieurs index mono-colonne, ce qui serait la solution la plus simple. Sinon, il faudra créer un index composite par combinaison exploitée dans les différentes requêtes des moteurs.
+
 ## Transactions
 
 La gestion des transactions est un concept fondamental dans les systèmes de bases de données qui garantit l'intégrité et la cohérence des données. Une transaction représente une séquence d'opérations exécutées comme une unité indivisible, c'est-à-dire soit toutes les opérations sont exécutées avec succès et validées, soit aucune opération n'est exécutée du tout.