Suchdienst-Architektur: Eigenständige Flask-Anwendung mit Valkey
Dieser Artikel erklärt, wie unser Suchdienst als eigenständige Flask-Anwendung auf einem separaten Server läuft und dabei Valkey (Redis-Fork) für hochperformante Vektorsuche, Caching und Autovervollständigung nutzt.
Das Problem: Suchleistung und Skalierbarkeit
Suchoperationen sind rechenintensiv:
-
Filterextraktion: 2.500+ Phrasen mit jeder Abfrage abgleichen
-
Verwandte Suchen: Ähnlichkeit über 65.000 Abfragen berechnen
-
Autovervollständigung: Präfix-Matching auf 65.000 Abfragen
-
Produktfilterung: 5.000+ Produkte nach mehreren Kriterien filtern
Die Ausführung dieser Operationen auf dem Haupt-Webserver führt zu:
-
Langsamen Seitenladezeiten: Suchvorgänge blockieren andere Anfragen
-
Speicherbelastung: Embeddings haben einen enormen Speicherbedarf
-
CPU-Konkurrenz: Ähnlichkeitsberechnung ist CPU-intensiv
-
Skalierungsproblemen: Suche kann nicht unabhängig skaliert werden
Wir benötigen einen dedizierten Suchdienst, der unabhängig skaliert werden kann.
Die Lösung: Eigenständiger Suchdienst
Wir betreiben eine separate Flask-Anwendung auf einem dedizierten Server:
Haupt-Webserver
↓ HTTP-API-Aufrufe
Suchdienst
↓ Valkey-Abfragen
Valkey-Server
Diese Architektur bietet:
-
Unabhängige Skalierung: Suchdienst skalieren, ohne den Haupt-Webserver zu beeinflussen
-
Ressourcenisolation: Suchoperationen beeinträchtigen den Haupt-Webserver nicht
-
Caching: Valkey cached Ergebnisse für schnelle wiederholte Abfragen
-
Hohe Verfügbarkeit: Suchdienst kann neu starten, ohne den Haupt-Webserver zu beeinflussen
Suchdienst-Komponenten
1. Filter-Extraktions-API
-
Endpunkt:
/api/extract_filters -
Zweck: Strukturierte Filter aus natürlichsprachigen Abfragen extrahieren
-
Beispiel:
GET /api/extract_filters?q=mini+pc+16gb+ram
Antwort:
{
"Form Factor": "Mini PC",
"Main Memory": "16"
}
Implementierung:
-
Lade Phrasen-zu-Filter-Zuordnungen aus Valkey oder JSON
-
Gleiche Phrasen mit Wortgrenzen-Regex ab
-
Gib strukturierte Filter zurück
Caching: Phrasenzuordnungen in Valkey gecached (30 Tage TTL)
2. Verwandte-Suchen-API
-
Endpunkt:
/api/related -
Zweck: Semantisch ähnliche Abfragen mittels Vektorsuche finden
-
Beispiel:
POST /api/related
{
"query": "mini pc",
"limit": 10
}
Antwort:
{
"related": [
{"query": "small computer", "similarity": 0.92},
{"query": "compact desktop", "similarity": 0.89},
{"query": "mini pc 8gb", "similarity": 0.87}
]
}
Implementierung:
-
Embedde Abfrage mit all-mpnet-base-v2
-
Frage Valkey RediSearch nach nächsten Nachbarn ab
-
Gib die Top-N-Ergebnisse sortiert nach Ähnlichkeit zurück
Caching: Ergebnisse in Valkey gecached (7 Tage TTL)
3. Autovervollständigungs-API
-
Endpunkt:
/api/autocomplete -
Zweck: Abfragen vorschlagen, während der Benutzer tippt
-
Beispiel:
GET /api/autocomplete?q=mini+p&limit=5
Antwort:
{
"suggestions": [
"mini pc",
"mini pc 16gb",
"mini pc 8gb ram",
"mini pc fanless",
"mini pc windows 11"
]
}
Implementierung:
-
Frage Valkey RediSearch mit Präfix-Matching ab
-
Sortiere nach Popularität (Impression + Klick-Score)
-
Gib die Top-N-Vorschläge zurück
Caching: Autovervollständigungs-Index in Valkey (täglich aktualisiert)
4. Beliebte-Abfragen-API
-
Endpunkt:
/api/popular -
Zweck: Die beliebtesten Abfragen abrufen
-
Beispiel:
GET /api/popular?limit=10
Antwort:
{
"queries": [
"mini pc",
"thin client",
"industrial pc",
"all in one pc"
]
}
Implementierung:
-
Lade Abfragen aus Valkey oder JSON
-
Sortiere nach Traffic-Score (Impressionen + Klicks)
-
Gib die Top-N-Abfragen zurück
Caching: Beliebte Abfragen in Valkey gecached (30 Tage TTL)
Valkey-Integration
Valkey ist ein Redis-Fork, der Folgendes bietet:
-
Vektorsuche: RediSearch-Modul für Ähnlichkeitssuche
-
Caching: Schneller In-Memory-Key-Value-Store
-
Autovervollständigung: Präfix-Matching mit sortierten Mengen
-
Persistenz: AOF (Append-Only File) für Dauerhaftigkeit
Vektorsuche mit RediSearch
Wir nutzen Valkeys RediSearch-Modul für Vektor-Ähnlichkeitssuche:
Index-Erstellung:
client.ft("queries_idx").create_index([
VectorField("embedding", "FLAT", {
"TYPE": "FLOAT32",
"DIM": 768,
"DISTANCE_METRIC": "COSINE"
}),
TextField("query"),
NumericField("score")
])
Vektorsuche:
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()}
)
Dies gibt die 10 nächsten Nachbarn nach Kosinus-Ähnlichkeit zurück.
Caching-Strategie
Wir cachen mehrere Datentypen in Valkey:
Phrasenzuordnungen (30 Tage TTL):
client.setex(
"seo:phrase_mappings",
30 * 24 * 3600,
json.dumps(phrase_mappings)
)
Verwandte Suchen (7 Tage TTL):
cache_key = f"related:{query_hash}"
client.setex(cache_key, 7 * 24 * 3600, json.dumps(results))
Beliebte Abfragen (30 Tage TTL):
client.setex(
"seo:popular_queries",
30 * 24 * 3600,
json.dumps(popular_queries)
)
Autovervollständigungs-Index (täglich aktualisiert):
for query, score in queries:
client.zadd("autocomplete:mini", {query: score})
Autovervollständigung mit sortierten Mengen
Wir nutzen Valkey sortierte Mengen für die Autovervollständigung:
Index-Struktur:
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]
Präfix-Lookup:
prefix = "mini"
results = client.zrevrange(f"autocomplete:{prefix}", 0, 9, withscores=True)
Dies gibt die Top-10-Abfragen zurück, die mit "mini" beginnen, sortiert nach Score.
API-Kommunikation
Der Haupt-Webserver ruft den Suchdienst über HTTP auf:
Filterextraktion
from app.shared.filter_service import extract_filters_from_query
filters = extract_filters_from_query("mini pc 16gb ram")
# Interner Aufruf: GET SEARCH_SERVICE_URL/api/extract_filters?q=...
Verwandte Suchen
import requests
response = requests.post(
"SEARCH_SERVICE_URL/api/related",
json={"query": "mini pc", "limit": 10},
timeout=2
)
related = response.json()["related"]
Autovervollständigung
response = requests.get(
"SEARCH_SERVICE_URL/api/autocomplete",
params={"q": "mini p", "limit": 5},
timeout=1
)
suggestions = response.json()["suggestions"]
Fehlerbehandlung und Fallbacks
Der Haupt-Webserver behandelt Ausfälle des Suchdienstes elegant:
try:
filters = extract_filters_from_query(query)
except Exception as e:
logger.error(f"Search service failed: {e}")
filters = {} # Fallback auf leere Filter
Dies stellt sicher, dass der Haupt-Webserver auch dann weiterfunktioniert, wenn der Suchdienst ausfällt.
### Netzwerkkonfiguration
Alle Server befinden sich in einem privaten Netzwerk:
- **Haupt-Webserver**: Kann auf Suchdienst und Valkey zugreifen
- **Suchdienst**: Kann auf Valkey zugreifen
- **Valkey**: Nur vom Haupt-Webserver und Suchdienst erreichbar
Kein externer Zugriff auf Suchdienst oder Valkey.
## Integration in die SEO-Pipeline
Der Suchdienst ist in die SEO-Pipeline integriert:
### Schritt 11: Migration zu Valkey
Die SEO-Pipeline lädt Daten in Valkey:
```python
# Lade Abfrage-Embeddings
for query, embedding in zip(queries, embeddings):
client.hset(f"query:{query_hash}", mapping={
"query": query,
"embedding": embedding.tobytes(),
"score": score
})
# Erstelle RediSearch-Index
client.ft("queries_idx").create_index([...])
Siehe Valkey-Migration für Details.
Abfrage-Protokollierung
Der Suchdienst protokolliert Abfragen für die SEO-Pipeline:
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")
Diese Protokolle speisen Schritt 1d zurück: Live-Abfragen abrufen.
Referenzen
Technische Konzepte
-
Valkey - Offizielle Website
-
Redis - Offizielle Website (Valkey-Fork)
-
RediSearch - Vektorsuchmodul
-
Kosinusähnlichkeit - Wikipedia
Verwandte Artikel
-
Filterextraktion - Wie Filter extrahiert werden
-
Verwandte-Suchen-Generierung - Wie verwandte Suchen generiert werden
-
Embedding-Strategie - Wie Embeddings generiert werden
-
SEO-Pipeline-Überblick - Komplette Pipeline-Architektur
-
Valkey-Migration - Laden von Daten in Valkey
Zusammenfassung
Unser Suchdienst läuft als eigenständige Flask-Anwendung auf einem separaten Server:
Architektur:
-
Eigenständige Flask-App auf dediziertem Server
-
Valkey (Redis-Fork) für Caching und Vektorsuche
-
HTTP-API für Kommunikation mit Haupt-Webserver
APIs:
-
/api/extract_filters- Filter aus Abfragen extrahieren -
/api/related- Ähnliche Abfragen finden (Vektorsuche) -
/api/autocomplete- Abfragen vorschlagen (Präfix-Matching) -
/api/popular- Beliebte Abfragen abrufen
Valkey-Features:
-
Vektorsuche (RediSearch-Modul)
-
Caching (30 Tage TTL für Phrasenzuordnungen)
-
Autovervollständigung (sortierte Mengen)
-
Persistenz (AOF)
Vorteile:
-
Unabhängige Skalierung
-
Ressourcenisolation
-
Hohe Leistung (Valkey-Caching)
-
Fehlertoleranz (graceful degradation)
Diese Architektur ermöglicht schnelle, skalierbare Suche bei gleichzeitig reaktionsschnellem Haupt-Webserver.