effidevFlutter · Cloudflare-Edge · Cloud-Kostenoptimierung
Deutsch

pgvector vs Cloudflare Vectorize: RAG Hybrid-Suche und Latenzoptimierung im Vergleich

pgvector vs Cloudflare Vectorize: RAG Hybrid-Suche und Latenzoptimierung im Vergleich

Ein häufiger Fehler beim Aufbau von Retrieval-Augmented Generation (RAG) Systemen ist die ausschließliche Nutzung von Vektorsuche. Wenn Benutzer nach bestimmten Produktnummern, Bestell-IDs oder Fehlercodes suchen, liefert die reine Kosinus-Ähnlichkeit oft semantisch ähnliche, aber falsche Dokumente. Die Lösung ist Hybrid-Suche (Kombination aus BM25-Schlüsselwortsuche und Vektorsuche).

Sollten Sie Ihre Hybrid-Suche auf einer zentralen PostgreSQL-Datenbank mit pgvector betreiben oder Cloudflare Vectorize an der Edge nutzen? Die beiden Ansätze unterscheiden sich drastisch in Bezug auf Latenz, Genauigkeit und betriebliche Komplexität.

Dieser Artikel vergleicht die Architekturen, zeigt Latenz-Benchmarks (Edge vs. zentrale DB) und bietet eine vollständige TypeScript-Implementierung des Reciprocal Rank Fusion (RRF) Algorithmus.

Wichtige Erkenntnisse

  • pgvector bietet volle ACID-Transaktionssicherheit und nahtlose SQL-JOINs, aber HNSW-Indizes verbrauchen viel Arbeitsspeicher (RAM) und Netzwerk-Roundtrips (RTT) zur zentralen Datenbank verursachen 30ms–100ms Latenz.
  • Cloudflare Vectorize läuft direkt an den Cloudflare Edge-PoPs zusammen mit Workers und Workers AI. Das ermöglicht Vektorabfragen in 0–5ms, erfordert jedoch für Schlüsselwortsuchen die Kombination mit D1 oder Workers KV.
  • Die Kombination aus Vektorsuche und BM25-Schlüsselwortsuche mittels Reciprocal Rank Fusion (RRF) erhöht die Accuracy/Recall von RAG-Systemen um über 25%.
  • Edge-Embedding-Generierung mit Workers AI (@cf/baai/bge-small-en-v1.5) und KV-Caching reduzieren die Pipeline-Latenz auf ein Minimum.

Architekturvergleich: pgvector vs. Cloudflare Vectorize

Feature pgvector (PostgreSQL Extension) Cloudflare Vectorize
Architektur Zentrale RDBMS-Erweiterung Global verteilter Edge-Vektor-Index
Index-Algorithmus IVFFlat, HNSW Verteilte HNSW-Engine
Latenz (RTT) 30ms – 150ms je nach Region Co-located mit Worker: 2ms – 10ms
Metadaten-Filter Voller SQL WHERE- & JOIN-Support Key-Value-Filter (eq, in, ne)

Latenz-Benchmark (p95)

Bei 1.000.000 Vektoren (1536 Dimensionen) betrug die p95-Latenz für die Dokumentenabfrage:

Reciprocal Rank Fusion (RRF) TypeScript-Implementierung

export interface Env {
  VECTOR_INDEX: VectorizeIndex;
  DB: D1Database;
  AI: Ai;
}

function reciprocalRankFusion(
  vectorResults: Array<{ id: string }>,
  keywordResults: Array<{ id: string }>,
  k = 60
): Map<string, number> {
  const rrfScores = new Map<string, number>();

  vectorResults.forEach((doc, index) => {
    const rank = index + 1;
    const currentScore = rrfScores.get(doc.id) || 0;
    rrfScores.set(doc.id, currentScore + 1 / (k + rank));
  });

  keywordResults.forEach((doc, index) => {
    const rank = index + 1;
    const currentScore = rrfScores.get(doc.id) || 0;
    rrfScores.set(doc.id, currentScore + 1 / (k + rank));
  });

  return rrfScores;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const query = url.searchParams.get("q");

    if (!query) return new Response("Missing query 'q'", { status: 400 });

    const embeddingResponse = await env.AI.run("@cf/baai/bge-base-en-v1.5", {
      text: [query],
    });
    const queryVector = embeddingResponse.data[0];

    const vectorPromise = env.VECTOR_INDEX.query(queryVector, { topK: 20 });
    const keywordPromise = env.DB.prepare(
      `SELECT id FROM documents_fts WHERE content MATCH ? ORDER BY rank LIMIT 20`
    ).bind(query).all<{ id: string }>();

    const [vectorRes, keywordRes] = await Promise.all([vectorPromise, keywordPromise]);

    const vectorList = vectorRes.matches.map((m) => ({ id: m.id }));
    const keywordList = (keywordRes.results || []).map((r) => ({ id: r.id }));

    const rrfScores = reciprocalRankFusion(vectorList, keywordList, 60);
    const sortedDocs = Array.from(rrfScores.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, 5);

    const docIds = sortedDocs.map(([id]) => id);
    if (docIds.length === 0) return Response.json({ results: [] });

    const placeholders = docIds.map(() => "?").join(",");
    const finalDocs = await env.DB.prepare(
      `SELECT id, title, content FROM documents WHERE id IN (${placeholders})`
    ).bind(...docIds).all();

    return Response.json({
      query,
      results: finalDocs.results,
      rrfScores: Object.fromEntries(sortedDocs)
    });
  },
};

Fazit

Wählen Sie pgvector, wenn Sie bereits eine PostgreSQL-Infrastruktur nutzen und komplexe JOINs und ACID-Garantien benötigen. Wählen Sie Cloudflare Vectorize, wenn Sie globale Serverless-RAG-Systeme mit Latenzen unter 50ms aufbauen möchten.