Architecture du Service de Recherche : Application Flask Autonome avec Valkey

Cet article explique comment notre service de recherche fonctionne comme une application Flask autonome sur un serveur séparé, utilisant Valkey (un fork de Redis) pour la recherche vectorielle haute performance, la mise en cache et la saisie semi-automatique.

Le Problème : Performance et Évolutivité de la Recherche

Les opérations de recherche sont coûteuses en calcul :

  • Extraction de filtres : Faire correspondre plus de 2 500 expressions à chaque requête

  • Recherches associées : Calculer la similarité sur 65 000 requêtes

  • Saisie semi-automatique : Correspondance par préfixe sur 65 000 requêtes

  • Filtrage de produits : Filtrer plus de 5 000 produits selon plusieurs critères

Exécuter ces opérations sur le serveur web principal entraîne :

  • Lenteur du chargement des pages : La recherche bloque les autres requêtes

  • Pression mémoire : Les embeddings représentent une empreinte mémoire massive

  • Contention CPU : Le calcul de similarité est gourmand en CPU

  • Difficulté de mise à l'échelle : Impossible de mettre à l'échelle la recherche indépendamment

Nous avons besoin d'un service de recherche dédié pouvant évoluer indépendamment.

La Solution : Service de Recherche Autonome

Nous exécutons une application Flask séparée sur un serveur dédié :

Serveur Web Principal
    ↓ Appels API HTTP
Service de Recherche
    ↓ Requêtes Valkey
Serveur Valkey

Cette architecture offre :

  • Mise à l'échelle indépendante : Mettre à l'échelle le service de recherche sans affecter le serveur web principal

  • Isolation des ressources : Les opérations de recherche n'impactent pas le serveur web principal

  • Mise en cache : Valkey met en cache les résultats pour les requêtes répétées rapides

  • Haute disponibilité : Le service de recherche peut redémarrer sans affecter le serveur web principal

Composants du Service de Recherche

1. API d'Extraction de Filtres

  • Point de terminaison : /api/extract_filters

  • Objectif : Extraire des filtres structurés de requêtes en langage naturel

  • Exemple :

GET /api/extract_filters?q=mini+pc+16gb+ram

Réponse :
{
  "Form Factor": "Mini PC",
  "Main Memory": "16"
}

Implémentation :

Mise en cache : Les correspondances d'expressions sont mises en cache dans Valkey (TTL de 30 jours)

2. API de Recherches Associées

  • Point de terminaison : /api/related

  • Objectif : Trouver des requêtes sémantiquement similaires en utilisant la recherche vectorielle

  • Exemple :

POST /api/related
{
  "query": "mini pc",
  "limit": 10
}

Réponse :
{
  "related": [
    {"query": "small computer", "similarity": 0.92},
    {"query": "compact desktop", "similarity": 0.89},
    {"query": "mini pc 8gb", "similarity": 0.87}
  ]
}

Implémentation :

  • Intégrer (embed) la requête en utilisant all-mpnet-base-v2

  • Interroger Valkey RediSearch pour les plus proches voisins

  • Renvoyer les N meilleurs résultats triés par similarité

Mise en cache : Les résultats sont mis en cache dans Valkey (TTL de 7 jours)

3. API de Saisie Semi-Automatique

  • Point de terminaison : /api/autocomplete

  • Objectif : Suggérer des requêtes pendant la saisie de l'utilisateur

  • Exemple :

GET /api/autocomplete?q=mini+p&limit=5

Réponse :
{
  "suggestions": [
    "mini pc",
    "mini pc 16gb",
    "mini pc 8gb ram",
    "mini pc fanless",
    "mini pc windows 11"
  ]
}

Implémentation :

  • Interroger Valkey RediSearch avec une correspondance par préfixe

  • Classer par popularité (score d'impressions + clics)

  • Renvoyer les N meilleures suggestions

Mise en cache : L'index de saisie semi-automatique dans Valkey (mis à jour quotidiennement)

4. API des Requêtes Populaires

  • Point de terminaison : /api/popular

  • Objectif : Obtenir les requêtes les plus populaires

  • Exemple :

GET /api/popular?limit=10

Réponse :
{
  "queries": [
    "mini pc",
    "thin client",
    "industrial pc",
    "all in one pc"
  ]
}

Implémentation :

  • Charger les requêtes depuis Valkey ou JSON

  • Trier par score de trafic (impressions + clics)

  • Renvoyer les N meilleures requêtes

Mise en cache : Les requêtes populaires sont mises en cache dans Valkey (TTL de 30 jours)

Intégration de Valkey

Valkey est un fork de Redis qui fournit :

  • Recherche vectorielle : Module RediSearch pour la recherche par similarité

  • Mise en cache : Stockage clé-valeur en mémoire rapide

  • Saisie semi-automatique : Correspondance par préfixe avec des ensembles triés

  • Persistance : AOF (Append-Only File) pour la durabilité

Recherche Vectorielle avec RediSearch

Nous utilisons le module RediSearch de Valkey pour la recherche par similarité vectorielle :

Création d'Index :

client.ft("queries_idx").create_index([
    VectorField("embedding", "FLAT", {
        "TYPE": "FLOAT32",
        "DIM": 768,
        "DISTANCE_METRIC": "COSINE"
    }),
    TextField("query"),
    NumericField("score")
])

Recherche Vectorielle :

query_embedding = model.encode(query)
results = client.ft("queries_idx").search(
    Query("*=>[KNN 10 @embedding $vec AS score]")
    .sort_by("score")
    .return_fields("query", "score")
    .dialect(2),
    query_params={"vec": query_embedding.tobytes()}
)

Cela renvoie les 10 plus proches voisins par similarité cosinus.

Stratégie de Mise en Cache

Nous mettons en cache plusieurs types de données dans Valkey :

Correspondances d'Expressions (TTL de 30 jours) :

client.setex(
    "seo:phrase_mappings",
    30 * 24 * 3600,
    json.dumps(phrase_mappings)
)

Recherches Associées (TTL de 7 jours) :

cache_key = f"related:{query_hash}"
client.setex(cache_key, 7 * 24 * 3600, json.dumps(results))

Requêtes Populaires (TTL de 30 jours) :

client.setex(
    "seo:popular_queries",
    30 * 24 * 3600,
    json.dumps(popular_queries)
)

Index de Saisie Semi-Automatique (mis à jour quotidiennement) :

for query, score in queries:
    client.zadd("autocomplete:mini", {query: score})

Saisie Semi-Automatique avec des Ensembles Triés

Nous utilisons les ensembles triés de Valkey pour la saisie semi-automatique :

Structure de l'Index :

autocomplete:m     → ["mini pc": 5000, "mini computer": 3000]
autocomplete:mi    → ["mini pc": 5000, "mini computer": 3000]
autocomplete:min   → ["mini pc": 5000, "mini computer": 3000]
autocomplete:mini  → ["mini pc": 5000, "mini computer": 3000]

Recherche par Préfixe :

prefix = "mini"
results = client.zrevrange(f"autocomplete:{prefix}", 0, 9, withscores=True)

Cela renvoie les 10 meilleures requêtes commençant par "mini", triées par score.

Communication API

Le serveur web principal appelle le service de recherche via HTTP :

Extraction de Filtres

from app.shared.filter_service import extract_filters_from_query

filters = extract_filters_from_query("mini pc 16gb ram")
# Appelle en interne : GET SEARCH_SERVICE_URL/api/extract_filters?q=...

Recherches Associées

import requests

response = requests.post(
    "SEARCH_SERVICE_URL/api/related",
    json={"query": "mini pc", "limit": 10},
    timeout=2
)
related = response.json()["related"]

Saisie Semi-Automatique

response = requests.get(
    "SEARCH_SERVICE_URL/api/autocomplete",
    params={"q": "mini p", "limit": 5},
    timeout=1
)
suggestions = response.json()["suggestions"]

Gestion des Erreurs et Solutions de Rechange

Le serveur web principal gère les défaillances du service de recherche avec élégance :

try:
    filters = extract_filters_from_query(query)
except Exception as e:
    logger.error(f"Échec du service de recherche : {e}")
    filters = {}  # Solution de rechange : filtres vides

Cela garantit que le serveur web principal continue de fonctionner même si le service de recherche est indisponible.

Configuration Réseau

Tous les serveurs sont sur un réseau privé :

  • Serveur web principal : Peut accéder au service de recherche et à Valkey

  • Service de recherche : Peut accéder à Valkey

  • Valkey : Accessible uniquement depuis le serveur web principal et le service de recherche

Aucun accès externe au service de recherche ou à Valkey.

Intégration avec le Pipeline SEO

Le service de recherche s'intègre au pipeline SEO :

Étape 11 : Migration vers Valkey

Le pipeline SEO charge les données dans Valkey :

# Charger les embeddings de requêtes
for query, embedding in zip(queries, embeddings):
    client.hset(f"query:{query_hash}", mapping={
        "query": query,
        "embedding": embedding.tobytes(),
        "score": score
    })

# Créer l'index RediSearch
client.ft("queries_idx").create_index([...])

Voir Migration Valkey pour les détails.

Journalisation des Requêtes

Le service de recherche journalise les requêtes pour le pipeline SEO :

log_entry = {
    "timestamp": datetime.now(timezone.utc).isoformat(),
    "query": query,
    "filters_extracted": filters,
    "results_count": len(results)
}
with open(SEO_LIVE_QUERIES_LOG, "a") as f:
    f.write(json.dumps(log_entry) + "\n")

Ces journaux alimentent à nouveau l'Étape 1d : Récupérer les Requêtes en Direct.

Références

Concepts Techniques

Articles Connexes

Résumé

Notre service de recherche fonctionne comme une application Flask autonome sur un serveur séparé :

Architecture :

  • Application Flask autonome sur un serveur dédié

  • Valkey (fork de Redis) pour la mise en cache et la recherche vectorielle

  • API HTTP pour la communication avec le serveur web principal

APIs :

  • /api/extract_filters - Extraire des filtres des requêtes

  • /api/related - Trouver des requêtes similaires (recherche vectorielle)

  • /api/autocomplete - Suggérer des requêtes (correspondance par préfixe)

  • /api/popular - Obtenir les requêtes populaires

Fonctionnalités de Valkey :

  • Recherche vectorielle (module RediSearch)

  • Mise en cache (TTL de 30 jours pour les correspondances d'expressions)

  • Saisie semi-automatique (ensembles triés)

  • Persistance (AOF)

Avantages :

  • Mise à l'échelle indépendante

  • Isolation des ressources

  • Haute performance (mise en cache Valkey)

  • Tolérance aux pannes (dégradation gracieuse)

Cette architecture permet une recherche rapide et évolutive tout en gardant le serveur web principal réactif.


← Retour à l'Index de Documentation