Architettura del Servizio di Ricerca: Applicazione Flask Standalone con Valkey

Questo articolo spiega come il nostro servizio di ricerca funzioni come un'applicazione Flask standalone su un server separato, utilizzando Valkey (un fork di Redis) per la ricerca vettoriale ad alte prestazioni, la cache e l'autocompletamento.

Il Problema: Prestazioni e Scalabilità della Ricerca

Le operazioni di ricerca sono computazionalmente costose:

  • Estrazione filtri: Confrontare oltre 2.500 frasi con ogni query

  • Ricerche correlate: Calcolare la similarità su 65K query

  • Autocompletamento: Corrispondenza per prefisso su 65K query

  • Filtraggio prodotti: Filtrare oltre 5K prodotti per molteplici criteri

Eseguire queste operazioni sul server web principale causa:

  • Lentezza nel caricamento delle pagine: La ricerca blocca altre richieste

  • Pressione sulla memoria: Gli embedding occupano un'enorme quantità di memoria

  • Contesa della CPU: Il calcolo della similarità è intensivo per la CPU

  • Difficoltà di scalabilità: Impossibile scalare la ricerca in modo indipendente

Abbiamo bisogno di un servizio di ricerca dedicato che possa scalare indipendentemente.

La Soluzione: Servizio di Ricerca Standalone

Eseguiamo un'applicazione Flask separata su un server dedicato:

Server Web Principale
    ↓ Chiamate API HTTP
Servizio di Ricerca
    ↓ Query Valkey
Server Valkey

Questa architettura fornisce:

  • Scalabilità indipendente: Scalare il servizio di ricerca senza influenzare il server web principale

  • Isolamento delle risorse: Le operazioni di ricerca non impattano il server web principale

  • Caching: Valkey memorizza in cache i risultati per query ripetute veloci

  • Alta disponibilità: Il servizio di ricerca può riavviarsi senza influenzare il server web principale

Componenti del Servizio di Ricerca

1. API Estrazione Filtri

  • Endpoint: /api/extract_filters

  • Scopo: Estrarre filtri strutturati da query in linguaggio naturale

  • Esempio:

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

Risposta:
{
  "Form Factor": "Mini PC",
  "Main Memory": "16"
}

Implementazione:

Caching: Mappature frasi memorizzate in cache in Valkey (TTL 30 giorni)

2. API Ricerche Correlate

  • Endpoint: /api/related

  • Scopo: Trovare query semanticamente simili usando la ricerca vettoriale

  • Esempio:

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

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

Implementazione:

  • Creare embedding della query usando all-mpnet-base-v2

  • Interrogare RediSearch di Valkey per i vicini più prossimi

  • Restituire i primi N risultati ordinati per similarità

Caching: Risultati memorizzati in cache in Valkey (TTL 7 giorni)

3. API Autocompletamento

  • Endpoint: /api/autocomplete

  • Scopo: Suggerire query mentre l'utente digita

  • Esempio:

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

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

Implementazione:

  • Interrogare RediSearch di Valkey con corrispondenza per prefisso

  • Classificare per popolarità (punteggio impressioni + clic)

  • Restituire i primi N suggerimenti

Caching: Indice di autocompletamento in Valkey (aggiornato giornalmente)

4. API Query Popolari

  • Endpoint: /api/popular

  • Scopo: Ottenere le query più popolari

  • Esempio:

GET /api/popular?limit=10

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

Implementazione:

  • Caricare query da Valkey o JSON

  • Ordinare per punteggio di traffico (impressioni + clic)

  • Restituire le prime N query

Caching: Query popolari memorizzate in cache in Valkey (TTL 30 giorni)

Integrazione con Valkey

Valkey è un fork di Redis che fornisce:

  • Ricerca vettoriale: Modulo RediSearch per la ricerca per similarità

  • Caching: Archivio chiave-valore in memoria veloce

  • Autocompletamento: Corrispondenza per prefisso con insiemi ordinati

  • Persistenza: AOF (Append-Only File) per durabilità

Ricerca Vettoriale con RediSearch

Utilizziamo il modulo RediSearch di Valkey per la ricerca per similarità vettoriale:

Creazione Indice:

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

Ricerca Vettoriale:

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()}
)

Questo restituisce i 10 vicini più prossimi per similarità coseno.

Strategia di Caching

Memorizziamo in cache in Valkey diversi tipi di dati:

Mappature Frasi (TTL 30 giorni):

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

Ricerche Correlate (TTL 7 giorni):

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

Query Popolari (TTL 30 giorni):

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

Indice Autocompletamento (aggiornato giornalmente):

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

Autocompletamento con Insiemi Ordinati

Utilizziamo insiemi ordinati di Valkey per l'autocompletamento:

Struttura Indice:

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]

Ricerca per Prefisso:

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

Questo restituisce le prime 10 query che iniziano con "mini", ordinate per punteggio.

Comunicazione API

Il server web principale chiama il servizio di ricerca via HTTP:

Estrazione Filtri

from app.shared.filter_service import extract_filters_from_query

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

Ricerche Correlate

import requests

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

Autocompletamento

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

Gestione Errori e Fallback

Il server web principale gestisce con garbo i fallimenti del servizio di ricerca:

try:
    filters = extract_filters_from_query(query)
except Exception as e:
    logger.error(f"Search service failed: {e}")
    filters = {}  # Fallback a filtri vuoti

Ciò garantisce che il server web principale continui a funzionare anche se il servizio di ricerca è fuori servizio.


### Configurazione di Rete

Tutti i server sono su una rete privata:

- **Server web principale**: Può accedere al servizio di ricerca e a Valkey

- **Servizio di ricerca**: Può accedere a Valkey

- **Valkey**: Accessibile solo dal server web principale e dal servizio di ricerca

Nessun accesso esterno al servizio di ricerca o a Valkey.

## Integrazione con la Pipeline SEO

Il servizio di ricerca si integra con la pipeline SEO:

### Step 11: Migrazione a Valkey

La pipeline SEO carica i dati in Valkey:

```python
# Caricare embedding delle query
for query, embedding in zip(queries, embeddings):
    client.hset(f"query:{query_hash}", mapping={
        "query": query,
        "embedding": embedding.tobytes(),
        "score": score
    })

# Creare indice RediSearch
client.ft("queries_idx").create_index([...])

Vedi Migrazione Valkey per i dettagli.

Registrazione delle Query

Il servizio di ricerca registra le query per la 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")

Questi log alimentano nuovamente lo Step 1d: Recupero Query Live.

Riferimenti

Concetti Tecnici

Articoli Correlati

Riepilogo

Il nostro servizio di ricerca funziona come un'applicazione Flask standalone su un server separato:

Architettura:

  • Applicazione Flask standalone su server dedicato

  • Valkey (fork di Redis) per caching e ricerca vettoriale

  • API HTTP per la comunicazione con il server web principale

API:

  • /api/extract_filters - Estrai filtri dalle query

  • /api/related - Trova query simili (ricerca vettoriale)

  • /api/autocomplete - Suggerisci query (corrispondenza per prefisso)

  • /api/popular - Ottieni query popolari

Funzionalità Valkey:

  • Ricerca vettoriale (modulo RediSearch)

  • Caching (TTL 30 giorni per mappature frasi)

  • Autocompletamento (insiemi ordinati)

  • Persistenza (AOF)

Vantaggi:

  • Scalabilità indipendente

  • Isolamento delle risorse

  • Alte prestazioni (caching Valkey)

  • Tolleranza ai guasti (degradazione graduale)

Questa architettura consente una ricerca veloce e scalabile mantenendo reattivo il server web principale.


← Torna all'Indice della Documentazione