LE MODÈLE DE DONNÉES
Comment je conçois le schéma d'un site annuaire à l'échelle six chiffres. Entités, attributs, slugs, index, et les choix de conception qui se cumulent.
Les décisions de schéma qui se cumulent
Un site annuaire à 28 K lignes (HostList.io) ou 137 K lignes (Not Another Sunday) réussit ou échoue sur les décisions de schéma prises la première semaine. Les décisions qui comptent vraiment : comment les entités sont normalisées, comment les attributs sont stockés, comment les slugs sont générés, ce qui est indexé.
Chacune de ces décisions est réversible à petite échelle et prohibitivement coûteuse à corriger à l'échelle six chiffres. Faites-les bien dès le début.
Entités, attributs, jonctions
Une table d'entités avec une ligne maître par entreprise, lieu ou produit. Clé primaire UUID stable, une colonne par attribut intrinsèque (nom, année_de_fondation, marché_principal). Évitez de fourrer les attributs dans des colonnes JSON à ce niveau ; ils doivent être requêtables comme colonnes de première classe.
Une table d'attributs par axe orthogonal. categories (chaque ligne est une catégorie), regions (chaque ligne est une région), price_tiers (chaque ligne est un tier). Ce sont les dimensions selon lesquelles les entités sont groupées.
Relations many-to-many via 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 ; les tables de jonction sont ce qui rend le filtrage, l'énumération et le comptage rapides.
Les slugs sont permanents
Une fois qu'une page de répertoire est indexée par Google, le slug ne doit pas changer. Utilisez une génération de slug déterministe à partir du nom de l'entité avec gestion des collisions via suffixe numérique : nginx-hosting, nginx-hosting-2 en cas de collision.
Maintenez une table slug_history séparée mappant tout slug historique au slug canonique actuel. Quand un nom d'entité doit changer (correction, rebrand), le nouveau slug canonique est généré, l'ancien slug entre dans la table history, et une redirection 301 permanente est mise en place automatiquement.
Le coût d'une mauvaise stabilité des slugs sur un site de 28K lignes est catastrophique : chaque chaîne de redirection que vous créez est une petite érosion du classement, et il n'y a pas de bons correctifs après coup.
Indexabilité au niveau de la ligne
Ajoutez une colonne booléenne indexable à la table entities, par défaut true, avec une logique qui la bascule à false quand l'entité ne satisfait pas les seuils de qualité (contenu unique insuffisant, attributs requis manquants, pas de liens internes entrants).
La requête sitemap filtre sur indexable = true. Le renderer de page ajoute la meta noindex quand indexable = false. Les deux sont dérivés de la même colonne, donc ils restent en sync de manière atomique.
Sur HostList.io, environ 18K des 28K entités sont indexables à tout moment. Les autres 10K sont crawlables mais non indexables, ce qui maintient la surface indexée propre et renforce les pages restantes indexées.
Les index qui comptent
Index sur slug pour la recherche de rendu de page. Index sur (indexable, last_updated_at) pour la requête sitemap. Index sur (category_id, indexable) sur la table de jonction pour les pages de listing par catégorie. 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 des secondes. Avec les bons index, elle prend des millisecondes. L'impact sur le temps de build s'accumule : 28K rendus de page à 100ms chacun font 47 minutes ; à 10ms chacun c'est 5 minutes.
Exécutez EXPLAIN ANALYZE sur chaque requête de rendu de page à l'échelle. Le planificateur de requêtes par défaut se trompe sur les jointures de type répertoire plus souvent que vous ne l'attendriez.