Lorsqu'une marketplace B2B comme Symposium gère plus de 10 millions de prix dans le secteur alimentaire, avec plusieurs millions de mises à jour quotidiennes, la recherche devient rapidement un défi majeur. Les requêtes SQL traditionnelles montrent leurs limites : temps de réponse trop longs, pertinence approximative, et difficulté à gérer les recherches à facettes. C'est exactement le problème que Meilisearch associé à Laravel a résolu de manière spectaculaire.
Dans cet article, nous allons explorer comment cette combinaison technologique a transformé l'expérience utilisateur sur Symposium, en réduisant les temps de recherche de plusieurs secondes à quelques millisecondes, tout en améliorant drastiquement la pertinence des résultats.
Pourquoi Meilisearch plutôt qu'une autre solution ?
Les limites des recherches SQL traditionnelles
Sur une base de données contenant des millions d'enregistrements, les recherches avec LIKE %term% deviennent impraticables. Les index ne peuvent pas être utilisés efficacement, et les jointures complexes ralentissent considérablement les performances. Sur Symposium, une simple recherche de produit pouvait prendre 3-5 secondes avec MySQL seul.
Comparaison des solutions de recherche
Avant de choisir Meilisearch, plusieurs alternatives ont été évaluées :
Elasticsearch
- ✅ Puissant et mature
- ✅ Riche en fonctionnalités
- ❌ Configuration complexe
- ❌ Ressources serveur importantes (RAM)
- ❌ Courbe d'apprentissage élevée
- ✅ Ultra-rapide et pertinent
- ✅ Interface intuitive
- ❌ Coût élevé pour gros volumes
- ❌ Dépendance à un service externe
- ❌ Facturation par opération
- ✅ Installation simple (un binaire)
- ✅ Performance exceptionnelle (écrit en Rust)
- ✅ Faible consommation de ressources
- ✅ Open source et auto-hébergé
- ✅ Intégration native avec Laravel Scout
- ✅ Tolérance aux fautes de frappe
- ⚠️ Moins de fonctionnalités qu'Elasticsearch (mais suffisant dans 90% des cas)
Architecture de la solution sur Symposium
Vue d'ensemble technique
Le système de recherche de Symposium repose sur une architecture hybride :
- Base de données principale (PostgreSQL) : Stockage des données de référence
- Meilisearch : Moteur de recherche dédié
- Laravel Scout : Couche d'abstraction entre Laravel et Meilisearch
- Queue Laravel : Synchronisation asynchrone des données
Intégration avec Laravel Scout
Laravel Scout simplifie considérablement l'intégration de Meilisearch. Voici la configuration de base :
`php
// config/scout.php
return [
'driver' => env('SCOUT_DRIVER', 'meilisearch'),
'meilisearch' => [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY'),
],
'queue' => env('SCOUT_QUEUE', true),
];
`
Sur le modèle Product, l'implémentation est élégante :
`php
namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Searchable;
class Product extends Model { use Searchable;
/ Index Meilisearch pour ce modèle / public function searchableAs(): string { return 'products_index'; }
/ Données à indexer / public function toSearchableArray(): array { return [ 'id' => $this->id, 'name' => $this->name, 'description' => $this->description, 'brand' => $this->brand, 'category' => $this->category->name, 'sku' => $this->sku, 'price' => $this->price, 'supplier' => $this->supplier->name, 'available' => $this->is_available, 'updated_at' => $this->updated_at->timestamp, ]; }
/
Conditions pour indexer un produit
/
public function shouldBeSearchable(): bool
{
return $this->is_active && $this->is_published;
}
}
`
Gestion des millions de mises à jour quotidiennes
Le défi de la synchronisation massive
Sur Symposium, plusieurs millions de prix sont mis à jour chaque jour via des imports CSV, des API de fournisseurs, et des ajustements manuels. Sans stratégie adaptée, la synchronisation avec Meilisearch pourrait :
- Saturer la queue Laravel
- Ralentir les performances globales
- Créer des incohérences temporaires
Stratégie de synchronisation optimisée
1. Indexation par batch
Plutôt que d'indexer produit par produit, Symposium utilise des batches :
`php
namespace App\Jobs;
use App\Models\Product; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue;
class IndexProductsBatch implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable;
public function __construct( private array $productIds ) {}
public function handle(): void { $products = Product::whereIn('id', $this->productIds) ->with(['category', 'supplier']) ->get();
// Indexation en batch (bien plus rapide)
$products->searchable();
}
}
`
2. Priorisation des mises à jour
Toutes les mises à jour ne sont pas égales. Symposium implémente une file de priorité :
`php
// Mise à jour critique (nouveau produit, changement de prix)
dispatch(new IndexProductsBatch($productIds))->onQueue('search-high');
// Mise à jour standard (description, images) dispatch(new IndexProductsBatch($productIds))->onQueue('search-normal');
// Mise à jour faible (statistiques, vues)
dispatch(new IndexProductsBatch($productIds))->onQueue('search-low');
`
3. Fenêtres de synchronisation
Les imports massifs (millions d'entrées) sont traités pendant les heures creuses avec un système de throttling :
`php
use Illuminate\Support\Facades\RateLimiter;
public function syncMassiveImport(array $productIds): void
{
$chunks = array_chunk($productIds, 1000);
foreach ($chunks as $chunk) {
RateLimiter::attempt(
'meilisearch-sync',
$perMinute = 60,
function() use ($chunk) {
dispatch(new IndexProductsBatch($chunk));
}
);
}
}
`
Configuration avancée de Meilisearch
Paramètres de recherche pour la pertinence
Meilisearch permet de configurer finement les critères de pertinence :
`php
use MeiliSearch\Client;
$client = new Client(config('scout.meilisearch.host'), config('scout.meilisearch.key')); $index = $client->index('products_index');
// Configuration des attributs de recherche (par ordre de priorité) $index->updateSearchableAttributes([ 'name', // Poids le plus élevé 'brand', 'sku', 'category', 'description', // Poids le plus faible ]);
// Attributs pour les filtres $index->updateFilterableAttributes([ 'category', 'brand', 'price', 'supplier', 'available', ]);
// Attributs pour le tri $index->updateSortableAttributes([ 'price', 'updated_at', 'name', ]);
// Configuration de la pertinence
$index->updateRankingRules([
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness',
]);
`
Recherche avec filtres et facettes
L'implémentation côté contrôleur est simple et puissante :
`php
public function search(Request $request)
{
$results = Product::search($request->input('q'))
->query(fn ($meilisearch) => $meilisearch
->filter([
'category = "' . $request->category . '"',
'price >= ' . $request->min_price,
'price <= ' . $request->max_price,
'available = true',
])
->sort(['price:asc'])
)
->paginate(20);
return response()->json($results);
}
`
Résultats et métriques de performance
Avant Meilisearch (MySQL uniquement)
- ⏱️ Temps de recherche moyen : 3,2 secondes
- 📊 Recherches > 5 secondes : 42%
- ❌ Taux d'abandon sur recherche : 35%
- 🎯 Pertinence utilisateur : 6,2/10
Après Meilisearch
- ⚡ Temps de recherche moyen : 45 millisecondes
- 📊 Recherches > 100ms : < 1%
- ✅ Taux d'abandon sur recherche : 8%
- 🎯 Pertinence utilisateur : 8,9/10
Consommation de ressources
Sur Symposium avec 10+ millions de produits indexés :
- RAM utilisée par Meilisearch : ~8 GB
- Espace disque index : ~12 GB
- CPU moyen : < 5% (pics à 25% lors des imports massifs)
Bonnes pratiques et pièges à éviter
✅ À faire
❌ À éviter
shouldBeSearchable() : Peut indexer des données non publiées
Négliger les filtres : Définir les attributs filtrables dès le début
Sous-estimer la RAM : Prévoir large pour de gros volumes
Ignorer les snapshots : Backups réguliers de l'index
Conclusion
L'intégration de Meilisearch avec Laravel sur Symposium a démontré qu'il est possible de gérer des volumes massifs de données avec d'excellentes performances, sans la complexité d'Elasticsearch ni le coût d'Algolia. Les 10+ millions de produits sont désormais recherchables en quelques millisecondes, avec une pertinence remarquable et une tolérance aux fautes de frappe appréciée des utilisateurs.
Les principaux facteurs de succès :
- Architecture hybride PostgreSQL + Meilisearch
- Synchronisation asynchrone intelligente via queues Laravel
- Configuration fine de la pertinence selon le métier
- Stratégie de batching pour les imports massifs
Que vous gériez une marketplace, un catalogue produit, ou une base documentaire, nous pouvons vous accompagner dans le choix et l'implémentation de la solution de recherche la plus adaptée à votre contexte.