Hasina Razafintsalama

Hasina RAZAFINTSALAMA

← Retour au Blog
Backend

PostgreSQL pour Laravel : JSONB, recherche full-text et pgvector

PostgreSQL fait beaucoup de ce pour quoi les équipes ajoutent un service séparé : documents flexibles, vraie recherche full-text, correspondance floue, et similarité vectorielle. Voici comment utiliser chacun depuis Laravel.

2026-09-03·11 min

Un pattern courant consiste à brancher Elasticsearch pour la recherche et une base vectorielle dédiée pour les embeddings, puis à passer le reste du projet à garder trois stockages synchronisés. Pour beaucoup d'applications, PostgreSQL couvre déjà ces besoins assez bien pour que les services supplémentaires ne valent pas leur coût opérationnel. Voici comment utiliser JSONB, la recherche full-text, la correspondance floue et pgvector directement depuis Laravel.

JSONB : des documents flexibles que vous pouvez quand même interroger

Utilisez une colonne jsonb pour des données dont la forme varie ou évolue, castez-la en tableau ou en ArrayObject sur le modèle, et interrogez-la avec les opérateurs -> et ->> ou le whereJsonContains de Laravel. Ajoutez un index GIN pour que ces requêtes utilisent un index au lieu de scanner chaque ligne.

php
// migration
Schema::table('products', function (Blueprint $table) {
    $table->jsonb('attributes')->nullable();
});
DB::statement('CREATE INDEX products_attributes_gin ON products USING gin (attributes)');

// modèle
protected $casts = ['attributes' => AsArrayObject::class];

// requête
Product::whereJsonContains('attributes->tags', 'waterproof')
    ->where('attributes->weight_grams', '<', 500)
    ->get();

Recherche full-text native

La recherche full-text de PostgreSQL transforme le texte en tsvector (tokens normalisés et lemmatisés) et le compare à un tsquery, avec un ranking. Dans Laravel, $table->fullText() sur le driver pgsql crée l'index GIN tsvector pour vous, et whereFullText() lance la correspondance. Pour contrôler la pondération et la langue, ajoutez vous-même une colonne tsvector générée.

php
// migration
Schema::table('articles', function (Blueprint $table) {
    $table->fullText(['title', 'body']);
});

// requête, triée par pertinence
Article::whereFullText(['title', 'body'], 'postgres full text search')
    ->orderByRaw("ts_rank(to_tsvector('english', title || ' ' || body), plainto_tsquery(?)) DESC", ['postgres full text search'])
    ->limit(20)
    ->get();

Correspondance floue avec pg_trgm

La recherche full-text porte sur les mots et le sens ; pg_trgm porte sur la similarité de caractères, ce qui est ce que vous voulez pour la tolérance aux fautes de frappe, le "vouliez-vous dire", et la correspondance sur des références ou des noms de produits. Activez l'extension, ajoutez un index trigramme GIN, et utilisez la fonction similarity() ou l'opérateur %.

Similarité vectorielle avec pgvector

pgvector ajoute un type de colonne vector et des opérateurs de similarité, donc vous pouvez stocker des embeddings à côté de vos données relationnelles et faire une recherche sémantique avec une requête SQL normale. Utilisez un index HNSW pour la vitesse, l'opérateur <=> pour la distance cosinus, et gardez le filtre metadata dans la même clause WHERE. Pour un corpus modéré, c'est suffisant pour faire du RAG sans base vectorielle séparée.

sql
CREATE EXTENSION IF NOT EXISTS vector;

ALTER TABLE documents ADD COLUMN embedding vector(1536);
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);

-- récupérer les 5 chunks les plus proches, filtrés par tenant
SELECT id, content
FROM documents
WHERE tenant_id = $1
ORDER BY embedding <=> $2
LIMIT 5;

Besoin, service externe, équivalent PostgreSQL

BesoinService externe habituelNatif PostgreSQL
Documents flexiblesMongoDBjsonb + index GIN
Recherche full-textElasticsearchtsvector + ts_rank
Correspondance tolérante aux fautesElasticsearch, Algoliasimilarity pg_trgm
Recherche vectorielle / sémantiquePinecone, Qdrant, Chromapgvector + HNSW
Texte insensible à la casseLogique applicativecitext

Quand PostgreSQL ne suffit plus

Passez à un moteur dédié quand les chiffres l'imposent : un corpus très large où les temps de construction d'index et la mémoire de pgvector deviennent un problème, une recherche qui a besoin de tuning de pertinence avancé, de facettes et de gestion des fautes sur plusieurs langues, ou un volume de requêtes qui rivalise avec votre charge transactionnelle pour les mêmes ressources de base. Jusque-là, une seule base, c'est une seule chose à opérer, sauvegarder et raisonner.

FAQ

Peut-on faire de la recherche full-text sans Elasticsearch ?
Pour beaucoup d'applications, oui. La recherche full-text de PostgreSQL gère la lemmatisation, le ranking et la correspondance multi-colonnes, et pg_trgm ajoute la tolérance aux fautes. Vous la dépassez quand vous avez besoin de tuning de pertinence avancé, de facettes, ou de recherche multi-langue à grande échelle, moment où un moteur dédié gagne son coût.
Comment stocker des embeddings dans Laravel ?
Activez l'extension pgvector, ajoutez une colonne vector avec la bonne dimension, et utilisez un package comme pgvector-php ou du SQL brut pour insérer et interroger. Ajoutez un index HNSW et utilisez l'opérateur <=> pour la distance cosinus. Les embeddings vivent à côté de vos données relationnelles, donc une recherche de similarité filtrée est une seule requête.
pgvector suffit-il pour un RAG en production ?
Pour un corpus petit à modéré, oui, et ça retire tout un service de votre stack. Les limites apparaissent avec des index très larges, où le temps de construction, la mémoire et le tuning du rappel deviennent un vrai travail, et quand le volume de recherche sémantique rivalise avec les requêtes transactionnelles sur la même base.
JSONB ou une table normalisée ?
Normalisez les données que vous interrogez, filtrez, joignez et contraignez. Utilisez JSONB pour des données dont la forme varie, qui sont écrites et lues d'un bloc, ou qui évoluent plus vite que vous ne voulez lancer de migrations. Un partage courant : colonnes normalisées pour les champs que vous indexez, JSONB pour la longue traîne d'attributs.
Comment indexer une colonne JSONB dans Laravel ?
Le schema builder de Laravel n'expose pas les index GIN directement, donc créez-en un avec DB::statement dans la migration : CREATE INDEX ... USING gin (colonne). Ensuite, whereJsonContains et les requêtes de containment sur cette colonne utilisent l'index au lieu d'un scan complet.

PostgreSQL n'est pas juste une base relationnelle ; c'est une base relationnelle avec un moteur de recherche, un matcher flou et un stockage vectoriel intégrés. Recourir à ceux-là avant d'ajouter Elasticsearch ou une base vectorielle garde votre stack petite, et les petites stacks sont plus faciles à opérer.

Besoin d'aide sur ce sujet ? Développement Full Stack

Découvrir ce service