LE MODÈLE DE DONNÉES
Comment je conçois le schéma d'un site d'annuaire à une échelle de plusieurs centaines de milliers d'entrées. Entités, attributs, slugs, index et les choix de conception qui s'y rattachent.
← Tous les guides Tous les guides de ce sujet
Les choix de schéma qui s'accumulent
La réussite ou l'échec d'un site d'annuaire comptant 28 000 lignes (HostList.io) ou 137 000 lignes (Not Another Sunday) dépend des choix de schéma effectués dès la première semaine. Les décisions les plus importantes concernent : la manière dont les entités sont normalisées, le mode de stockage des attributs, la génération des slugs et les éléments indexés.
Chacune de ces erreurs est réversible à petite échelle, mais leur correction à une échelle de plusieurs centaines de milliers d'unités revient extrêmement cher. Veillez à les corriger dès le début.
Entités, attributs, jonctions
Une table « entités » contenant une ligne maître par entreprise, lieu ou produit. Clé primaire UUID stable, une colonne par attribut intrinsèque (nom, année_de_création, marché_principal). Évitez d'entasser les attributs dans des colonnes JSON à ce niveau ; ils doivent pouvoir faire l'objet de requêtes en tant que colonnes à part entière.
Un tableau d'attributs par axe orthogonal : catégories (chaque ligne correspond à une catégorie), régions (chaque ligne correspond à une région), tranches de prix (chaque ligne correspond à une tranche). Ce sont les dimensions selon lesquelles les entités sont regroupées.
Relations « plusieurs-à-plusieurs » via des tables de jonction : entity_categories, entity_regions, entity_features. Ne stockez jamais de listes de catégories séparées par des virgules dans la table entities ; ce sont les tables de jonction qui permettent d'accélérer le filtrage, l'affichage des listes et le comptage.
Les limaces, c'est pour toujours
Une fois qu'une page de répertoire est indexée par Google, le slug ne doit plus changer. Utilisez une génération déterministe du slug à partir du nom de l'entité, avec gestion des conflits via un suffixe numérique : nginx-hosting, nginx-hosting-2 en cas de conflit.
Gérez une table distincte, « slug_history », qui associe chaque slug historique au slug canonique actuel. Lorsqu'un nom d'entité doit être modifié (correction, changement de marque), le nouveau slug canonique est généré, l'ancien slug est ajouté à la table d'historique et une redirection 301 permanente est automatiquement mise en place.
Les conséquences d'une mauvaise configuration de la stabilité des slugs sur un site comptant 28 000 lignes sont catastrophiques : chaque chaîne de redirection que vous créez entraîne une légère baisse du référencement, et il n'existe aucune solution efficace pour y remédier a posteriori.
Indexabilité au niveau des lignes
Ajouter une colonne booléenne indexable à la table des entités, dont la valeur par défaut est « true », avec une logique qui la fait passer à « false » lorsque l'entité ne respecte pas les seuils de qualité (contenu unique insuffisant, attributs obligatoires manquants, absence de liens internes entrants).
La requête du plan du site applique un filtre sur la valeur « indexable = true ». Le moteur de rendu de la page ajoute la balise meta « noindex » lorsque « indexable = false ». Ces deux valeurs sont dérivées de la même colonne ; elles restent donc synchronisées de manière atomique.
Sur HostList.io, environ 18 000 des 28 000 entités sont indexables à tout moment. Les 10 000 autres sont explorables mais non indexables, ce qui permet de maintenir la surface indexée propre et de renforcer les autres pages indexées.
Les indices qui comptent
Index sur le slug pour la recherche de rendu de page. Index sur (indexable, last_updated_at) pour la requête relative au plan du site. Index sur (category_id, indexable) dans la table de jonction pour les pages de liste des catégories. Index sur (region_id, indexable) pour les pages régionales.
À l'échelle de centaines de milliers de lignes, une requête de rendu de page sans index prend quelques secondes. Avec des index adaptés, cela ne prend que quelques millisecondes. L'impact sur le temps de compilation s'amplifie : le rendu de 28 000 pages à raison de 100 ms chacune prend 47 minutes ; à raison de 10 ms chacune, cela ne prend que 5 minutes.
Exécutez EXPLAIN ANALYZE sur chaque requête de rendu de page à grande échelle. Le planificateur de requêtes par défaut se trompe plus souvent que vous ne le pensez sur les jointures de type « répertoire ».