This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Selbstgehostete Vektordatenbanken mit Qdrant: Ein tiefer Tauchgang
Sunday, 23 November 2025
Einleitung
Im Zusammenhang mit der RAG-Serie: Dieser Artikel bietet einen tiefen Einblick in Qdrant, die Vektordatenbank, die verwendet wird in:
- Teil 4: Umsetzung von ONNX und Qdrant - Gebäude semantische Suche
- Teil 5: Hybride Suche & Auto-Indexierung - Produktionsintegration
Qdrant (ausgesprochen "Quadrant") ist eine Open-Source-Vektor-Datenbank in Rust. Dieser Artikel umfasst Kernkonzepte, den C#-Client, Performance-Tuning und Produktionsmuster.
Was ist Qdrant?
A Vektordatenbank speichert hochdimensionale Vektoren (Embeddings) und ermöglicht eine schnelle Ähnlichkeitssuche. Im Gegensatz zu traditionellen Datenbanken, die exakte Übereinstimmungen finden, findet Qdrant semantisch ähnlich Artikel.
flowchart LR
A[Text: 'Docker deployment'] --> B[Embedding Model]
B --> C["Vector: [0.12, -0.34, 0.56, ...]"]
C --> D[Qdrant]
E[Query: 'container setup'] --> F[Embedding Model]
F --> G["Vector: [0.11, -0.32, 0.58, ...]"]
G --> H[Similarity Search]
D --> H
H --> I[Similar Results]
style B stroke:#6366f1,stroke-width:3px
style D stroke:#ef4444,stroke-width:3px
style F stroke:#6366f1,stroke-width:3px
style H stroke:#10b981,stroke-width:2px
Hauptmerkmale von Qdrant:
- HNSW-Indizierung - Sublineare Suchzeiten
- Filtern - Ähnlichkeitssuche mit Metadatenfiltern kombinieren
- gRPC & REST APIs - High-Performance-Zugang
- Verteilte Bereitstellung - Horizontal skalieren
- Schnappschüsse - Sicherung und Wiederherstellung
Kernkonzepte
Sammlungen
A Sammlung ist wie eine Tabelle - es hält Vektoren mit einer festen Dimensionalität und Distanzmetrik.
flowchart TB
subgraph Collection["Collection: blog_posts"]
A[Vector Size: 384]
B[Distance: Cosine]
C[HNSW Index]
end
subgraph Points
D[Point 1: slug=docker-intro]
E[Point 2: slug=kubernetes-basics]
F[Point N...]
end
Collection --> Points
style A stroke:#6366f1,stroke-width:2px
style B stroke:#6366f1,stroke-width:2px
style C stroke:#f59e0b,stroke-width:2px
style D stroke:#10b981,stroke-width:2px
style E stroke:#10b981,stroke-width:2px
// Create collection - see https://qdrant.tech/documentation/concepts/collections/#create-a-collection
await client.CreateCollectionAsync(
collectionName: "blog_posts",
vectorsConfig: new VectorParams
{
Size = 384, // Must match your embedding model
Distance = Distance.Cosine // Best for text embeddings
}
);
- Cosin - misst Winkel zwischen Vektoren (am besten für Text)
- Punkt - Rohes Innenprodukt (für vornormierte Vektoren)
- Euklid - Geometrische Entfernung (für Geodaten)
Punkte
A Ziffer Es handelt sich um einen einzigen Datensatz, der Folgendes enthält:
flowchart LR
subgraph Point
A[ID: uuid/int]
B["Vector: float[384]"]
C[Payload: JSON metadata]
end
style A stroke:#8b5cf6,stroke-width:2px
style B stroke:#f59e0b,stroke-width:2px
style C stroke:#10b981,stroke-width:2px
// Upsert points - see https://qdrant.tech/documentation/concepts/points/#upload-points
var point = new PointStruct
{
Id = new PointId { Uuid = Guid.NewGuid().ToString() },
Vectors = embedding, // float[384]
Payload =
{
["slug"] = "my-post",
["title"] = "Vector Databases",
["language"] = "en",
["categories"] = new[] { "AI", "Databases" },
["published"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
}
};
await client.UpsertAsync("blog_posts", points: new[] { point });
Filtern
Filtern läuft Vorher Ähnlichkeitssuche - extrem effizient.
flowchart TB
A[Search Query] --> B{Apply Filters First}
B --> C[Language = 'en']
B --> D[Year >= 2024]
C --> E[Filtered Subset]
D --> E
E --> F[Vector Similarity Search]
F --> G[Ranked Results]
style B stroke:#ec4899,stroke-width:3px
style E stroke:#f59e0b,stroke-width:2px
style F stroke:#6366f1,stroke-width:2px
style G stroke:#10b981,stroke-width:2px
// Filter conditions - see https://qdrant.tech/documentation/concepts/filtering/#filtering-conditions
var filter = new Filter
{
Must = // AND conditions
{
new Condition { Field = new FieldCondition
{
Key = "language",
Match = new Match { Keyword = "en" }
}},
new Condition { Field = new FieldCondition
{
Key = "published",
Range = new Range { Gte = 1704067200 } // 2024-01-01
}}
},
MustNot = // Exclude conditions
{
new Condition { Field = new FieldCondition
{
Key = "slug",
Match = new Match { Keyword = "draft-post" }
}}
}
};
Match.Keyword- Genauer String-MatchMatch.Text- Volltext-MatchMatch.Any- Passen Sie alle in ArrayRange- Zahlenbereiche (Gte, Lte, Gt, Lt)GeoBoundingBox/GeoRadius- Geofilterung
Der C#-Client
Installieren Sie den offiziellen Qdrant.Client Verpackung (GitHub):
dotnet add package Qdrant.Client
Verbindungsaufbau
using Qdrant.Client;
using Qdrant.Client.Grpc;
// gRPC client (recommended) - see https://qdrant.tech/documentation/interfaces/#grpc-interface
var client = new QdrantClient(
host: "localhost",
port: 6334, // gRPC port (6333 is REST)
https: false
);
// With API key - see https://qdrant.tech/documentation/guides/security/
var secureClient = new QdrantClient(
host: "your-qdrant.cloud",
port: 6334,
https: true,
apiKey: "your-api-key"
);
Verwenden Sie gRPC immer (Port 6334) für die Produktion - 3-5x schneller als REST.
Windows HTTP/2 Fixieren
Aktivieren Sie unter Windows unverschlüsselt HTTP/2 Vorher Erstellen des Clients:
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
Schlüsseloperationen
Suchen
// Vector search - see https://qdrant.tech/documentation/concepts/search/
var results = await client.SearchAsync(
collectionName: "blog_posts",
vector: queryEmbedding,
limit: 10,
filter: filter,
scoreThreshold: 0.5f, // Minimum similarity
searchParams: new SearchParams
{
HnswEf = 128, // Search accuracy (higher = better recall)
Exact = false // Use approximate search
},
withPayload: true
);
foreach (var result in results)
{
Console.WriteLine($"{result.Payload["title"].StringValue}: {result.Score}");
}
Batch Upsert
// Batch operations - see https://qdrant.tech/documentation/concepts/points/#batch-update
var points = documents.Select(doc => new PointStruct
{
Id = new PointId { Uuid = doc.Id },
Vectors = doc.Embedding,
Payload = { ["slug"] = doc.Slug, ["title"] = doc.Title }
}).ToList();
await client.UpsertAsync(
collectionName: "blog_posts",
points: points,
wait: true // Wait for indexing
);
Löschen
// Delete by filter - see https://qdrant.tech/documentation/concepts/points/#delete-points
await client.DeleteAsync(
collectionName: "blog_posts",
filter: new Filter
{
Must = { new Condition { Field = new FieldCondition
{
Key = "slug",
Match = new Match { Keyword = "old-post" }
}}}
}
);
HNSW-Index-Tuning
HNSW (Hierarchical Navigable Small World) ist Qdrants Indexalgorithmus.
flowchart TB
subgraph "HNSW Graph Layers"
L2[Layer 2 - Sparse]
L1[Layer 1 - Medium]
L0[Layer 0 - Dense]
end
Q[Query] --> L2
L2 --> L1
L1 --> L0
L0 --> R[Nearest Neighbors]
style L2 stroke:#8b5cf6,stroke-width:2px
style L1 stroke:#6366f1,stroke-width:2px
style L0 stroke:#3b82f6,stroke-width:2px
style Q stroke:#10b981,stroke-width:2px
style R stroke:#ef4444,stroke-width:2px
Index-Parameter
// HNSW config - see https://qdrant.tech/documentation/concepts/indexing/#hnsw-index
var hnswConfig = new HnswConfigDiff
{
M = 16, // Edges per node (16-32 recommended)
EfConstruct = 100, // Build-time accuracy (100-200)
FullScanThreshold = 10000 // Brute force threshold
};
await client.UpdateCollectionAsync(
collectionName: "blog_posts",
hnswConfig: hnswConfig
);
Suchzeitgenauigkeit:
var searchParams = new SearchParams
{
HnswEf = 128 // Higher = better recall, slower (64-256)
};
Tuning-Leitlinien: Verwenden Sie Fall , M , EfConstruct , HnswEf , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , |----------|---|-------------|--------| Schnelles, niedriges Rückrufen 8, 64, 32, Ausgeglichen 16, 100, 128, Hoch zurückrufen 32, 200, 256,
Nutzlastindizes
Erstellen Nutzlastindizes für häufig gefilterte Felder:
// Keyword index - see https://qdrant.tech/documentation/concepts/indexing/#payload-index
await client.CreatePayloadIndexAsync(
collectionName: "blog_posts",
fieldName: "language",
schemaType: PayloadSchemaType.Keyword
);
// Integer index for ranges
await client.CreatePayloadIndexAsync(
collectionName: "blog_posts",
fieldName: "published",
schemaType: PayloadSchemaType.Integer
);
Auswirkungen: 10-100x schnellere Filterung auf großen Sammlungen.
Quantifizierung
Quantifizierung reduziert den Speicherverbrauch:
// Scalar quantization - see https://qdrant.tech/documentation/guides/quantization/#scalar-quantization
await client.UpdateCollectionAsync(
collectionName: "blog_posts",
quantizationConfig: new ScalarQuantization
{
Scalar = new ScalarQuantizationConfig
{
Type = ScalarType.Int8, // float32 -> int8
Quantile = 0.99f,
AlwaysRam = true
}
}
);
Abtretung: 4x weniger Speicher, ~2% Rückrufverlust, 1.5x schnellere Suche.
Docker-Einsatz
# docker-compose.yml - see https://qdrant.tech/documentation/guides/installation/
services:
qdrant:
image: qdrant/qdrant:v1.12.1 # Pin version!
ports:
- "6333:6333" # REST
- "6334:6334" # gRPC
volumes:
- qdrant_data:/qdrant/storage
environment:
- QDRANT__SERVICE__GRPC_PORT=6334
- QDRANT__SERVICE__HTTP_PORT=6333
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:6333/health"]
interval: 30s
timeout: 10s
retries: 3
volumes:
qdrant_data:
Sicherheit
Aktivieren API-Schlüssel-Authentifizierung:
environment:
- QDRANT__SERVICE__API_KEY=your-secret-key
Überwachung
Metrik
Qdrant entlarvt Prometheus-Metriken bei /metrics:
curl http://localhost:6333/metrics
Schlüsselkennzahlen:
qdrant_collections_vector_count- Vektoren insgesamtqdrant_rest_responses_duration_seconds- Latenz abfragenqdrant_memory_usage_bytes- Speicherverbrauch
Schnappschüsse
Erstellen Sicherungen:
# Create snapshot
curl -X POST http://localhost:6333/collections/blog_posts/snapshots
# List snapshots
curl http://localhost:6333/collections/blog_posts/snapshots
# Restore (copy snapshot to storage/collections/blog_posts/snapshots/)
Häufig Gotchas
1. Port Confusion
- 6333 = REST API
- 6334 = gRPC API (verwenden!)
2. Missanpassung der Vektor-Dimension
Error: expected dim: 384, got 768
Ihr Einbettungsmodell und Ihre Sammlung müssen stimmen:
all-MiniLM-L6-v2: 384 Abmessungennomic-embed-text: 768 Abmessungen- OpenAI
text-embedding-3-small: 1536 Abmessungen
3. Langsame erste Abfrage
HNSW Lazy-loads in den Speicher. Aufwärmen nach dem Start:
await client.SearchAsync("blog_posts", new float[384], limit: 1);
4. Array Filtern
Verwendung Match.Any für Array-Felder:
new Match { Any = new RepeatedStrings { Strings = { "AI", "ML" } } }
Aufkommen
Offizielle Qdrant-Dokumentation
- Überblick - Fangen wir an.
- Begriffsbestimmungen - Kernkonzepte
- Sammlungen - Erstellen und Verwalten von Sammlungen
- Punkte - Arbeiten mit Vektoren
- Suchen - Abfragen
- Filtern - Filterbedingungen
- Indexierung - HNSW und Nutzlastindizes
- Quantifizierung - Speicheroptimierung
- Sicherheit - Authentifizierung
- Überwachung - Metrische und Telemetrie
- Schnappschüsse - Sicherung und Wiederherstellung
Client-Bibliotheken
- Qdrant .NET Client - Offizielles C# SDK
- NuGet-Paket - Letzte Veröffentlichung
Verwandte Artikel
- Teil 4: Umsetzung von ONNX und Qdrant
- Teil 5: Hybride Suche & Auto-Indexierung
- Überblick über die RAG-Serien
Quellencode
Alle verfügbaren Codes unter: github.com/scottgal/mostlylucidweb
Mostlylucid.SemanticSearch/Services/QdrantVectorStoreService.cs- Qdrant-Integration