Zum Inhalt springen

Leitfaden zur Auswahl von Vektordatenbanken für KI-Anwendungen

Vergleich von Pinecone, Weaviate, Qdrant, Milvus und pgvector für KI-Anwendungen: Indexierung, Abfrageleistung, Filterung, Betriebskomplexität.

4 Min. Lesezeit
Architekturdiagramm, das eigenständige Vektordatenbanken mit eingebetteten Vektorerweiterungen vergleicht und den Abfragefluss von der Embedding-Erzeugung über die Indexsuche bis zur Ergebnisbewertung zeigt

Jede KI-Anwendung, die Embeddings nutzt, braucht eine Vektordatenbank – doch die Wahl zwischen einem verwalteten Dienst, einer eigenständigen Vektordatenbank und einer Postgres-Erweiterung wirkt sich massiv auf Architektur, Betriebsaufwand und die Fähigkeit aus, Vektorsuche mit klassischen Abfragen zu kombinieren.

Der Markt ist regelrecht explodiert vor lauter Optionen. Jede geht andere Kompromisse zwischen Abfrageleistung, Filterfunktionen, Skalierbarkeit und Betriebseinfachheit ein. Die falsche Wahl erzeugt Migrationsschmerzen, die mit wachsender Embedding-Sammlung immer größer werden.

Vektorindex-Typen verstehen

Das zentrale Unterscheidungsmerkmal zwischen Vektordatenbanken ist ihre Indexierungsstrategie. Sie bestimmt Abfragegeschwindigkeit, Recall-Genauigkeit, Speicherverbrauch und Build-Zeit.

tstypescript
// ❌ Brute-force search — O(n) per query
function bruteForceSearch(
  query: number[],
  vectors: number[][],
  k: number
): number[] {
  const distances = vectors.map((v, i) => ({
    index: i,
    distance: cosineSimilarity(query, v),
  }));
  distances.sort((a, b) => b.distance - a.distance);
  return distances.slice(0, k).map((d) => d.index);
}
// Works for < 10K vectors. Unusable at scale.
tstypescript
// ✅ HNSW index — O(log n) per query with high recall
interface HNSWConfig {
  // Number of connections per node in the graph
  // Higher = better recall, more memory
  M: number;
  // Size of the dynamic candidate list during construction
  // Higher = better index quality, slower build
  efConstruction: number;
  // Size of candidate list during search
  // Higher = better recall, slower search
  efSearch: number;
}
 
const balancedConfig: HNSWConfig = {
  M: 16,
  efConstruction: 200,
  efSearch: 100,
};
 
const highRecallConfig: HNSWConfig = {
  M: 32,
  efConstruction: 400,
  efSearch: 200,
  // 99%+ recall, ~2x memory vs balanced
};
 
const lowLatencyConfig: HNSWConfig = {
  M: 12,
  efConstruction: 100,
  efSearch: 50,
  // ~95% recall, fastest queries
};
 
// IVF index — partition-based approach
interface IVFConfig {
  // Number of partitions (clusters)
  nlist: number;
  // Number of partitions to search per query
  nprobe: number;
}
 
// IVF suits large datasets where memory is constrained
// HNSW suits workloads requiring consistent low latency

HNSW-Graphen (Hierarchical Navigable Small World) dominieren, weil sie für die meisten Workloads das beste Verhältnis von Recall zu Latenz bieten. IVF (Inverted File Index) benötigt weniger Speicher, erfordert aber ein sorgfältiges Abstimmen der Anzahl an Partitionen und Probes. Produktquantisierung (PQ) komprimiert Vektoren für sehr große Datensätze auf Kosten eines gewissen Recall-Verlusts.

Vektordatenbank-Optionen im Vergleich

Jede Datenbank besetzt einen anderen Punkt im Spannungsfeld zwischen Betriebseinfachheit, Abfragefunktionen und Skalierbarkeit.

tstypescript
interface VectorDBComparison {
  name: string;
  type: "managed" | "self-hosted" | "extension";
  indexTypes: string[];
  filtering: "pre-filter" | "post-filter" | "hybrid";
  scalarFields: boolean;
  multiTenancy: "native" | "namespace" | "manual";
  operationalComplexity: "low" | "medium" | "high";
}
 
const databases: VectorDBComparison[] = [
  {
    name: "Pinecone",
    type: "managed",
    indexTypes: ["proprietary"],
    filtering: "hybrid",
    scalarFields: true,
    multiTenancy: "namespace",
    operationalComplexity: "low",
  },
  {
    name: "Weaviate",
    type: "self-hosted",
    indexTypes: ["HNSW", "flat"],
    filtering: "pre-filter",
    scalarFields: true,
    multiTenancy: "native",
    operationalComplexity: "medium",
  },
  {
    name: "Qdrant",
    type: "self-hosted",
    indexTypes: ["HNSW"],
    filtering: "hybrid",
    scalarFields: true,
    multiTenancy: "manual",
    operationalComplexity: "medium",
  },
  {
    name: "pgvector",
    type: "extension",
    indexTypes: ["IVFFlat", "HNSW"],
    filtering: "pre-filter",
    scalarFields: true,
    multiTenancy: "manual",
    operationalComplexity: "low",
  },
];

pgvector: Wenn deine Daten bereits in Postgres liegen

Für Anwendungen, bei denen die Vektorsuche nur eine von vielen Funktionen ist und Postgres bereits im Einsatz ist, erspart pgvector eine komplette zusätzliche Infrastrukturabhängigkeit.

sqlsql
-- Setup pgvector
CREATE EXTENSION IF NOT EXISTS vector;
 
-- Create table with embedding column
CREATE TABLE documents (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  title TEXT NOT NULL,
  content TEXT NOT NULL,
  embedding vector(1536),
  category TEXT NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now(),
  tenant_id UUID NOT NULL
);
 
-- HNSW index for cosine similarity
CREATE INDEX ON documents
  USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 200);
 
-- Combined vector + scalar query — single database
SELECT
  id,
  title,
  1 - (embedding <=> $1::vector) AS similarity
FROM documents
WHERE tenant_id = $2
  AND category = $3
  AND created_at > NOW() - INTERVAL '90 days'
ORDER BY embedding <=> $1::vector
LIMIT 10;
tstypescript
// TypeScript integration with pgvector
import { Pool } from "pg";
 
interface SearchResult {
  id: string;
  title: string;
  similarity: number;
}
 
async function semanticSearch(
  pool: Pool,
  queryEmbedding: number[],
  tenantId: string,
  options: {
    category?: string;
    limit?: number;
    minSimilarity?: number;
  } = {}
): Promise<SearchResult[]> {
  const { category, limit = 10, minSimilarity = 0.7 } =
    options;
 
  const embeddingStr = `[${queryEmbedding.join(",")}]`;
 
  const conditions = [
    "tenant_id = $2",
    `1 - (embedding <=> $1::vector) >= $3`,
  ];
  const params: unknown[] = [
    embeddingStr,
    tenantId,
    minSimilarity,
  ];
 
  if (category) {
    conditions.push(`category = $${params.length + 1}`);
    params.push(category);
  }
 
  const query = `
    SELECT id, title,
      1 - (embedding <=> $1::vector) AS similarity
    FROM documents
    WHERE ${conditions.join(" AND ")}
    ORDER BY embedding <=> $1::vector
    LIMIT ${limit}
  `;
 
  const result = await pool.query(query, params);
  return result.rows;
}

Der Vorteil liegt in der transaktionalen Konsistenz: Du kannst ein Dokument und sein Embedding in derselben Transaktion einfügen, Vektoren mit gewöhnlichen SQL-Joins und -Filtern abfragen und alles mit deinem bestehenden Postgres-Tooling verwalten.

Dedizierte Vektordatenbanken: Wenn die Skalierung es erfordert

Bei Millionen von Vektoren, einer geforderten Latenz unter 10 ms oder Anforderungen wie Echtzeit-Indexaktualisierungen bei hohem Durchsatz zieht eine dedizierte Vektordatenbank vorbei.

tstypescript
// Qdrant client example — dedicated vector DB features
import { QdrantClient } from "@qdrant/js-client-rest";
 
const client = new QdrantClient({
  url: "http://localhost:6333",
});
 
// Create collection with optimized settings
await client.createCollection("documents", {
  vectors: {
    size: 1536,
    distance: "Cosine",
  },
  optimizers_config: {
    indexing_threshold: 20000,
    memmap_threshold: 50000,
  },
  hnsw_config: {
    m: 16,
    ef_construct: 200,
    full_scan_threshold: 10000,
  },
});
 
// Search with payload filtering
const results = await client.search("documents", {
  vector: queryEmbedding,
  filter: {
    must: [
      {
        key: "tenant_id",
        match: { value: tenantId },
      },
      {
        key: "category",
        match: { any: ["engineering", "ai"] },
      },
    ],
    must_not: [
      {
        key: "status",
        match: { value: "archived" },
      },
    ],
  },
  limit: 10,
  with_payload: true,
  score_threshold: 0.7,
});

Entscheidungsrahmen

Die Wahl hängt letztlich von deiner Skalierung, deinen Abfragemustern und deiner betrieblichen Kapazität ab.

tstypescript
interface DecisionInput {
  vectorCount: number;
  queryLatencyTarget: string;
  existingDatabase: string;
  needsJoins: boolean;
  teamSize: number;
  budget: string;
}
 
function recommendDatabase(
  input: DecisionInput
): string {
  // Under 1M vectors + already using Postgres + needs joins
  if (
    input.vectorCount < 1_000_000 &&
    input.existingDatabase === "postgres" &&
    input.needsJoins
  ) {
    return "pgvector — minimal operational overhead, " +
      "transactional consistency, SQL joins";
  }
 
  // Over 1M vectors or sub-10ms latency requirement
  if (
    input.vectorCount >= 1_000_000 ||
    input.queryLatencyTarget === "sub-10ms"
  ) {
    if (input.teamSize <= 3) {
      return "Pinecone — managed service, no ops burden";
    }
    return "Qdrant/Weaviate — self-hosted for control " +
      "and cost efficiency at scale";
  }
 
  // Default: start simple
  return "pgvector — start here, migrate when you " +
    "hit measured performance limits";
}

Die wichtigsten Erkenntnisse

HNSW-Indizes bieten für die meisten Workloads das beste Verhältnis von Recall zu Latenz, aber das Abstimmen von Parametern wie M, efConstruction und efSearch tauscht Speicher und Build-Zeit direkt gegen Abfragequalität. pgvector erspart eine Infrastrukturabhängigkeit, wenn die Vektorsuche nur eine von vielen Funktionen ist – du erhältst transaktionale Konsistenz, SQL-Joins und dein bestehendes Postgres-Tooling. Dedizierte Vektordatenbanken wie Qdrant und Weaviate ziehen bei Millionen von Vektoren vorbei, wenn Latenzanforderungen unter 10 ms, Echtzeit-Indexaktualisierungen und native Multi-Tenancy gefragt sind. Die Filterstrategie spielt eine wichtige Rolle: Pre-Filtering wendet skalare Bedingungen vor der Vektorsuche an (präzise, kann aber die nächstgelegenen Vektoren verfehlen), Post-Filtering durchsucht erst alle Vektoren und filtert danach die Ergebnisse (kann weniger Ergebnisse liefern als angefordert), und hybride Ansätze balancieren beides aus. Starte mit pgvector, wenn du bereits Postgres nutzt und weniger als eine Million Vektoren hast – die Migration zu einer dedizierten Vektordatenbank ist unkompliziert, sobald du gemessene Leistungsgrenzen erreichst, aber eine verfrühte Migration bringt operative Komplexität ohne Nutzen.

Wilfredo Rujel

Wilfredo Rujel

Full-Stack-Softwareentwickler

Diesen Beitrag teilenX