DanLevy.net

Búsqueda Semántica Vectorial y Otros Temas para Ganar Amigos y Enamorados

El panorama completo de búsqueda: exacta, difusa, semántica, híbrida — y cuándo combinarlas todas.

La búsqueda no es una sola cosa, y la búsqueda semántica no reemplaza al resto.

“Encontrar el usuario con email dan@example.com” y “encuéntrame artículos sobre depuración para ingenieros novatos” se describen ambos como búsqueda, pero tienen muy poco en común como problemas de ingeniería. El primero tiene una respuesta correcta y una búsqueda indexada en O(log n). El segundo no tiene respuesta correcta — solo relevancia — y requiere comprender lenguaje, intención y significado.

Los ingenieros más persuasivos a la hora de tomar decisiones sobre búsqueda — los que ganan los argumentos y construyen el sistema correcto — entienden todo el panorama. Saben qué herramienta usar y por qué, y pueden explicarlo con claridad.

Este artículo cubre la capa semántica: qué hace realmente la búsqueda vectorial, cuándo gana, y dónde debería apartarse. La versión útil no es “incrusta todo”. Es saber cuándo los vectores pertenecen junto a la búsqueda léxica, difusa y de coincidencia exacta en una arquitectura híbrida.

La mitad léxica y difusa del panorama — tsvector, pg_trgm, pg_search — está en Postgres Text Searching Guide 2026.

Términos en Resumen

Embedding (Incrustación) — Una lista densa de números de punto flotante producida por un modelo, que representa un fragmento de texto (o imagen, audio, etc.) como un punto en un espacio de alta dimensionalidad. El contenido semánticamente relacionado aterriza cerca; el contenido no relacionado aterriza lejos.

Búsqueda léxica — Búsqueda basada en coincidencia exacta de palabras y tokens. Rápida, determinista y correcta para términos conocidos. No entiende sinónimos, paráfrasis ni equivalentes entre idiomas.

Búsqueda semántica — Búsqueda basada en significado en lugar de tokens. Una consulta como “cómo manejo los timeouts” puede coincidir con un documento titulado “configuración de políticas de reintento” sin palabras compartidas, porque sus embeddings están geométricamente cerca.

Vector — Una lista de números. En contextos de búsqueda, la salida de un modelo de embedding. La “búsqueda vectorial” encuentra los vectores más cercanos a un vector consulta por distancia geométrica.

FTS (Full-Text Search) — La búsqueda léxica integrada de Postgres, impulsada por tsvector / tsquery. Tokeniza, lematiza e indexa texto para consultas por palabra clave. Fuerte para prosa y búsqueda de términos exactos; ciega al significado.

BM25 — Un algoritmo de ranking para búsqueda léxica (usado por Elasticsearch, Qdrant y otros). Puntúa resultados por frecuencia de términos ponderada por lo raro que es el término en el corpus. Mejor que la coincidencia de palabras clave pura; sigue siendo léxica.

HNSW (Hierarchical Navigable Small World) — El índice estándar de vecino más cercano aproximado para búsqueda vectorial. Construye un grafo de proximidad en capas para consultas de similitud rápidas y de alto recall. pgvector, Qdrant, Weaviate y la mayoría lo usan.

RRF (Reciprocal Rank Fusion) — Un algoritmo para fusionar listas de resultados ranked de múltiples sistemas de recuperación. Usa solo la posición de ranking — sin normalización de puntajes. Un resultado que aparece alto en ambas listas (FTS y vectorial) obtiene un puntaje combinado más fuerte que uno que domina solo una.

Qué Hace Realmente la Búsqueda Semántica

Los embeddings vectoriales convierten texto (o imágenes, audio, etc.) en una lista de números — un punto en un espacio de alta dimensionalidad. Un modelo de embedding se entrena para que el texto semánticamente relacionado aterrice cerca en ese espacio. “Perro” y “canino” quedan cerca. “Correr un maratón” y “correr un script de Python” quedan lejos a pesar de compartir una palabra.

La búsqueda de similitud en ese espacio encuentra documentos cuyo significado está más cerca del significado de la consulta, sin importar la superposición exacta de palabras.

Esto significa:

La búsqueda léxica (tsvector, pg_trgm) no puede hacer nada de esto. Opera sobre palabras y caracteres, no sobre significado. Las herramientas no son intercambiables — resuelven problemas diferentes.

Cuándo pgvector Gana

Construir RAG. Retrieval-Augmented Generation (Generación Aumentada con Recuperación) recupera los fragmentos de documento cuyo significado está más cerca de la pregunta del usuario, y luego los pasa a un modelo de lenguaje como contexto. Este paso de recuperación es una operación vectorial. FTS perderá paráfrasis, sinónimos y coincidencias conceptuales que un fragmento relevante podría expresar de otra forma. La ventaja de pgvector sobre un almacén vectorial independiente: se ejecuta dentro de tu instancia de Postgres existente — sin servicio separado que desplegar, operar o sincronizar.

Los usuarios describen lo que quieren, no qué buscar. “Artículos sobre construir confianza como nuevo manager” no tiene palabras clave que aparezcan de forma fiable en los posts relevantes. “Un framework ligero para manejar efectos secundarios” puede no usar esas palabras exactas en la documentación. La búsqueda vectorial coincide con la intención, no con la ortografía.

Encontrar elementos similares. Productos relacionados, tickets de soporte similares, informes de bugs duplicados, artículos que también te pueden gustar. “Encontrar issues similares a este” es una búsqueda de vecino más cercano — incrustar el elemento, encontrar sus vecinos geométricos. Una advertencia importante: la búsqueda vectorial siempre devuelve resultados, incluso cuando nada es genuinamente similar. Para casos de deduplicación y recomendación, filtra por un umbral mínimo de similitud (por ejemplo, similitud coseno ≥ 0.80) para evitar mostrar coincidencias de baja confianza como si fueran significativas.

Deduplicación semántica. Antes de indexar contenido para RAG o búsqueda, a menudo necesitas identificar casi-duplicados en el corpus — artículos revisados varias veces, tickets de soporte presentados dos veces, entradas de base de conocimiento que se solapan significativamente. Incrusta los documentos y filtra por similitud coseno con umbral para marcar o fusionar casi-duplicados antes de que contaminen tu índice. Esto evita que la recuperación devuelva múltiples fragmentos casi idénticos y diluya la ventana de contexto.

Búsqueda multilingüe. Los modelos de embedding multilingües mapean contenido semánticamente equivalente entre idiomas en vectores cercanos. Una consulta en español para “perder peso” puede coincidir con un artículo en inglés sobre “sustainable weight loss habits” — sin tokens compartidos, mismo significado subyacente. FTS requiere configuración de diccionario por idioma y maneja mal las consultas entre idiomas. pg_trgm es independiente del idioma pero ortográfico, no semántico.

Configurar pgvector

Desde la instalación de la extensión hasta la consulta de similitud, la configuración son unas pocas sentencias SQL:

CREATE EXTENSION IF NOT EXISTS vector;
ALTER TABLE documents ADD COLUMN embedding vector(1536);
-- HNSW suele ser el primer índice a probar para datasets de tamaño moderado
CREATE INDEX documents_embedding_idx
ON documents USING hnsw (embedding vector_cosine_ops);
-- Consulta de búsqueda semántica
SELECT id, title, 1 - (embedding <=> $1::vector) AS similarity
FROM documents
ORDER BY embedding <=> $1::vector
LIMIT 10;

<=> es distancia coseno. 1 - cosine_distance da la similitud coseno (1.0 = idéntico, 0.0 = ortogonal). Para ivfflat (la alternativa más antigua, más rápida de construir), usa lists = sqrt(row_count) como punto de partida.

Qué pgvector No Maneja Bien

Búsqueda Híbrida: El Caso de Ambas

La documentación técnica es el ejemplo más claro donde ninguna herramienta basta sola.

Los usuarios que buscan “cómo configurar timeouts” necesitan coincidencia conceptual: un artículo titulado “Configuración de políticas de reintento y límites de conexión” no tiene palabras clave superpuestas pero es exactamente lo que necesitan.

Los mismos usuarios también buscan withRetry(), ECONNRESET y ERR_SOCKET_TIMEOUT. Estas cadenas exactas deben aparecer — la coincidencia semántica puede no encontrarlas de forma fiable, y un falso positivo (conceptualmente similar pero no la API correcta) es activamente engañoso.

La búsqueda vectorial maneja las consultas conceptuales. FTS maneja los términos exactos. Ninguna maneja bien ambas por sí sola.

La solución es búsqueda híbrida: ejecutar ambas y fusionar los resultados.

Reciprocal Rank Fusion

Reciprocal Rank Fusion (RRF) es el algoritmo estándar para combinar listas ranked de diferentes sistemas de recuperación. No requiere normalizar puntajes entre sistemas — solo usa posiciones de ranking. Un resultado que aparece alto en ambas listas obtiene un puntaje combinado más fuerte que uno que domina solo una.

WITH fts_results AS (
SELECT id,
ROW_NUMBER() OVER (ORDER BY ts_rank(search_vector, query) DESC) AS rank
FROM documents, to_tsquery('english', $1) query
WHERE search_vector @@ query
LIMIT 50
),
vector_results AS (
SELECT id,
ROW_NUMBER() OVER (ORDER BY embedding <=> $2::vector) AS rank
FROM documents
ORDER BY embedding <=> $2::vector
LIMIT 50
),
rrf AS (
SELECT
COALESCE(f.id, v.id) AS id,
COALESCE(1.0 / (60 + f.rank), 0) +
COALESCE(1.0 / (60 + v.rank), 0) AS rrf_score
FROM fts_results f
FULL OUTER JOIN vector_results v ON f.id = v.id
)
SELECT d.id, d.title, rrf.rrf_score
FROM rrf
JOIN documents d ON d.id = rrf.id
ORDER BY rrf_score DESC
LIMIT 10;

El 60 en el denominador es la constante RRF. Valores más altos suavizan las diferencias de posición; valores más bajos las amplifican. El valor por defecto de 60 funciona bien para la mayoría de los tipos de contenido.

RRF evita el problema más difícil de normalizar ts_rank (un puntaje de frecuencia logarítmica) contra distancia coseno (una medida geométrica). No son comparables. RRF solo pregunta: “¿qué tan alto apareció este resultado en cada lista?”

Búsqueda Híbrida con Trigramas También

Para búsqueda orientada al usuario sobre contenido mixto — donde los usuarios pueden buscar un nombre de persona, un concepto o un término exacto en la misma sesión — la fusión de tres capas maneja todos los casos:

WITH trgm_results AS (
SELECT id,
ROW_NUMBER() OVER (ORDER BY similarity(title, $1) DESC) AS rank
FROM documents
WHERE title % $1
LIMIT 50
),
fts_results AS (
SELECT id,
ROW_NUMBER() OVER (ORDER BY ts_rank(search_vector, to_tsquery('english', $1)) DESC) AS rank
FROM documents
WHERE search_vector @@ to_tsquery('english', $1)
LIMIT 50
),
vector_results AS (
SELECT id,
ROW_NUMBER() OVER (ORDER BY embedding <=> $2::vector) AS rank
FROM documents
ORDER BY embedding <=> $2::vector
LIMIT 50
),
rrf AS (
SELECT
COALESCE(t.id, f.id, v.id) AS id,
COALESCE(1.0 / (60 + t.rank), 0) +
COALESCE(1.0 / (60 + f.rank), 0) +
COALESCE(1.0 / (60 + v.rank), 0) AS rrf_score
FROM trgm_results t
FULL OUTER JOIN fts_results f ON t.id = f.id
FULL OUTER JOIN vector_results v ON COALESCE(t.id, f.id) = v.id
)
SELECT d.id, d.title, rrf.rrf_score
FROM rrf
JOIN documents d ON d.id = rrf.id
ORDER BY rrf_score DESC
LIMIT 10;

Esto maneja: coincidencias difusas de nombres (trigramas), coincidencias exactas de palabras clave (FTS), y consultas conceptuales (vectorial). Una única caja de búsqueda puede servir los tres tipos de intención del usuario.

Arquitecturas Híbridas Multicapa

Las aplicaciones reales rara vez tienen una única superficie de búsqueda. Tienen múltiples, cada una con una necesidad diferente:

SuperficieQué consulta el usuarioCapas recomendadas
Búsqueda de blog / documentaciónPalabras clave + conceptosFTS + pgvector (RRF)
Búsqueda de nombres de usuario/clienteNombres con errataspg_trgm
Búsqueda de productosNombres, descripciones, “similar a”pg_trgm + FTS + pgvector
Deduplicación de tickets de soporte”Issues similares a este”Solo pgvector
Búsqueda interna de SKU/pedidoIdentificadores exactosÍndice B-tree
RAG sobre base de conocimiento grandePreguntas en lenguaje naturalpgvector (documentos fragmentados)
E-commerce “también te puede gustar”Similitud comportamental + semánticapgvector
AutocompletadoPrefijo, tolerante a errataspg_trgm

Estos no son hipotéticos. La mayoría de aplicaciones con mucho contenido necesitan al menos dos superficies de búsqueda distintas con formas de consulta diferentes. La tentación es elegir un enfoque y usarlo en todas partes — normalmente búsqueda vectorial ahora, porque es la opción de moda. Eso lleva a embeddings caros para problemas donde un índice de trigramas habría sido más rápido, más barato y más correcto.

La Regla de Oro

Añade una capa cuando aparece un modo de fallo que la capa actual no puede arreglar:

Si Necesitas un Almacén Vectorial Dedicado

pgvector maneja mucha búsqueda de aplicación antes de que necesites otra base de datos. El límite aproximado depende del conteo de vectores, configuración de índices, tasa de escritura, filtros, hardware y concurrencia, así que trata cualquier regla de “menos de 10M vectores” como una suposición inicial a benchmarkear, no como un límite del producto. Cuando realmente lo superas — concurrencia muy alta, requisitos de latencia p99 muy bajos, miles de millones de vectores, o necesidades serias de aislamiento multi-tenant — el panorama de bases de datos vectoriales dedicadas es amplio y vale la pena entenderlo.

Qué Significan Realmente las Columnas de la Matriz

Búsqueda híbrida significa que la búsqueda de palabras clave BM25 y la similitud vectorial se ejecutan en una sola consulta, fusionadas mediante RRF. Sin ella, o eliges un modo de búsqueda o fusionas dos consultas tú mismo.

Vectores dispersos van más allá de BM25. Un vector SPLADE disperso tiene ~30.000 dimensiones (una por término de vocabulario), ~98% de ceros. Las posiciones no nulas te dicen qué términos importan y cuánto. Una consulta para “dogs” también pondera “canine” y “pet” — precisión a nivel BM25 más expansión de términos dentro de un índice vectorial. Si esta columna es falsa, necesitas una capa FTS separada para consultas de términos exactos.

# SPLADE: ~30.000 dims, ~60 no nulos — solo dispara el vocabulario relevante
def encode_splade(text: str) -> dict:
tokens = tokenizer(text, return_tensors="pt", truncation=True, max_length=512)
with torch.no_grad():
output = model(**tokens)
vec = torch.log1p(torch.relu(output.logits)).max(dim=1).values.squeeze()
return {"indices": vec.nonzero().squeeze().tolist(), "values": vec[vec != 0].tolist()}

SQL / similar a SQL trata realmente sobre filtrado. La búsqueda vectorial sin filtrado es una demo. Todavía necesitas alcance de tenant, rangos de fechas, permisos y filtros de categoría. El SQL completo (pgvector, LanceDB) expresa esto junto a tus joins existentes. Las bases de datos de propósito propio usan objetos de filtro JSON (Qdrant, Pinecone), un DSL de consultas (Elasticsearch, Milvus), o GraphQL (Weaviate). Funcionan; SQL se vuelve más atractivo a medida que la lógica de filtrado se complica.

-- pgvector: la similitud vectorial es solo otra expresión
SELECT id, title, 1 - (embedding <=> $1) AS score
FROM documents
WHERE tenant_id = $2
AND category = ANY($3::text[])
AND created_at > NOW() - INTERVAL '90 days'
ORDER BY embedding <=> $1
LIMIT 10;
# Qdrant: filtro equivalente como objeto Python — mismo resultado, más ceremonia
results = client.query_points(
collection_name="documents", query=query_embedding,
query_filter=models.Filter(must=[
models.FieldCondition(key="tenant_id", match=models.MatchValue(value=tenant_id)),
models.FieldCondition(key="category", match=models.MatchAny(any=categories)),
models.FieldCondition(key="created_at", range=models.DatetimeRange(gte=cutoff)),
]),
limit=10,
)

Multimodal nativo significa que la base de datos incluye modelos de embedding para contenido no textual. Le pasas una URL de imagen cruda; ella se encarga de la vectorización. La mayoría de las bases de datos son agnósticas al embedding — tú eres dueño del pipeline de embedding. Marqo y Weaviate (vía módulos CLIP/ImageBind) cierran este bucle.

# Marqo: POST imágenes crudas, consulta con texto — sin paso de embedding externo
mq.index("products").add_documents(
[{"id": "shoe-001", "image": "https://cdn.example.com/shoes/001.jpg"}],
tensor_fields=["image"]
)
results = mq.index("products").search(q="lightweight shoes for summer")
# Devuelve shoe-001 a pesar de cero superposición de palabras clave — CLIP maneja la coincidencia cross-modal

Índice basado en disco es una palanca de coste. Los índices HNSW en RAM pueden requerir varios GB de RAM por millón de vectores de 1536 dimensiones una vez contados los vectores crudos, la sobrecarga del grafo y los metadatos. Las alternativas nativas en disco (Milvus DiskANN, Elasticsearch DiskBBQ, el formato Lance de LanceDB, el tier de almacenamiento de objetos de Turbopuffer) suelen intercambiar algo de latencia de consulta por menor coste de infraestructura. Para cargas de trabajo RAG donde la latencia del modelo ya domina, ese intercambio suele merecer la pena benchmarkear.

Dimensiones máximas es una migración oculta en tu arquitectura. text-embedding-3-large usa 3072 dims, Jina v3 puede emitir embeddings más grandes, y los modelos de investigación siguen subiendo. Algunos servicios gestionados publican límites duros de dimensión; otros documentan límites altos o ningún límite práctico para modelos de embedding típicos. Revisa la documentación actual antes de comprometerte. Elige algo con margen; migrar un índice vectorial porque alcanzaste un techo de dimensiones es un sprint doloroso.

Verificado por última vez contra la documentación pública y las páginas de producto el 8 de mayo de 2026. Trata la tabla de abajo como una ayuda de decisión, no como un sustituto de verificar límites actuales, precios y flags de función de servicios gestionados.

El Panorama

Base de datosDespliegueLicenciaBúsqueda HíbridaVectores DispersosSQL / similar a SQLMultimodalÍndice en DiscoDims MáxPunto Fuerte
pgvectorSelf-host / gestionado (Supabase, Neon, RDS)OSS (PostgreSQL)Manual (RRF vía SQL)✅ SQL completo✅ HNSW en disco16.000 almacenamiento; 2.000 indexado vectorYa en Postgres; conteo moderado de vectores
QdrantSelf-host / CloudApache 2.0✅ BM25 nativo✅ Soporte maduro❌ (REST/gRPC)65.535Consultas filtradas a escala; metadatos complejos
WeaviateSelf-host / CloudBSD 3✅ BM25 nativo + RRF❌ (GraphQL / gRPC)✅ vía módulos65.535Patrones de acceso GraphQL; vectorización integrada
PineconeSolo CloudPropietaria✅ (añadido 2024)✅ (serverless)20.000Simplicidad gestionada; sin equipo de ops
Milvus / ZillizSelf-host / Cloud (Zilliz)Apache 2.0✅ Nativo✅ Similar a SQL (Milvus Query Language)✅ DiskANN32.768Escala de miles de millones; enterprise on-prem
ChromaEmbedded / self-hostApache 2.065.535Solo dev local y prototipado
LanceDBEmbedded / CloudApache 2.0✅ SQL vía DataFusion✅ Nativo✅ (formato Lance)IlimitadoEdge / serverless; lakehouse multimodal
OramaEmbedded / CloudApache 2.0✅ Texto completo + vectorVariableApps JS/edge; búsqueda ligera de sitio/app
TurbopufferSolo Cloud (serverless)Propietaria✅ BM25 + vector✅ (almacenamiento de objetos)16.000SaaS multi-tenant; millones de namespaces
ElasticsearchSelf-host / Elastic CloudSSPL / AGPLv3✅ RRF + ELSER disperso✅ (ELSER)✅ Query DSL✅ DiskBBQ4.096Ya en stack Elastic; búsqueda híbrida enterprise
OpenSearchSelf-host / AWS gestionadoApache 2.0✅ RRF + Neural Search✅ Query DSL✅ FAISS + HNSW16.000Nativo AWS; alternativa open-source a Elastic
VespaSelf-host / CloudApache 2.0✅ Nativo✅ Tensores / ranking léxico✅ YQL✅ TensoresEfectivamente ilimitadoSistemas de búsqueda + ranking + recomendación
ClickHouseSelf-host / CloudApache 2.0Manual✅ SQL completo✅ Columnar + HNSWVariableAnalítica/logs con búsqueda vectorial junto a OLAP
MongoDB AtlasCloud / self-hostSSPL✅ Integrado✅ MQL + agregación✅ HNSW8.192Ya en MongoDB; documento + vector en uno
Redis (VSS)Self-host / Redis CloudRSALv2 / SSPL✅ (RediSearch)❌ Solo RAM32.768Ultra-baja latencia; búsqueda vectorial en capa de caché
MarqoCloud / self-hostApache 2.0✅ Enfoque nativoVariableMultimodal end-to-end: imagen + texto + vídeo

Unas Cuantas Cosas que No Caben en la Tabla

Multi-tenencia de Turbopuffer está construida alrededor de conteos muy altos de namespaces. Su posicionamiento público y casos de cliente enfatizan cargas de trabajo como el corpus grande y de alto uso de namespaces de Notion. Si cada usuario u organización necesita búsqueda vectorial aislada, esa arquitectura puede cambiar la economía, pero sigue benchmarkando tu propia forma de tenencia.

Modo embedded de LanceDB es lo más cercano a “SQLite para búsqueda vectorial.” Se ejecuta en proceso, no requiere servidor, y funciona en Lambda, Cloudflare Workers y entornos edge. El formato columnar Lance hace que la operación embedded sea práctica a escala real.

Chroma es más fuerte en dev/test y despliegues de app pequeños. Si apuntas a corpus muy grandes, HA, operación pesada en disco, o búsqueda híbrida de primera clase, evalúa un almacén orientado a producción antes de promover el prototipo a infraestructura.

Vespa es a lo que acudes cuando la recuperación es solo la mitad del producto. Combina recuperación léxica, búsqueda de vecino más cercano, tensores, expresiones de ranking, agrupamiento y serving online. Ese poder es real, pero también lo es la complejidad operativa y de modelado. Encaja más con equipos de búsqueda/recomendación que con “añadir búsqueda semántica a mi app CRUD.”

ClickHouse pertenece a la conversación cuando la búsqueda está adjunta a analytics. Si tu fuente de verdad son eventos, logs, traces o métricas, ClickHouse mantiene distancia vectorial, filtrado, agregación e indexación de texto completo seria en un único motor SQL. No es una base de datos vectorial de propósito propio, pero suele ser la respuesta aburrida-correcta para recuperación analítica.

Los vectores dispersos son cómo obtienes coincidencia de palabras clave de calidad BM25 dentro de un índice vectorial — sin ejecutar un motor de texto completo separado. Qdrant y Elasticsearch tienen implementaciones especialmente maduras aquí. Si la búsqueda híbrida es crítica y una arquitectura de dos sistemas es un deal-breaker, el soporte de vectores dispersos es lo que hay que buscar.

Elegir Cuando Has Superado pgvector

La Única Cosa que No Debes Hacer

No uses búsqueda vectorial como búsqueda difusa de texto para cosas que tienen respuestas correctas.

“Encuéntrame el usuario con email dan@example.com” no es un problema de búsqueda vectorial. “Encuentra la orden con ID ORD-12345” tampoco lo es. Incrustar ORD-12345 y buscar por similitud coseno devolverá algo — pero puede ser incorrecto. Un identificador tiene una respuesta correcta. Una coincidencia aproximada sobre un identificador es un bug.

La búsqueda vectorial devuelve lo más similar en tu dataset, incluso cuando nada es realmente relevante. No sabe cuándo no existe una buena respuesta. Eso está bien para documentos relacionados. Es un problema serio para búsqueda exacta de registros, donde una respuesta incorrecta pero segura es peor que un resultado vacío.

Lo mismo aplica en la otra dirección: no uses FTS para consultas donde el usuario está describiendo un concepto. “artículos sobre tomar decisiones difíciles bajo incertidumbre” no contiene palabras clave fiables. FTS devolverá ruido o nada. Usa la herramienta correcta para la forma de consulta.

La Imagen Completa

La mayoría de sistemas de búsqueda en producción necesitan más de una capa:

Estos no son herramientas competidoras. Son complementarias. Un sistema de búsqueda bien construido elige la capa correcta para cada forma de consulta — y cuando las formas de consulta se solapan, ejecuta múltiples capas y fusiona los resultados.

Los equipos que construyen buenas funcionalidades de búsqueda entienden todo el stack. Los que no, eligen una base de datos vectorial, incrustan todo, y se preguntan por qué las búsquedas exactas a veces devuelven el registro equivocado.