Skip to content
StackPractices
intermediate Por Mathias Paulenko

Bases de Datos Vectoriales

Guia practica de bases de datos vectoriales: embeddings, busqueda por similitud, vecinos aproximados mas cercanos, y elegir entre Pinecone, Weaviate, pgvector y Chroma.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Overview

Las bases de datos vectoriales almacenan vectores numericos de alta dimension (embeddings) generados por modelos de machine learning y habilitan busqueda por similitud mediante algoritmos de Vecino Mas Cercano Aproximado (ANN). Potencian busqueda semantica, sistemas de recomendacion, recuperacion de imagenes por contenido, y Generacion Aumentada por Recuperacion (RAG) para LLMs. A diferencia de bases de datos tradicionales que buscan por coincidencia exacta o rango, las bases de datos vectoriales encuentran los vectores “mas cercanos” en el espacio de embeddings — la representacion matematica de significado, caracteristicas de imagen o firmas de audio.

When to Use

  • For alternatives, see Complete Guide to Vector Databases.

  • Necesitas busqueda semantica (encontrar significado similar, no solo coincidencia de palabras clave)

  • Los pipelines RAG de LLMs requieren recuperar chunks de contexto relevantes

  • Sistemas de recomendacion sugieren items similares a preferencias de usuario

  • Recuperacion de imagen, audio o video por similitud de contenido

  • Tienes modelos de embedding pre-entrenados y necesitas almacenamiento vectorial preparado para crecimiento

Como Funciona la Busqueda Vectorial

  1. Embedding: Un modelo (OpenAI, BERT, CLIP) convierte texto/imagen en un vector denso (ej. 768-1536 dimensiones)
  2. Indexacion: Los vectores se organizan en un indice ANN (HNSW, IVF, PQ) para recuperacion rapida
  3. Consulta: La consulta se embebe y el indice retorna los K vecinos mas cercanos
  4. Filtrado de metadata: Combinar similitud vectorial con filtrado tradicional (fecha, categoria, ID de usuario)

Comparacion

Base de datosDespliegueIndiceMejor para
PineconeNube gestionadaHNSW, filtros metadataRAG en produccion, sin overhead de ops
WeaviateAuto-gestionado / nubeHNSW, BM25 hibridoMulti-modal, interfaz GraphQL
pgvectorExtension PostgreSQLivfflat, hnswEquipos ya en Postgres
ChromaEmbebido / localHNSWPrototipado, RAG local a pequena escala
Milvus/ZillizAuto-gestionado / nubeIVF, HNSW, GPUGran escala, alto throughput

Ejemplo pgvector

-- Habilitar extension pgvector
CREATE EXTENSION IF NOT EXISTS vector;

-- Crear tabla con columna vectorial
CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT,
    embedding vector(1536)
);

-- Crear indice HNSW para busqueda ANN rapida
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);

-- Insertar documento con embedding
INSERT INTO documents (title, content, embedding)
VALUES ('Guia de Bases Vectoriales', 'Una guia sobre bases de datos vectoriales...', '[0.12, -0.03, ...]');

-- Busqueda semantica: encontrar 5 documentos mas similares
SELECT id, title, content,
    1 - (embedding <=> '[0.11, -0.02, ...]') as similarity
FROM documents
ORDER BY embedding <=> '[0.11, -0.02, ...]'
LIMIT 5;

Busqueda Hibrida (Vectorial + Palabras Clave)

# Weaviate: combinar similitud vectorial con busqueda BM25
import weaviate

client = weaviate.connect_to_local()

results = client.collections.get("Article").query.hybrid(
    query="arquitectura base de datos vectorial",
    vector=[0.12, -0.03, ...],
    alpha=0.5,  # 0 = puro BM25, 1 = puro vectorial
    limit=10
)

Ejemplo de Pipeline RAG

from openai import OpenAI
import chromadb

# 1. Cargar y fragmentar documentos
chunks = load_and_chunk_documents("knowledge_base/")

# 2. Embeber y almacenar en Chroma
client = chromadb.Client()
collection = client.create_collection("docs")
embeddings = openai_client.embeddings.create(input=chunks, model="text-embedding-3-small")
collection.add(ids=ids, documents=chunks, embeddings=[e.embedding for e in embeddings.data])

# 3. Recuperar chunks relevantes para una consulta
query_embedding = openai_client.embeddings.create(input="Como funcionan los indices vectoriales?", model="text-embedding-3-small")
results = collection.query(query_embeddings=[query_embedding.data[0].embedding], n_results=5)

# 4. Aumentar prompt del LLM con contexto recuperado
context = "\n".join([r["document"] for r in results["documents"][0]])
prompt = f"Contexto:\n{context}\n\nPregunta: Como funcionan los indices vectoriales?"
response = openai_client.chat.completions.create(model="gpt-4o", messages=[{"role": "user", "content": prompt}])

Algoritmos ANN

AlgoritmoTipoVelocidadMemoriaMejor para
HNSWBasado en grafosRapidoAltaProposito general, alta recall
IVFClusteringMediaMediaGrandes datasets, limitado de memoria
PQCuantizacionRapidoBajaBillones de vectores, recall aceptable

Common Mistakes

  • Metrica de distancia incorrecta — similitud coseno para texto semantico, euclidiana para caracteristicas de imagen, producto punto para embeddings normalizados
  • Sin filtrado de metadata — busqueda puramente vectorial retorna resultados irrelevantes; siempre combinar con filtros de metadata
  • Ignorar ajuste de indice — parametros HNSW por defecto pueden no ajustarse a tus requisitos de recall/latencia
  • Almacenar vectores raw sin indice — escaneo completo de fuerza bruta es O(n) e inusable a escala
  • Usar una base vectorial para consultas estructuradas — combinar con una base relacional; las bases vectoriales son malas en agregacion y joins

Troubleshooting

  • Query is slow after an index change: check execution plans and cardinality estimates. Rebuild statistics and verify the index is being used.
  • Replication lag grows: monitor network, disk I/O, and long transactions. Split large writes and consider parallel replication.
  • Connections exhausted: review connection pool size, idle timeouts, and leaked connections. Use prepared statements and close connections in finally blocks.
  • Backup takes too long: enable compression, incremental backups, and off-peak scheduling. Test restore times against RTO targets.
  • Deadlocks in high concurrency: access tables and rows in a consistent order. Keep transactions short and retry deadlocked operations.

FAQ

Necesito una base de datos vectorial dedicada o puedo usar Postgres? Para escala pequena a media (< 1M vectores) y equipos ya en Postgres, pgvector es suficiente. Para alta escala, multi-tenant o gestionada, Pinecone o Weaviate son mejores.

Como elijo dimensiones de embedding? Usa la dimension de salida de tu modelo elegido (OpenAI text-embedding-3-small = 1536, BERT-base = 768). No reduzcas dimensiones arbitrariamente sin entrenamiento consciente de cuantizacion.

Puedo actualizar vectores en su lugar? Si, pero puede requerir re-indexacion dependiendo de la base de datos y tipo de indice. Algunos sistemas soportan actualizaciones incrementales; otros requieren reconstruccion completa.

¿Cómo empiezo con esto en un proyecto existente?

Empieza con una parte pequeña y aislada de tu codebase. Aplica los conceptos de esta guía a un módulo o servicio. Mide el impacto, luego expande a otras áreas.

¿Qué herramientas necesito?

Las herramientas mencionadas throughout esta guía se listan en cada sección. La mayoría son open-source y ampliamente adoptadas. Consulta los recursos relacionados para instrucciones de setup.

¿Cómo mido el éxito después de implementar esto?

Define métricas claras antes de empezar: benchmarks de rendimiento, tasas de error o indicadores de mantenibilidad. Compara antes y después. Itera basándote en datos, no en suposiciones.

Temas Avanzados

Escenario Detallado: Sistema RAG para Documentacion Tecnica

Sistema: Chatbot RAG sobre 50,000 paginas de documentacion tecnica
Stack: OpenAI text-embedding-3-small (1536 dim) + pgvector + GPT-4o
Requisitos: Respuestas precisas con citas, latencia < 3s

Pipeline:
  1. Cargar documentos (PDF, Markdown, HTML)
  2. Fragmentar en chunks de ~500 tokens con overlap de 50
  3. Generar embeddings con OpenAI
  4. Almacenar en PostgreSQL con pgvector
  5. En consulta: embeber pregunta, buscar chunks similares, aumentar LLM

Esquema pgvector:
  CREATE EXTENSION IF NOT EXISTS vector;

  CREATE TABLE doc_chunks (
      id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
      doc_id VARCHAR(100) NOT NULL,
      chunk_index INT NOT NULL,
      content TEXT NOT NULL,
      embedding vector(1536) NOT NULL,
      metadata JSONB DEFAULT '{}',
      created_at TIMESTAMPTZ DEFAULT NOW()
  );

  -- Indice HNSW para busqueda ANN
  CREATE INDEX idx_doc_chunks_embedding
  ON doc_chunks USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

  -- Indice para filtrado por documento
  CREATE INDEX idx_doc_chunks_doc ON doc_chunks(doc_id);

Ingestion (Python):
  from openai import OpenAI
  import psycopg2

  client = OpenAI()
  conn = psycopg2.connect("dbname=ragdb")

  def embed_text(text: str) -> list[float]:
      resp = client.embeddings.create(
          input=text, model="text-embedding-3-small"
      )
      return resp.data[0].embedding

  def ingest_document(doc_id: str, chunks: list[str]):
      for i, chunk in enumerate(chunks):
          emb = embed_text(chunk)
          with conn.cursor() as cur:
              cur.execute(
                  "INSERT INTO doc_chunks (doc_id, chunk_index, content, embedding) "
                  "VALUES (%s, %s, %s, %s)",
                  (doc_id, i, chunk, emb)
              )
      conn.commit()

Consulta RAG:
  def rag_query(question: str, top_k: int = 5) -> str:
      q_emb = embed_text(question)
      with conn.cursor() as cur:
          cur.execute(
              "SELECT content, doc_id, chunk_index, "
              "1 - (embedding <=> %s) AS similarity "
              "FROM doc_chunks "
              "ORDER BY embedding <=> %s "
              "LIMIT %s",
              (q_emb, q_emb, top_k)
          )
          results = cur.fetchall()

      context = "\n\n".join([r[0] for r in results])
      sources = [f"doc:{r[1]} chunk:{r[2]} sim:{r[3]:.3f}" for r in results]

      prompt = (
          f"Contexto:\n{context}\n\n"
          f"Pregunta: {question}\n"
          f"Responde basandote en el contexto. Cita la fuente."
      )
      response = client.chat.completions.create(
          model="gpt-4o",
          messages=[{"role": "user", "content": prompt}]
      )
      return response.choices[0].message.content + "\n\nFuentes: " + ", ".join(sources)

Resultados:
  | Metrica | Valor |
  |---------|-------|
  | Tiempo de embedding | 200ms |
  | Tiempo de busqueda vectorial | 15ms |
  | Tiempo de LLM | 1.5s |
  | Latencia total | ~1.7s |
  | Recall@5 | 92% |
  | Precision de respuestas | 87% (con citas correctas) |

Optimizacion de indice HNSW:
  - m=16: balance entre recall y memoria (default)
  - ef_construction=64: calidad de construccion (mayor = mejor, mas lento)
  - ef_search=40: ajustar en consulta para trade-off recall/latencia
  - Para 50K vectores: 50MB de indice, busqueda < 20ms

Lecciones aprendidas:
  - pgvector es suficiente para < 1M vectores
  - El tamano de chunk afecta la calidad: 500 tokens con overlap funciona bien
  - Filtrar por metadata antes de la busqueda vectorial mejora relevancia
  - HNSW supera a IVF en recall para datasets pequenos-medianos

Como manejo actualizaciones de embeddings cuando cambia el modelo?

Versiona los embeddings: almacena el modelo y la version en metadata. Cuando cambies de modelo, re-embebe todos los documentos en una nueva columna o tabla. Manten ambas versiones durante la transicion. Actualiza las consultas para usar la nueva version. Elimina la vieja cuando no haya trafico. Programa re-embedding en lotes durante horas de baja carga para evitar impactar latencia.

End of document. Review and update quarterly.

Errores Comunes en Producción

  • Tratar la guía como un checklist para completar una vez en lugar de una práctica por evolucionar.
  • Adoptar cada recomendación de golpe en lugar de comenzar con un cambio medido.
  • Saltar la evaluación de madurez e imponer prácticas avanzadas a un equipo no preparado.
  • No actualizar runbooks y expectativas de guardia al introducir nuevas prácticas.
  • Ignorar datos reales de incidentes al priorizar qué partes de la guía aplicar primero.
  • No asignar un responsable que revise decisiones trimestralmente.
  • Copiar ejemplos sin adaptarlos a las herramientas y restricciones reales del equipo.
  • Olvidar medir resultados antes de agregar la siguiente mejora.