Veröffentlicht am

Qdrant auf Kubernetes: Vector Search produktionsreif

Teilen:
Authors

Qdrant auf Kubernetes: Vector Search und RAG-Pipelines produktionsreif betreiben

TL;DR

  • Qdrant laeuft als StatefulSet auf Kubernetes mit persistenten Volumes -- das offizielle Helm Chart macht den Einstieg einfach
  • Eine produktionsreife RAG-Pipeline besteht aus 4 Komponenten: Ingestion-Worker, Embedding-Service, Qdrant-Cluster, und LLM-Gateway
  • HNSW-Index-Parameter (m und ef_construct) bestimmen den Trade-off zwischen Suchgeschwindigkeit und Recall -- Default-Werte sind selten optimal
  • Fuer DSGVO-relevante Daten: Embedding-Modell lokal im Cluster ausfuehren statt externe APIs nutzen
  • Sharding + Replikation ab ca. 5 Mio. Vektoren sinnvoll; darunter reicht eine Single-Node-Instanz

Warum Qdrant auf Kubernetes?

Vektordatenbanken sind das Rueckgrat jeder RAG-Architektur. Ohne sie liefert ein LLM nur Antworten aus seinem Training -- ohne Zugriff auf eure Unternehmensdaten. Qdrant hat sich als performante Open-Source-Option etabliert, die sich gut in bestehende Kubernetes-Infrastruktur integriert.

Der Betrieb auf Kubernetes bringt drei konkrete Vorteile gegenueber einer Standalone-Installation:

  1. Lifecycle-Management: Rolling Updates, automatische Restarts bei Crashes, Health Checks
  2. Skalierung: Horizontal ueber Sharding, ohne manuelle Datenumverteilung
  3. Infrastruktur-als-Code: Das gesamte Setup ist in Helm Values definiert und reproduzierbar

Qdrant per Helm deployen

Das offizielle Qdrant Helm Chart deckt die meisten Szenarien ab. Hier eine values.yaml fuer ein Produktions-Setup:

# qdrant-values.yaml
replicaCount: 3

persistence:
  enabled: true
  size: 50Gi
  storageClass: "ssd-retain"

resources:
  requests:
    cpu: "1"
    memory: "4Gi"
  limits:
    cpu: "2"
    memory: "8Gi"

config:
  storage:
    optimizers:
      default_segment_number: 4
      memmap_threshold_kb: 50000
  service:
    grpc_port: 6334
    enable_tls: false
  cluster:
    enabled: true
    p2p:
      port: 6335

service:
  type: ClusterIP
  port: 6333
  grpcPort: 6334

tolerations: []
nodeSelector: {}

podDisruptionBudget:
  enabled: true
  minAvailable: 2

Installation:

helm repo add qdrant https://qdrant.github.io/qdrant-helm
helm repo update

helm install qdrant qdrant/qdrant \
  --namespace vector-search \
  --create-namespace \
  --values qdrant-values.yaml

# Pruefen ob alle Pods laufen
kubectl get pods -n vector-search -l app.kubernetes.io/name=qdrant

# Qdrant REST API testen
kubectl port-forward -n vector-search svc/qdrant 6333:6333 &
curl http://localhost:6333/healthz

Drei Punkte die oft uebersehen werden:

  • StorageClass mit Retain-Policy: Wenn ein Pod stirbt, duerfen die Daten nicht verschwinden. reclaimPolicy: Retain ist Pflicht.
  • PodDisruptionBudget: Bei 3 Replicas minAvailable: 2 setzen. Sonst kann ein Node-Drain alle Qdrant-Pods gleichzeitig killen.
  • Memory: Qdrant nutzt mmap fuer grosse Segmente. Die Limits muessen das beruecksichtigen -- 2x die erwartete Index-Groesse ist ein guter Startwert.

Fuer die Wahl der richtigen StorageClass siehe auch unseren Kubernetes Storage Guide.

Architektur einer RAG-Pipeline

Eine vollstaendige RAG-Pipeline auf Kubernetes besteht aus vier Hauptkomponenten:

KomponenteKubernetes-RessourceAufgabe
Ingestion WorkerJob / CronJobDokumente laden, chunken, an Embedding-Service senden
Embedding ServiceDeployment + HPAText in Vektoren umwandeln (z.B. Sentence-Transformers)
Qdrant ClusterStatefulSetVektoren speichern und durchsuchen
RAG APIDeployment + HPAAnfrage embedden, Qdrant abfragen, Kontext an LLM senden

Der Datenfluss bei einer Nutzeranfrage:

  1. Nutzer sendet Frage an die RAG API
  2. RAG API sendet den Text an den Embedding Service
  3. Embedding Service gibt einen Vektor zurueck
  4. RAG API sucht in Qdrant die Top-K aehnlichsten Chunks
  5. RAG API baut einen Prompt: System-Instruction + Context-Chunks + User-Frage
  6. LLM generiert eine Antwort auf Basis des Kontexts

Embedding Service: Lokal vs. API

Das ist eine der wichtigsten Architekturentscheidungen. Hier der Vergleich:

KriteriumLokaler Embedding ServiceExterne API (z.B. OpenAI)
Latenz10-50ms (im Cluster)100-500ms (Netzwerk)
DatenschutzDaten verlassen Cluster nichtDaten gehen an Drittanbieter
KostenGPU/CPU im ClusterPer-Token-Abrechnung
ModellwahlVolle KontrolleAn Anbieter gebunden
BetriebsaufwandHoeher (Modell-Updates, GPU-Mgmt)Niedriger

Fuer DSGVO-relevante Daten ist ein lokaler Embedding Service die sauberere Loesung. Ein Deployment mit sentence-transformers/all-MiniLM-L12-v2 laeuft problemlos auf CPUs:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: embedding-service
  namespace: vector-search
spec:
  replicas: 2
  selector:
    matchLabels:
      app: embedding-service
  template:
    metadata:
      labels:
        app: embedding-service
    spec:
      containers:
      - name: embedder
        image: registry.example.com/embedding-service:1.2.0
        ports:
        - containerPort: 8000
        env:
        - name: MODEL_NAME
          value: "sentence-transformers/all-MiniLM-L12-v2"
        - name: MAX_BATCH_SIZE
          value: "64"
        resources:
          requests:
            cpu: "2"
            memory: "2Gi"
          limits:
            cpu: "4"
            memory: "4Gi"
---
apiVersion: v1
kind: Service
metadata:
  name: embedding-service
  namespace: vector-search
spec:
  selector:
    app: embedding-service
  ports:
  - port: 8000
    targetPort: 8000
  type: ClusterIP

Wenn ihr groessere Modelle (z.B. intfloat/multilingual-e5-large) braucht, wird eine GPU sinnvoll. Details dazu in unserem GPU-Cluster Guide.

HNSW-Index Tuning

Qdrant nutzt den HNSW-Algorithmus (Hierarchical Navigable Small World) fuer Nearest-Neighbor-Suche. Die zwei wichtigsten Parameter:

  • m (Connections per layer): Mehr Verbindungen = hoeherer Recall, aber mehr Speicher und langsamerer Index-Aufbau. Default: 16.
  • ef_construct (Construction beam width): Hoeherer Wert = bessere Indexqualitaet, aber laengere Build-Zeit. Default: 100.

Fuer die Suche gibt es zusaetzlich ef (Search beam width): Hoeherer Wert = hoeherer Recall, aber langsamere Suche.

Praxis-Empfehlung nach Datenmenge:

Vektorenmef_constructef (search)Erwarteter Recall
unter 100k1612864~95%
100k - 1M16200128~97%
1M - 10M32256256~98%
ueber 10M48512512~99%

Collection mit angepassten Parametern erstellen:

curl -X PUT http://localhost:6333/collections/documents \
  -H 'Content-Type: application/json' \
  -d '{
    "vectors": {
      "size": 384,
      "distance": "Cosine",
      "hnsw_config": {
        "m": 32,
        "ef_construct": 256
      }
    },
    "optimizers_config": {
      "indexing_threshold": 20000
    },
    "replication_factor": 2,
    "shard_number": 3
  }'

Der indexing_threshold steuert, ab wie vielen Vektoren der HNSW-Index gebaut wird. Fuer Bulk-Imports setzt man ihn hoch (oder auf 0 fuer sofortiges Indexing) und baut den Index danach.

Monitoring: Was ihr beobachten muesst

Qdrant exponiert Prometheus-Metriken auf Port 6333 unter /metrics. Die wichtigsten:

  • qdrant_search_latency_seconds: Wenn das ueber 100ms geht, stimmt etwas mit dem Index oder den Ressourcen nicht
  • qdrant_grpc_responses_total: Fehlerrate beobachten, insbesondere bei Cluster-Operationen
  • qdrant_collections_total / qdrant_points_total: Wachstum der Datenmenge tracken
  • Container Memory Usage vs. Limits: Qdrant nutzt mmap aggressiv -- wenn der OOM-Killer zuschlaegt, sind die Limits zu niedrig

Ein Grafana Dashboard fuer Qdrant aufzusetzen lohnt sich ab Tag 1. Fuer den gesamten Monitoring-Stack empfehle ich unseren Observability-Stack Artikel.

Ingestion-Pipeline als CronJob

Dokumente muessen regelmaessig in Qdrant geladen werden. Ein Kubernetes CronJob eignet sich gut dafuer:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: document-ingestion
  namespace: vector-search
spec:
  schedule: "0 2 * * *"  # Taeglich um 02:00
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: ingestor
            image: registry.example.com/doc-ingestor:1.0.3
            env:
            - name: QDRANT_URL
              value: "http://qdrant.vector-search.svc.cluster.local:6333"
            - name: EMBEDDING_URL
              value: "http://embedding-service.vector-search.svc.cluster.local:8000"
            - name: SOURCE_BUCKET
              value: "s3://company-docs/knowledge-base"
            - name: COLLECTION_NAME
              value: "documents"
            - name: CHUNK_SIZE
              value: "512"
            - name: CHUNK_OVERLAP
              value: "64"
            resources:
              requests:
                cpu: "500m"
                memory: "1Gi"
              limits:
                cpu: "1"
                memory: "2Gi"
          restartPolicy: OnFailure
      backoffLimit: 3

Wichtig: concurrencyPolicy: Forbid stellt sicher, dass nicht zwei Ingestion-Jobs parallel laufen und sich gegenseitig in die Quere kommen.

Skalierung: Wann Sharding und Replikation?

Qdrant auf Kubernetes skaliert ueber Sharding (Daten verteilen) und Replikation (Daten kopieren). Die Frage ist: Ab wann braucht ihr das?

Single-Node reicht wenn:

  • Weniger als 5 Mio. Vektoren
  • Suchlatenz unter 50ms akzeptabel
  • Kein Hochverfuegbarkeits-Requirement

Sharding sinnvoll ab:

  • Ueber 5 Mio. Vektoren
  • Index passt nicht mehr in den RAM eines Nodes
  • Parallel-Suche ueber mehrere Shards beschleunigt Queries

Replikation sinnvoll fuer:

  • Produktionssysteme mit Uptime-Anforderung
  • Read-heavy Workloads (Replicas koennen Leseanfragen bedienen)
  • Ausfallsicherheit bei Node-Failures

Im Helm Chart von oben ist der Cluster-Modus bereits aktiviert. Shards und Replicas werden pro Collection konfiguriert, nicht global.

Haeufige Fehler

Embedding-Modell-Mismatch: Ingestion und Query muessen exakt das gleiche Embedding-Modell und die gleiche Preprocessing-Pipeline nutzen. Unterschiedliches Tokenizing oder Normalisierung fuehrt zu schlechten Suchergebnissen.

Chunk-Groesse falsch gewaehlt: Zu kleine Chunks (unter 100 Tokens) verlieren Kontext. Zu grosse Chunks (ueber 1000 Tokens) verwessern den Vektor und liefern ungenaue Matches. 256-512 Tokens mit 10-15% Overlap ist ein guter Startpunkt.

Kein Payload-Filtering: Qdrant kann neben der Vektorsuche auch nach Metadaten filtern. Wer das nicht nutzt, bekommt Ergebnisse aus falschen Dokumentkategorien. Immer relevante Metadaten (Dokumenttyp, Datum, Abteilung) als Payload mitspeichern.

Keine Backup-Strategie: Qdrant bietet Snapshots per API. Plant regelmaessige Snapshots ein und speichert sie ausserhalb des Clusters. Ein PV-Verlust ohne Backup bedeutet kompletter Re-Ingest aller Dokumente.

Performance-Vergleich: Qdrant vs. Alternativen

FeatureQdrantWeaviateMilvuspgvector
Kubernetes-nativJa (Helm)Ja (Helm)Ja (Helm)Nein (Postgres-Operator)
Filterbare PayloadJaJa (Properties)Ja (Scalar Fields)Ja (SQL WHERE)
QuantisierungScalar + ProductBQ + PQScalar + PQNein
Cluster-ModusJaJaJaNein (PG-Replikation)
Speicher-EffizienzSehr gut (mmap)GutGutMaessig
Latenz (1M Vektoren)~5-15ms~10-25ms~8-20ms~50-200ms

pgvector ist eine gute Wahl wenn ihr bereits PostgreSQL nutzt und unter 1M Vektoren bleibt. Fuer groessere Datasets und niedrige Latenzanforderungen ist eine dedizierte Vektordatenbank wie Qdrant die bessere Wahl.

Zusammenfassung

Qdrant auf Kubernetes zu betreiben ist kein Hexenwerk, erfordert aber Aufmerksamkeit bei Storage, Ressourcen und Index-Konfiguration. Die Kombination aus StatefulSet, persistenten Volumes und dem offiziellen Helm Chart gibt euch eine solide Basis.

Der groesste Hebel liegt nicht in der Infrastruktur, sondern in der Datenqualitaet: Saubere Chunks, das richtige Embedding-Modell und durchdachtes Payload-Schema machen den Unterschied zwischen "funktioniert irgendwie" und "liefert zuverlaessige Ergebnisse".

Fuer weitergehende Fragen zur Architektur oder Hilfe beim Aufbau einer RAG-Pipeline koennt ihr uns jederzeit kontaktieren.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen