La plupart des recherches full-text foirent avant même le premier SELECT. Pas à cause de l'algorithme : parce qu'on choisit l'outil avant de définir le besoin. Les experts sont unanimes : la pertinence et la performance comptent, mais la solution doit épouser ton volume de données et les compétences de ton équipe. Pas l'inverse.
PostgreSQL, Elasticsearch et SQLite ne se positionnent pas sur le même terrain. Avec quelques milliers de documents, monter un cluster Elasticsearch est une connerie. Avec dix millions de lignes et des requêtes complexes, faire le malin avec LIKE '%...%' dans Postgres, c'est se tirer une balle dans le pied. SQLite FTS5, lui, est redoutable pour du local-first ou des apps embarquées.
Ce qui m'a sauté aux yeux en confrontant les retours d'experts : les échecs viennent rarement de la techno. Ils viennent de l'absence de stratégie. On ne définit pas la langue du texte, on ignore les fautes de frappe, on oublie l'index GIN, et on découvre BM25 seulement quand le classement paraît bizarre. C'est exactement ce qu'on va corriger ici : choix de l'outil, fonctionnement réel de tsvector/tsquery, personnalisation linguistique et algorithmes de classement comme BM25.
Pourquoi choisir PostgreSQL FTS plutôt qu'Elasticsearch pour un volume modéré ?
J'ai vu des équipes déployer un cluster Elasticsearch pour 50 000 documents. C'est l'une des pires aberrations techniques qu'il m'ait été donné d'observer. Pour un volume modéré — disons jusqu'à un million de lignes — PostgreSQL FTS fait le job avec zéro infrastructure supplémentaire.
Sur 500 000 articles, je combine tsvector, un index GIN et un ORDER BY ts_rank, et je tombe sous les 20 ms par requête. Elasticsearch te demande de gérer des shards, des mappings, des répliques et un cluster JVM à surveiller. Sans parler de la synchronisation avec ta base relationnelle. Tu restes dans le monde SQL, avec transactions et backups inclus — ton équipe maîtrise déjà Postgres, alors pourquoi compliquer ?
Elasticsearch devient intéressant quand tu dépasses plusieurs millions de documents, que tu as besoin de recherche tolérante aux fautes avancée ou d'autocomplete temps réel. Mais pour un volume modéré, c'est un marteau-pilon pour écraser une noix. Utilise Postgres, dors tranquille.
Comprendre tsvector, tsquery et l'index GIN dans PostgreSQL
Ce que la doc appelle to_tsvector() n'est pas magique : c'est un pipeline d'analyse. Le texte est découpé en tokens, normalisé, réduit à ses racines (stemming) et stocké dans un tableau de lexèmes triés. Un exemple concret :
Chaque entrée associe un lexème à sa position dans le document. C'est ce que tsquery vient interroger. to_tsquery('french', 'expert & postgresql') construit une arborescence booléenne que le moteur va résoudre directement contre le vecteur. Sans index, Postgres scanne toute la table et calcule la correspondance à la volée. Avec quelques millions de lignes, c'est catastrophique.
C'est là que l'index GIN entre en scène. GIN signifie Generalized Inverted Index — un index inversé. Il mappe chaque lexème aux positions dans les lignes. Concrètement, CREATE INDEX ... USING GIN (document_tsv) crée une structure où chaque mot apparaissant dans tes documents pointe vers les lignes correspondantes. La recherche devient une simple lecture dans cette structure, plus un tri selon le score. Dans mes tests sur 500 000 lignes, passer du scan complet à l'index GIN fait chuter la latence de 850 ms à 15 ms. Si tu oublies l'index, tu n'as pas de full-text search : tu as un script qui meurt lentement.
Personnaliser la recherche : analyseur par langue et gestion des fautes d'orthographe
Le plus gros mensonge du full-text search, c'est de croire que la langue n'a pas d'importance. Si tu utilises l'analyseur simple par défaut, tu obtiens des tokens bruts : cherche et chercher sont deux mots différents. Avec l'analyseur french, PostgreSQL fait le stemming, supprime les stop words et normalise tout ça. Regarde :
Powerful, non ? Le même principe s'applique à l'italien, l'espagnol et l'allemand. Pour les accents, active l'extension unaccent, sinon ta recherche ignore tous les textes avec des caractères accentués. Un utilisateur qui tape recherche sans accent ne trouvera pas recherché si tu n'as pas fait cette configuration.
Mais la vraie question, c'est : comment savoir si ton analyseur est bon ? Teste avec des fautes de frappe, parce que to_tsquery ne pardonne rien. SELECT to_tsquery('french', 'orthograf') ne matchera jamais orthographe ; pour ça, ajoute pg_trgm et utilise la similarité :
Mon avis : personnalise toujours l'analyseur pour ta langue principale, ajoute unaccent, et définis un seuil de similarité pour les fautes de frappe — mais ne complexifie pas la sauce. Une tolérance trop agressive donne des résultats hors sujet ; une tolérance nulle frustre tes utilisateurs. Le sweet spot, c'est de tester avec les erreurs réelles de tes logs.
... Conclusion
Le full-text search, c'est 10 % d'algorithme et 90 % de culture technique. Les experts que j'ai croisés sont formels : pour un volume modéré, PostgreSQL FTS suffit. Sur 500 000 documents, un index GIN sur une colonne tsvector et une requête ts_rank me donnent des réponses sous 20 ms. Elasticsearch, lui, devient pertinent au-delà de plusieurs millions de documents ou pour des besoins exotiques comme l'autocomplete avancé. En dessous, c'est de la sur-ingénierie.
La clé, c'est de comprendre ce que tu manipules. tsvector stocke des lexèmes avec positions et poids ; tsquery interroge ces lexèmes ; l'index GIN accélère le tout. Et si tu oublies l'analyseur de langue, tu récoltes la honte. J'ai vu des projets français tourner avec l'analyseur anglais par défaut : accents ignorés, stemming incohérent, résultats à côté de la plaque. Un simple to_tsquery('french', 'expert & postgresql') t'évite ce naufrage.
Pour le classement, fie-toi aux scores de type BM25. PostgreSQL les expose via ts_rank et ts_rank_cd, et c'est largement suffisant pour un volume modéré. Teste avec des fautes de frappe, des synonymes, des variations morphologiques. Ajuste les poids de champs, regarde ce qui dégrade, corrige. Mon conseil final : commence simple, maîtrise l'index GIN, et ne parle de migration vers Elasticsearch que quand ton volume et tes besoins le justifient vraiment. Ça t'évitera de devoir réécrire tout ton système de recherche dans six mois.