Technischer Leitfaden

Vektorsuche-Architektur: Produktionsreife Similarity-Search-Systeme bauen

Technischer Leitfaden zu Vektorsuche-Systemen. Lerne Vektordatenbanken, Indexierungsalgorithmen (HNSW, IVF), Metriken und Skalierungsstrategien.

10. Februar 202618 Min. LesezeitOronts Engineering Team

Warum Vektorsuche jetzt wichtig ist

Hier ist das Problem mit traditioneller Suche: Sie findet nur exakte Treffer. Du suchst nach "Laufschuhe" und verpasst alle Ergebnisse über "Jogging-Sneaker", weil die Wörter nicht übereinstimmen. Vektorsuche löst diese fundamentale Einschränkung, indem sie Bedeutung versteht, nicht nur Keywords matcht.

Wir haben Vektorsuche-Systeme für E-Commerce-Produktentdeckung, Dokumentensuche in RAG-Pipelines und Empfehlungssysteme deployed, die Millionen von Anfragen pro Tag verarbeiten. Die Technologie ist in den letzten zwei Jahren erheblich gereift, und das Tooling ist endlich produktionsreif.

Bei Vektorsuche geht es nicht nur darum, ähnliche Artikel zu finden. Es geht darum, Systeme zu bauen, die verstehen, was Benutzer wirklich meinen, nicht nur was sie tippen.

Lass mich dir zeigen, wie man diese Systeme richtig baut, von der Wahl der richtigen Datenbank bis zur Skalierung für echten Traffic.

Wie Vektorsuche tatsächlich funktioniert

Bevor wir in Datenbanken und Algorithmen eintauchen, lass uns verstehen, was wir tatsächlich tun. Vektorsuche funktioniert in drei Schritten:

Schritt 1: Embeddings erstellen

Du nimmst deine Daten (Text, Bilder, Produkte, was auch immer) und konvertierst sie mit einem Embedding-Modell in Vektoren. Diese Vektoren sind Arrays von Zahlen, die die semantische Bedeutung deiner Inhalte erfassen.

// OpenAI Embeddings verwenden
const response = await openai.embeddings.create({
  model: "text-embedding-3-small",
  input: "bequeme Laufschuhe für Marathons"
});

const vector = response.data[0].embedding;
// Gibt zurück: [0.0023, -0.0142, 0.0089, ...] (1536 Dimensionen)

Schritt 2: Vektoren speichern und indexieren

Du speicherst diese Vektoren in einer spezialisierten Datenbank, die einen Index für schnelles Retrieval aufbaut. Hier passiert die Magie mit Algorithmen wie HNSW und IVF.

Schritt 3: Nach Ähnlichkeit abfragen

Wenn ein Benutzer sucht, konvertierst du seine Anfrage in einen Vektor und findest die nächsten Nachbarn in deinem Index. Die Datenbank gibt die ähnlichsten Elemente nach Distanz sortiert zurück.

KomponenteZweckBeispiel
Embedding-ModellKonvertiert Daten zu VektorenOpenAI ada-002, Cohere embed-v3
VektordatenbankSpeichert und indexiert VektorenPinecone, Weaviate, Qdrant
DistanzmetrikMisst ÄhnlichkeitCosine, Euclidean, Dot Product
ANN-AlgorithmusSchnelle approximative SucheHNSW, IVF, PQ

Die richtige Vektordatenbank wählen

Vergleichen wir die wichtigsten Optionen. Ich bin ehrlich über Trade-offs, weil jede Datenbank in verschiedenen Szenarien glänzt.

Pinecone: Verwaltete Einfachheit

Pinecone ist am einfachsten zum Starten. Vollständig verwaltet, skaliert automatisch und funktioniert einfach. Der Nachteil? Du bist an ihre Infrastruktur gebunden und die Preise können bei Skalierung steil werden.

import { Pinecone } from '@pinecone-database/pinecone';

const pinecone = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });
const index = pinecone.index('products');

// Vektoren upserten
await index.upsert([
  {
    id: 'product-123',
    values: embedding,
    metadata: { category: 'schuhe', price: 129.99 }
  }
]);

// Abfrage mit Metadaten-Filterung
const results = await index.query({
  vector: queryEmbedding,
  topK: 10,
  filter: { category: { $eq: 'schuhe' } },
  includeMetadata: true
});

Am besten für: Teams, die schnell shippen wollen ohne Infrastruktur zu verwalten. Startups mit Funding. Use Cases unter 10M Vektoren, wo Kosten nicht die primäre Sorge sind.

Weaviate: Schema-First mit GraphQL

Weaviate geht mit seinem Schema-basierten Design und GraphQL-API einen anderen Weg. Du definierst Objektklassen mit Properties, und Weaviate übernimmt die Vektorisierung automatisch, wenn du willst.

import weaviate from 'weaviate-ts-client';

const client = weaviate.client({
  scheme: 'https',
  host: 'your-cluster.weaviate.network'
});

// Schema definieren
await client.schema.classCreator().withClass({
  class: 'Product',
  vectorizer: 'text2vec-openai',
  properties: [
    { name: 'name', dataType: ['text'] },
    { name: 'description', dataType: ['text'] },
    { name: 'price', dataType: ['number'] }
  ]
}).do();

// Abfrage mit GraphQL
const result = await client.graphql
  .get()
  .withClassName('Product')
  .withNearText({ concepts: ['Marathon Laufschuhe'] })
  .withLimit(10)
  .withFields('name description price _additional { distance }')
  .do();

Am besten für: Teams, die Knowledge Graphs bauen, Anwendungen die hybride Suche brauchen (Keyword + Vektor), Projekte wo Schema-Enforcement wichtig ist.

Qdrant: Performance-fokussiert und Open Source

Qdrant ist in Rust geschrieben und für Performance optimiert. Es ist Open Source, kann selbst gehostet werden und hat ein exzellentes Filtersystem. Wir haben gesehen, wie es 50M+ Vektoren mit Sub-100ms Latenzen auf bescheidener Hardware handhabt.

import { QdrantClient } from '@qdrant/js-client-rest';

const client = new QdrantClient({ url: 'http://localhost:6333' });

// Collection mit optimierten Einstellungen erstellen
await client.createCollection('products', {
  vectors: {
    size: 1536,
    distance: 'Cosine'
  },
  optimizers_config: {
    indexing_threshold: 20000
  },
  hnsw_config: {
    m: 16,
    ef_construct: 100
  }
});

// Upsert mit Payload
await client.upsert('products', {
  points: [
    {
      id: 'product-123',
      vector: embedding,
      payload: { category: 'schuhe', price: 129.99, in_stock: true }
    }
  ]
});

// Abfrage mit komplexen Filtern
const results = await client.search('products', {
  vector: queryEmbedding,
  limit: 10,
  filter: {
    must: [
      { key: 'category', match: { value: 'schuhe' } },
      { key: 'price', range: { lte: 150 } },
      { key: 'in_stock', match: { value: true } }
    ]
  }
});

Am besten für: Self-hosted Deployments, kostensensitive Projekte bei Skalierung, Anwendungen die komplexe Filterung brauchen, Teams die Kontrolle über Infrastruktur wollen.

pgvector: Vektorsuche in PostgreSQL

Wenn du bereits PostgreSQL betreibst, lässt pgvector dich Vektorsuche ohne eine weitere Datenbank hinzufügen. Es ist nicht die schnellste Option, aber oft schnell genug und vereinfacht deine Architektur drastisch.

-- Extension aktivieren
CREATE EXTENSION vector;

-- Tabelle mit Vektor-Spalte erstellen
CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  name TEXT,
  description TEXT,
  category TEXT,
  price NUMERIC,
  embedding VECTOR(1536)
);

-- HNSW-Index für schnellere Abfragen erstellen
CREATE INDEX ON products
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

-- Nächste Nachbarn abfragen
SELECT id, name, price,
       1 - (embedding <=> query_embedding) AS similarity
FROM products
WHERE category = 'schuhe' AND price < 150
ORDER BY embedding <=> query_embedding
LIMIT 10;

Am besten für: Teams die bereits auf PostgreSQL sind, Anwendungen unter 5M Vektoren, Use Cases wo du ACID-Transaktionen mit Vektorsuche brauchst, Reduzierung der operativen Komplexität.

Vektordatenbank-Vergleich

FeaturePineconeWeaviateQdrantpgvector
DeploymentNur verwaltetVerwaltet + Self-hostedVerwaltet + Self-hostedSelf-hosted
Max SkalierungMilliardenHunderte MillionenHunderte Millionen~10 Millionen
Query-Latenz<50ms<100ms<50ms<200ms
FilterungGutExzellentExzellentNative SQL
LernkurveEinfachModeratEinfachMinimal wenn du SQL kannst
Kosten bei SkalierungHochModeratNiedrig (self-hosted)Niedrig
Hybride SucheBegrenztExzellentGutÜber Volltextsuche

Indexierungsalgorithmen verstehen

Der Index-Algorithmus bestimmt, wie schnell du Millionen von Vektoren durchsuchen kannst. Hier ist, was du über die zwei häufigsten Ansätze wissen musst.

HNSW: Hierarchical Navigable Small World

HNSW ist der dominante Algorithmus für Vektorsuche. Er baut einen mehrschichtigen Graphen, bei dem höhere Schichten weniger Knoten und größere Sprünge haben, während niedrigere Schichten mehr Knoten und kürzere Verbindungen haben. Die Suche startet oben und arbeitet sich nach unten.

ParameterWas er kontrolliertTrade-off
mAnzahl der Verbindungen pro KnotenHöher = besserer Recall, mehr Speicher
ef_constructSuchbreite während Index-AufbauHöher = bessere Qualität, langsameres Indexing
ef_searchSuchbreite während AbfragenHöher = besserer Recall, langsamere Abfragen
// Qdrant HNSW-Konfiguration
const hnswConfig = {
  m: 16,              // 16 Verbindungen pro Knoten (Standard)
  ef_construct: 100,  // Konstruktionsqualität
  full_scan_threshold: 10000  // Wechsle zu Brute Force unter diesem Wert
};

// Für hohe Recall-Anforderungen
const highRecallConfig = {
  m: 32,
  ef_construct: 200
};

// Für speicherbeschränkte Umgebungen
const lowMemoryConfig = {
  m: 8,
  ef_construct: 50
};

Pros: Schnelle Abfragen, guter Recall, funktioniert gut für die meisten Use Cases Cons: Speicherintensiv, langsame Index-Updates, nicht ideal für Streaming-Daten

IVF: Inverted File Index

IVF clustert deine Vektoren und durchsucht nur relevante Cluster. Es ist schneller zu bauen als HNSW und verwendet weniger Speicher, hat aber typischerweise niedrigeren Recall.

ParameterWas er kontrolliertTrade-off
nlistAnzahl der ClusterHöher = mehr Präzision, langsamere Abfragen
nprobeZu durchsuchende ClusterHöher = besserer Recall, langsamere Abfragen
# Faiss IVF Beispiel
import faiss

dimension = 1536
nlist = 100  # Anzahl der Cluster

# IVF-Index mit flachem Quantizer erstellen
quantizer = faiss.IndexFlatL2(dimension)
index = faiss.IndexIVFFlat(quantizer, dimension, nlist)

# Mit Beispieldaten trainieren
index.train(training_vectors)
index.add(all_vectors)

# Suche mit nprobe
index.nprobe = 10  # 10 Cluster durchsuchen
distances, indices = index.search(query_vector, k=10)

Pros: Speichereffizient, schneller Index-Aufbau, gut für Streaming-Updates Cons: Niedrigerer Recall als HNSW, erfordert Trainingsschritt, mehr Tuning nötig

Product Quantization: Vektoren komprimieren

Wenn Speicher knapp ist, komprimiert Product Quantization (PQ) Vektoren durch Aufteilen in Subvektoren und Verwendung von Codebüchern. Du tauschst Recall gegen dramatische Speichereinsparungen.

# IVF + PQ für massive Skalierung
m = 8  # Anzahl der Subquantisierer
bits = 8  # Bits pro Subvektor

index = faiss.IndexIVFPQ(quantizer, dimension, nlist, m, bits)
# Reduziert Speicher ~32x verglichen mit flachem Index

Verwende PQ wenn: Du 100M+ Vektoren hast und dir Full-Precision-Speicherung nicht leisten kannst. Erwarte 5-10% Recall-Verlust.

Ähnlichkeitsmetriken: Die richtige Distanz wählen

Die Distanzmetrik bestimmt, wie Ähnlichkeit berechnet wird. Die Wahl ist wichtiger als du denkst.

MetrikFormelAm besten für
Cosine1 - (A.B / ||A|| ||B||)Text-Embeddings, normalisierte Vektoren
Euclidean (L2)sqrt(sum((A-B)^2))Bild-Embeddings, dichte Features
Dot ProductA.BWenn Magnitude wichtig ist, Empfehlungen

Faustregel: Wenn deine Embeddings von einem Text-Modell kommen (OpenAI, Cohere, etc.), verwende Cosine Similarity. Die Vektoren sind bereits normalisiert, und Cosine handhabt sie korrekt. Für Bild-Embeddings oder Custom-Modelle funktioniert Euclidean oft besser.

// Die meisten Text-Embedding-APIs geben normalisierte Vektoren zurück
// Cosine Similarity = Dot Product für normalisierte Vektoren
const cosineSimilarity = (a, b) => {
  return a.reduce((sum, val, i) => sum + val * b[i], 0);
};

// Euklidische Distanz für nicht-normalisierte Vektoren
const euclideanDistance = (a, b) => {
  return Math.sqrt(
    a.reduce((sum, val, i) => sum + Math.pow(val - b[i], 2), 0)
  );
};

Vektorsuche für Produktion skalieren

Hier trifft Theorie auf Realität. Lass mich teilen, was wir beim Skalieren von Vektorsuche für Millionen von Anfragen gelernt haben.

Sharding-Strategien

Wenn ein Knoten nicht alle deine Vektoren halten kann, musst du sharden. Es gibt zwei Hauptansätze:

Geografisches Sharding: Aufteilen nach Region wenn Anfragen lokalisiert sind

shard_de: Deutsche Produkte (5M Vektoren)
shard_eu: EU Produkte (3M Vektoren)
shard_global: Globale Produkte (2M Vektoren)

Hash-basiertes Sharding: Gleichmäßig über Knoten verteilen

const shardId = hash(productId) % numShards;

Metadaten-basiertes Sharding: Nach Kategorie oder Attribut aufteilen

shard_elektronik: Elektronikprodukte
shard_kleidung: Bekleidungsprodukte
shard_heim: Haus & Garten Produkte

Replikation für Verfügbarkeit

Betreibe mindestens 3 Replikas für Produktions-Workloads. Vektorsuche ist leseintensiv, also helfen Replikas auch beim Durchsatz.

# Qdrant Cluster-Konfiguration
cluster:
  enabled: true
  replication_factor: 3
  shard_number: 6

# Pinecone (automatisch)
# Wähle einfach "production" Tier mit mehreren Pods

# Weaviate
replicationConfig:
  factor: 3

Hot Queries cachen

Manche Anfragen sind viel häufiger als andere. Cache sie.

import { Redis } from 'ioredis';

const redis = new Redis();
const CACHE_TTL = 3600; // 1 Stunde

async function searchWithCache(query, filters) {
  const cacheKey = `vsearch:${hash(query)}:${hash(filters)}`;

  // Zuerst Cache prüfen
  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  // Vektordatenbank abfragen
  const embedding = await generateEmbedding(query);
  const results = await vectorDB.search({
    vector: embedding,
    filter: filters,
    limit: 20
  });

  // Ergebnisse cachen
  await redis.setex(cacheKey, CACHE_TTL, JSON.stringify(results));

  return results;
}

Batching und Async-Verarbeitung

Verarbeite Vektoren nicht einzeln. Batche alles.

// Schlecht: Sequentielle Verarbeitung
for (const doc of documents) {
  const embedding = await generateEmbedding(doc.text);
  await vectorDB.upsert({ id: doc.id, vector: embedding });
}

// Gut: Batch-Verarbeitung
const BATCH_SIZE = 100;
for (let i = 0; i < documents.length; i += BATCH_SIZE) {
  const batch = documents.slice(i, i + BATCH_SIZE);

  // Embeddings parallel generieren
  const embeddings = await Promise.all(
    batch.map(doc => generateEmbedding(doc.text))
  );

  // Im Batch upserten
  await vectorDB.upsert(
    batch.map((doc, j) => ({
      id: doc.id,
      vector: embeddings[j],
      metadata: doc.metadata
    }))
  );
}

Produktions-Architektur Beispiel

Hier ist eine echte Architektur, die wir für ein E-Commerce-Suchsystem mit 10M+ Produkten und 1000+ Anfragen pro Sekunde verwenden:

┌─────────────────────────────────────────────────────────────┐
│                         Load Balancer                        │
└─────────────────────┬───────────────────────────────────────┘
                      │
┌─────────────────────▼───────────────────────────────────────┐
│                      API Gateway                             │
│              (Rate Limiting, Auth, Routing)                  │
└─────────────────────┬───────────────────────────────────────┘
                      │
         ┌────────────┼────────────┐
         │            │            │
   ┌─────▼─────┐ ┌────▼────┐ ┌────▼────┐
   │   Cache   │ │ Search  │ │ Search  │
   │  (Redis)  │ │ Node 1  │ │ Node 2  │
   └───────────┘ └────┬────┘ └────┬────┘
                      │           │
              ┌───────▼───────────▼───────┐
              │     Qdrant Cluster        │
              │   (3 Shards, 3 Replikas)  │
              └───────────────────────────┘
                          │
              ┌───────────▼───────────────┐
              │   Embedding Service       │
              │   (GPU-beschleunigt)      │
              └───────────────────────────┘

Schlüsselkomponenten

Search Nodes: Stateless Services, die Query-Verarbeitung, Embedding-Generierung und Ergebnis-Ranking übernehmen. Wir betreiben 2-4 Instanzen hinter einem Load Balancer.

Qdrant Cluster: 3 Shards für Datenverteilung, 3 Replikas für Verfügbarkeit. Jeder Shard handhabt ~3.5M Vektoren. Gesamtspeicher: ~50GB über den Cluster.

Embedding Service: Dedizierter GPU-Service für Embedding-Generierung. Wir verwenden ONNX-optimierte Modelle für 10x schnellere Inferenz als Vanilla Transformers.

Redis Cache: Cached häufige Anfragen und Hot Embeddings. Reduziert Qdrant-Last um ~60%.

Häufige Fallstricke und wie man sie vermeidet

Fallstrick 1: Vektoren nicht normalisieren

Wenn du Cosine Similarity verwendest aber deine Vektoren nicht normalisiert sind, bekommst du falsche Ergebnisse. Die meisten Embedding-APIs geben normalisierte Vektoren zurück, aber wenn du ein Custom-Modell verwendest, normalisiere sie selbst.

function normalize(vector) {
  const magnitude = Math.sqrt(
    vector.reduce((sum, val) => sum + val * val, 0)
  );
  return vector.map(val => val / magnitude);
}

Fallstrick 2: Das Embedding-Modell ignorieren

Das Embedding-Modell ist wichtiger als die Datenbank. Ein gutes Modell mit pgvector schlägt ein schlechtes Modell mit Pinecone. Investiere Zeit in die Evaluierung der Embedding-Qualität bevor du Infrastruktur optimierst.

Fallstrick 3: Updates nicht einplanen

Die meisten Vektordatenbanken sind für Reads optimiert, nicht für Writes. Wenn du häufige Updates brauchst, plane dafür:

  • Verwende Write-Ahead-Logs
  • Batche Updates während verkehrsarmer Zeiten
  • Erwäge einen Staging-Index für neue Daten

Fallstrick 4: Zu viel Filtern vor der Vektorsuche

Filtern nach der Vektorsuche ist normalerweise effizienter als Filtern davor. Lass den Vektor-Index seine Arbeit machen, dann filtere die Ergebnisse.

// Weniger effizient: Zuerst filtern, dann verbleibende Vektoren durchsuchen
// Effizienter: Alles durchsuchen, dann Top-Ergebnisse filtern

const results = await vectorDB.search({
  vector: queryEmbedding,
  limit: 100  // Hole mehr Ergebnisse als benötigt
});

const filtered = results.filter(r =>
  r.metadata.price < maxPrice &&
  r.metadata.in_stock
).slice(0, 10);

Monitoring und Observability

Du kannst nicht verbessern, was du nicht misst. Tracke diese Metriken:

MetrikZielAktion wenn überschritten
p50 Latenz<50msIndex-Konfiguration prüfen
p99 Latenz<200msReplikas oder Shards hinzufügen
Recall@10>95%ef_search oder m erhöhen
QPS pro Knoten<1000Mehr Knoten hinzufügen
Speichernutzung<80%Sharden oder PQ-Kompression verwenden
// Such-Latenz tracken
const startTime = Date.now();
const results = await vectorDB.search(query);
const latency = Date.now() - startTime;

metrics.histogram('vector_search_latency_ms', latency, {
  collection: 'products',
  filter_count: Object.keys(query.filter || {}).length
});

// Recall tracken (erfordert Ground Truth)
const recall = calculateRecall(results, groundTruth);
metrics.gauge('vector_search_recall', recall);

Loslegen: Praktische Empfehlungen

Wenn du frisch startest, hier ist was ich empfehlen würde:

  1. Unter 1M Vektoren: Starte mit pgvector. Es ist einfach, wahrscheinlich schnell genug, und du kannst bereits SQL.

  2. 1M-10M Vektoren: Qdrant self-hosted gibt dir die beste Performance pro Euro. Pinecone wenn du keine Infrastruktur verwalten willst.

  3. 10M-100M Vektoren: Qdrant oder Weaviate mit richtigem Sharding. Erwäge Pinecone's Enterprise-Tier wenn das Budget es erlaubt.

  4. 100M+ Vektoren: Du brauchst spezialisierte Architektur. Erwäge Milvus, Multi-Cluster Qdrant oder Custom-Lösungen mit Faiss.

Starte mit HNSW-Indizes (der Standard in den meisten Datenbanken). Optimiere erst, wenn du tatsächliche Performance-Probleme hast. Vorzeitige Optimierung verschwendet Zeit mit Problemen, die du vielleicht gar nicht hast.

Fazit

Vektorsuche hat sich von Forschungskuriosität zur Produktionsnotwendigkeit entwickelt. Ob du semantische Suche, Empfehlungssysteme oder RAG-Pipelines baust, das Verständnis dieser Grundlagen wird dir helfen, bessere architektonische Entscheidungen zu treffen.

Die Technologie ist ausgereift, das Tooling ist gut, und die Community hat die meisten gängigen Probleme gelöst. Was bleibt, ist die richtigen Tools für deinen spezifischen Use Case zu wählen und sie durchdacht zu implementieren.

Wenn du Vektorsuche-Systeme baust und über Architektur diskutieren möchtest, teilen wir gerne, was wir aus Produktions-Deployments gelernt haben.

Behandelte Themen

VektorsucheVektordatenbankPineconeWeaviateQdrantpgvectorHNSWIVFSimilarity SearchEmbeddingssemantische SucheANNApproximate Nearest Neighbor

Bereit, produktionsreife KI-Systeme zu bauen?

Unser Team ist spezialisiert auf produktionsreife KI-Systeme. Lass uns besprechen, wie wir deinem Unternehmen helfen können.

Gespräch starten