Bases de datos vectoriales y búsqueda por similitud
Guía práctica de bases de datos vectoriales para búsqueda por similitud: embeddings, indexación, métricas de distancia y cuándo elegir cada opción.

Las bases de datos vectoriales almacenan y consultan vectores de alta dimensionalidad: representaciones numéricas de datos como texto, imágenes o comportamiento de usuario. Cuando conviertes una frase en un vector de 384 dimensiones mediante un modelo de embeddings, las frases similares generan vectores similares. Una base de datos vectorial encuentra de forma eficiente los vectores más cercanos a un vector de consulta, lo que permite la búsqueda semántica, los motores de recomendación y la generación aumentada por recuperación (RAG) para LLMs.
Las bases de datos tradicionales comparan valores exactos o rangos. Las bases de datos vectoriales comparan significado. Buscar "cómo solucionar una fuga de memoria" devuelve resultados sobre "depuración de errores por falta de memoria" y "ajuste del recolector de basura", aunque esos documentos no compartan ni una sola palabra clave con la consulta.
Cómo funciona la búsqueda vectorial
La búsqueda vectorial convierte la consulta en un vector y encuentra los K vectores más cercanos en la base de datos utilizando una métrica de distancia.
import numpy as np
from numpy.linalg import norm
# Three distance metrics for vector similarity
def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
"""Measures angle between vectors. Range: -1 to 1.
Most common for text embeddings."""
return float(np.dot(a, b) / (norm(a) * norm(b)))
def euclidean_distance(a: np.ndarray, b: np.ndarray) -> float:
"""Measures straight-line distance. Range: 0 to infinity.
Useful when magnitude matters."""
return float(norm(a - b))
def dot_product(a: np.ndarray, b: np.ndarray) -> float:
"""Combines angle and magnitude. Range: -inf to inf.
Fastest to compute."""
return float(np.dot(a, b))
# Example: text embeddings from sentence-transformers
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
docs = [
"How to optimize database queries",
"SQL query performance tuning",
"Introduction to machine learning",
"Best practices for REST API design",
]
embeddings = model.encode(docs) # Shape: (4, 384)
query = model.encode("making SQL queries faster") # Shape: (384,)
# Find most similar documents
similarities = [cosine_similarity(query, emb) for emb in embeddings]
# [0.72, 0.85, 0.12, 0.18]
# "SQL query performance tuning" is most similar (0.85)Indexación aproximada de vecinos más cercanos (ANN)
La búsqueda exacta de vecinos más cercanos compara la consulta con todos los vectores de la base de datos, es decir, O(n) por consulta. Con millones de vectores, esto resulta demasiado lento. Los índices ANN sacrifican una pequeña parte de precisión a cambio de enormes mejoras de velocidad.
# HNSW (Hierarchical Navigable Small World) — the most popular ANN algorithm
# Used by pgvector, Weaviate, Qdrant, and others
# How HNSW works:
# - Builds a multi-layer graph of vectors
# - Top layers: few vectors, large jumps (coarse search)
# - Bottom layers: many vectors, small jumps (fine search)
# - Search starts at top layer, navigates down to find nearest neighbors
# - Typical recall: 95-99% with 10-100x speedup over brute force
# IVF (Inverted File Index) — cluster-based approach
# Used by FAISS
# - Clusters vectors using k-means
# - At query time, search only the nearest clusters
# - nprobe parameter controls accuracy/speed trade-off// ❌ Brute force search — O(n) for every query
async function searchBruteForce(
query: number[],
allVectors: number[][],
k: number
): Promise<number[]> {
const similarities = allVectors.map((vec, idx) => ({
idx,
score: cosineSimilarity(query, vec),
}));
similarities.sort((a, b) => b.score - a.score);
return similarities.slice(0, k).map((s) => s.idx);
}
// 1M vectors × 384 dimensions = ~1.5 seconds per query
// ✅ ANN search with an index — sublinear query time
// Same 1M vectors with HNSW index: ~5 milliseconds per query
// 95-99% of results match exact searchUsar pgvector con PostgreSQL
Para las aplicaciones que ya usan PostgreSQL, pgvector añade búsqueda vectorial sin necesidad de incorporar una base de datos nueva. Es el camino más sencillo para los equipos que no necesitan una base de datos vectorial dedicada.
-- Enable the pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;
-- Create a table with a vector column
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
content TEXT NOT NULL,
embedding vector(384) NOT NULL, -- 384 dimensions
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Create an HNSW index for fast similarity search
CREATE INDEX idx_documents_embedding
ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
-- Insert a document with its embedding
INSERT INTO documents (content, embedding, metadata)
VALUES (
'How to optimize PostgreSQL queries for large datasets',
'[0.023, -0.041, 0.089, ...]'::vector,
'{"category": "database", "author": "jane"}'
);
-- Search for similar documents
SELECT
id,
content,
1 - (embedding <=> $1::vector) AS similarity
FROM documents
WHERE metadata->>'category' = 'database'
ORDER BY embedding <=> $1::vector
LIMIT 10;// TypeScript: Using pgvector for semantic search
import { Pool } from 'pg';
interface SearchResult {
id: number;
content: string;
similarity: number;
metadata: Record<string, string>;
}
async function semanticSearch(
pool: Pool,
queryEmbedding: number[],
options: {
limit?: number;
minSimilarity?: number;
filter?: Record<string, string>;
} = {}
): Promise<SearchResult[]> {
const { limit = 10, minSimilarity = 0.5, filter } = options;
// Build the embedding string for pgvector
const embeddingStr = `[${queryEmbedding.join(',')}]`;
let whereClause = '';
const params: unknown[] = [embeddingStr, limit];
if (filter) {
const conditions = Object.entries(filter).map(([key, value], i) => {
params.push(value);
return `metadata->>'${key}' = $${i + 3}`;
});
whereClause = `WHERE ${conditions.join(' AND ')}`;
}
const result = await pool.query<SearchResult>(
`SELECT
id,
content,
1 - (embedding <=> $1::vector) AS similarity,
metadata
FROM documents
${whereClause}
ORDER BY embedding <=> $1::vector
LIMIT $2`,
params
);
return result.rows.filter((r) => r.similarity >= minSimilarity);
}Usar FAISS para búsquedas locales
FAISS (Facebook AI Similarity Search) es una biblioteca de búsqueda vectorial en memoria. Es más rápida que las soluciones basadas en bases de datos, pero no persiste los datos: el almacenamiento hay que gestionarlo por separado.
import faiss
import numpy as np
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
# Generate embeddings for your documents
documents = [
"Kubernetes pod scheduling algorithms",
"Docker container networking basics",
"PostgreSQL index optimization guide",
"React hooks performance patterns",
# ... thousands more documents
]
embeddings = model.encode(documents)
embeddings = np.array(embeddings).astype("float32")
# Normalize for cosine similarity
faiss.normalize_L2(embeddings)
# Build an IVF index for approximate search
dimension = embeddings.shape[1] # 384
nlist = 100 # Number of clusters
quantizer = faiss.IndexFlatIP(dimension) # Inner product
index = faiss.IndexIVFFlat(quantizer, dimension, nlist)
# Train the index on the data
index.train(embeddings)
index.add(embeddings)
# Search
query = model.encode(["container orchestration"])
query = np.array(query).astype("float32")
faiss.normalize_L2(query)
index.nprobe = 10 # Search 10 nearest clusters (accuracy/speed trade-off)
distances, indices = index.search(query, k=5)
for i, (dist, idx) in enumerate(zip(distances[0], indices[0])):
print(f"{i+1}. [{dist:.3f}] {documents[idx]}")
# 1. [0.892] Kubernetes pod scheduling algorithms
# 2. [0.847] Docker container networking basics
# 3. [0.312] PostgreSQL index optimization guideElegir la solución adecuada
La elección depende de la escala, la infraestructura existente y los requisitos de las consultas.
const vectorDbComparison = {
pgvector: {
bestFor: 'Teams already using PostgreSQL, <5M vectors',
pros: [
'No new infrastructure',
'SQL filtering + vector search combined',
'ACID transactions with vector data',
],
cons: [
'Slower than dedicated vector DBs at scale',
'Limited to single-node performance',
],
},
pinecone: {
bestFor: 'Managed service, production RAG systems',
pros: [
'Fully managed, no ops',
'Fast at any scale',
'Metadata filtering built-in',
],
cons: [
'Vendor lock-in',
'Cost scales with vector count',
],
},
faiss: {
bestFor: 'In-memory search, batch processing, research',
pros: [
'Fastest query performance',
'No network latency',
'GPU acceleration available',
],
cons: [
'No persistence (manage storage yourself)',
'In-memory only — limited by RAM',
],
},
weaviate: {
bestFor: 'Self-hosted vector DB with rich features',
pros: [
'Built-in vectorization modules',
'Hybrid search (keyword + vector)',
'Multi-tenancy support',
],
cons: [
'Operations overhead',
'More complex than pgvector',
],
},
};# ❌ Using a dedicated vector database for 10k documents
# Over-engineered: pgvector handles this trivially
# Extra infrastructure, extra cost, extra complexity
# ✅ Choosing based on scale and requirements
# < 1M vectors + PostgreSQL already in stack → pgvector
# 1M-100M vectors + managed preference → Pinecone
# Batch processing + low latency → FAISS
# Self-hosted + hybrid search → WeaviateConclusiones clave
- La búsqueda vectorial encuentra significado, no palabras clave: convertir el texto en embeddings permite una similitud semántica que la búsqueda por palabras clave no puede lograr
- Usa índices ANN para escalar: la fuerza bruta es O(n) por consulta; HNSW ofrece un recall del 95-99% con una aceleración de 10 a 100 veces
- pgvector es el punto de partida más sencillo: si ya usas PostgreSQL y tienes menos de 5 millones de vectores, añade la extensión en lugar de sumar una base de datos nueva
- Cosine similarity es la métrica predeterminada para texto: mide el ángulo entre vectores sin tener en cuenta la magnitud, y la mayoría de los modelos de embeddings de texto están optimizados para ella
- Normaliza los vectores antes de indexarlos: los vectores sin normalizar producen puntuaciones de similitud inconsistentes; normalízalos una sola vez al insertarlos
- Elige en función de tu infraestructura existente: la mejor base de datos vectorial es la que se adapta a tu stack sin añadir complejidad operativa innecesaria


