Saltar al contenido

Cómo elegir una base de datos vectorial para aplicaciones de IA

Compara Pinecone, Weaviate, Qdrant, Milvus y pgvector para IA: estrategias de indexación, rendimiento, filtrado y complejidad operativa.

5 min de lectura
Diagrama de arquitectura que compara bases de datos vectoriales independientes con extensiones vectoriales integradas, mostrando el flujo de consultas desde la generación de embeddings hasta la búsqueda en el índice y la clasificación de resultados

Toda aplicación de IA que utiliza embeddings necesita una base de datos vectorial, pero la elección entre un servicio gestionado, una base de datos vectorial independiente y una extensión de Postgres afecta drásticamente tu arquitectura, la carga operativa y la capacidad de combinar la búsqueda vectorial con consultas tradicionales.

El mercado se ha llenado de opciones. Cada una plantea compensaciones distintas entre rendimiento de consultas, capacidades de filtrado, escalabilidad y simplicidad operativa. Elegir la opción equivocada genera problemas de migración que se agravan a medida que crece tu colección de embeddings.

Entendiendo los tipos de índices vectoriales

El principal factor diferenciador entre bases de datos vectoriales es su estrategia de indexación. De ella dependen la velocidad de consulta, la precisión del recall, el uso de memoria y el tiempo de construcción del índice.

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

Los grafos HNSW (Hierarchical Navigable Small World) predominan porque ofrecen la mejor relación entre recall y latencia para la mayoría de las cargas de trabajo. IVF (Inverted File Index) usa menos memoria, pero requiere ajustar el número de particiones y de sondas (probes). La cuantización de producto (PQ) comprime los vectores para conjuntos de datos masivos a costa de cierta pérdida de recall.

Comparación de opciones de bases de datos vectoriales

Cada base de datos ocupa un punto distinto en el espacio de compensaciones entre simplicidad operativa, funciones de consulta y escalabilidad.

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: cuando tus datos ya viven en Postgres

Para aplicaciones en las que la búsqueda vectorial es solo una función más entre muchas, y en las que ya usas Postgres, pgvector elimina por completo una dependencia de infraestructura.

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;
}

La ventaja es la consistencia transaccional: puedes insertar un documento y su embedding en la misma transacción, consultar vectores con joins y filtros SQL estándar, y gestionar todo con las herramientas de Postgres que ya usas.

Bases de datos vectoriales dedicadas: cuando la escala lo exige

Cuando tienes millones de vectores, necesitas latencias inferiores a 10 ms o requieres funciones como actualizaciones de índice en tiempo real con alto throughput, una base de datos vectorial dedicada toma la delantera.

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,
});

Marco de decisión

La elección se reduce a tu escala, tus patrones de consulta y tu capacidad operativa.

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";
}

Puntos clave

Los índices HNSW ofrecen la mejor relación entre recall y latencia para la mayoría de las cargas de trabajo, pero ajustar parámetros como M, efConstruction y efSearch supone intercambiar directamente memoria y tiempo de construcción por calidad de consulta. pgvector elimina una dependencia de infraestructura cuando la búsqueda vectorial es solo una función más entre muchas: obtienes consistencia transaccional, joins SQL y las herramientas de Postgres que ya conoces. Las bases de datos vectoriales dedicadas, como Qdrant y Weaviate, toman la delantera a partir de millones de vectores, con requisitos de latencia inferiores a 10 ms, actualizaciones de índice en tiempo real y multi-tenancy nativa. La estrategia de filtrado importa: el pre-filtrado aplica condiciones escalares antes de la búsqueda vectorial (preciso, pero puede omitir los vectores más cercanos), el post-filtrado busca en todos los vectores y luego filtra los resultados (puede devolver menos resultados de los solicitados), y los enfoques híbridos equilibran ambos. Empieza con pgvector si ya usas Postgres y tienes menos de un millón de vectores: la migración a una base de datos vectorial dedicada es sencilla cuando alcanzas límites de rendimiento medidos, pero migrar prematuramente añade complejidad operativa sin ningún beneficio.

Wilfredo Rujel

Wilfredo Rujel

Ingeniero de Software Full Stack

Compartir esta publicaciónX