Ir al contenido

Por qué tu búsqueda no devuelve nada — Y cómo la búsqueda vectorial de MongoDB lo soluciona

La búsqueda por palabras clave solo puede encontrar lo que está literalmente allí. Cuando los usuarios buscan 'laptop bag' y tus documentos dicen 'notebook carrying case', las expresiones regulares no ayudarán. La búsqueda vectorial entiende el significado — y MongoDB Atlas la soporta de forma nativa.

Durval Pereira
Durval Pereira
10 min

El problema de los resultados ocultos

Tu búsqueda funciona. O al menos, eso parece. Los usuarios escriben una consulta, los resultados aparecen, nadie se queja demasiado. Pero hay una clase de fallo que es casi invisible: los resultados que deberían haber aparecido pero no lo hicieron.

Considera un catálogo de productos para una tienda en línea. Un usuario escribe "laptop bag" en la barra de búsqueda. El sistema devuelve dos productos que contienen la frase "laptop bag" en su título o descripción. Parece correcto.

Pero hay una docena de otros productos relevantes en el catálogo. Un "notebook carrying case", una "padded sleeve for 15-inch computers", una "tech commuter backpack with device compartment". Ninguno de ellos contiene la cadena literal "laptop bag". Así que ninguno de ellos aparece.

El usuario ve dos resultados y asume que eso es todo lo que tienes. El sistema ocultó silenciosamente los productos más relevantes porque el mecanismo de búsqueda es estructuralmente incapaz de entender lo que el usuario quería decir.

Cómo funciona realmente la búsqueda por palabras clave

La mayoría de las implementaciones de búsqueda internas que he encontrado utilizan alguna forma de regex o coincidencia de subcadenas (substring matching). La cadena de consulta se escanea contra un conjunto de campos indexados — productName, description, category, specifications — y se devuelve cualquier documento que contenga esa secuencia exacta de caracteres.

db.products.find({
  $or: [
    { productName: { $regex: "laptop bag", $options: "i" } },
    { description: { $regex: "laptop bag", $options: "i" } },
    { category: { $regex: "laptop bag", $options: "i" } },
    { specifications: { $regex: "laptop bag", $options: "i" } },
  ]
})

Esto funciona cuando el lenguaje del usuario coincide exactamente con el lenguaje del documento. Buscar "laptop" encuentra documentos que dicen "laptop". Buscar "bag" también funciona porque es una subcadena de "bags".

Pero el enfoque es puramente léxico. No tiene concepto de significado.

Dónde falla

Los modos de fallo son sistemáticos, no casos excepcionales:

El usuario buscaEspera encontrarPor qué falla la búsqueda por palabras clave
"laptop bag"Fundas para portátiles, mochilas tecnológicasEl producto dice "estuche de transporte", no "bolso"
"winter jacket"Parkas, abrigos acolchados, chaquetas aislantesEl producto dice "ropa de abrigo térmica"
"kids tablet"Dispositivos educativos, tablets de aprendizajeEl producto dice "pantalla interactiva para niños"
"gift for a runner"Zapatillas de correr, monitores de actividad física, equipo de hidrataciónNingún campo contiene el concepto de "regalo para un corredor"
"something for a road trip"Neveras portátiles, cargadores de coche, almohadas de viajeLas consultas conceptuales no tienen una coincidencia literal

Ninguna cantidad de indexación de campos puede anticipar todas las formas en que un usuario podría expresar su intención. La limitación no está en la implementación, sino en el paradigma.

La tirita de la desnormalización

Una reacción común es desnormalizar: extraer datos relacionados de otras colecciones al documento buscable. Por ejemplo, tu catálogo tiene una colección products con metadatos básicos, pero las descripciones ricas en palabras clave viven en una colección productDetails separada, vinculada por SKU.

// Antes: documento de producto ligero con referencias
{
  "_id": "prod_2241",
  "productName": "TechShield Commuter Pack",
  "brand": "TechShield",
  "skus": ["TS-441", "TS-442", "TS-443"]
}

// Después: enriquecido con metadatos de detalle
{
  "_id": "prod_2241",
  "productName": "TechShield Commuter Pack",
  "brand": "TechShield",
  "skus": ["TS-441", "TS-442", "TS-443"],
  "variantNames": [
    "TechShield Padded Laptop Bag 15-inch, Black",
    "TechShield Padded Laptop Bag 15-inch, Navy",
    "TechShield Padded Laptop Sleeve 13-inch, Gray"
  ]
}

Ahora, una búsqueda de "laptop bag" coincidirá con este producto porque la cadena aparece en variantNames. Esto funciona como una solución táctica. Pero introduce una desventaja: cada documento de producto debe actualizarse cada vez que cambian los datos de las variantes, la redundancia debe mantenerse con el tiempo y sigues intentando ponerte al día con el vocabulario del usuario.

Un usuario que busca "backpack for my MacBook" seguirá sin coincidir con "Padded Laptop Bag" a menos que sigas expandiendo los campos desnormalizados. Estás parcheando un sistema fundamentalmente léxico, sinónimo a sinónimo.

Búsqueda vectorial: coincidencia por significado

La búsqueda vectorial adopta un enfoque completamente diferente. En lugar de comparar secuencias de caracteres, compara el significado.

La idea central: convertir texto en representaciones numéricas de alta dimensión llamadas embeddings. Estos son generados por modelos de machine learning (Voyage AI, text-embedding-3-small de OpenAI, modelos de código abierto como nomic-embed-text) entrenados en enormes corpus de texto. Los modelos aprenden relaciones semánticas entre palabras y conceptos.

En el espacio de embeddings:

  • Las palabras con significados similares se agrupan cerca (pequeña distancia vectorial)
  • Las palabras con significados diferentes están muy separadas (gran distancia vectorial)
"laptop bag"     → [0.021, -0.187, 0.443, 0.078, ..., 0.312]  (768 dimensions)
"notebook sleeve" → [0.019, -0.174, 0.451, 0.065, ..., 0.298]  (nearby)
"refrigerator"   → [-0.342, 0.501, -0.113, 0.227, ..., -0.089] (distant)

Cuando un usuario busca "laptop bag", la consulta se convierte en un embedding y se compara con los embeddings precalculados de todos los documentos. Los resultados se clasifican por similitud de coseno. El "notebook carrying case" aparece — no por una coincidencia de cadena, sino porque el modelo entiende que los estuches de transporte y los bolsos para portátiles habitan en el mismo vecindario semántico.

MongoDB Atlas Vector Search: implementación

MongoDB Atlas soporta la búsqueda vectorial de forma nativa. No hay una infraestructura de búsqueda separada, ni un sidecar de Elasticsearch, ni una pipeline de sincronización de datos. Se ejecuta en tu clúster existente.

Paso 1: Generar embeddings

Para cada documento, concatena los campos semánticamente significativos y pásalos a través de un modelo de embedding:

def build_embedding_text(product):
    parts = [
        product.get("productName", ""),
        product.get("brand", ""),
        product.get("description", ""),
        product.get("category", ""),
        product.get("specifications", ""),
    ]
    return " | ".join(part for part in parts if part)

Para la mochila de viaje, esto produce:

"TechShield Commuter Pack | TechShield | Durable backpack with padded
device compartment and organizer pockets | Bags & Accessories | Water-
resistant nylon, fits up to 15-inch devices"

El embedding resultante captura el concepto — "un bolso para llevar dispositivos tecnológicos, formato mochila, acolchado protector". Almacénalo como un nuevo campo en el documento:

{
  "_id": "prod_2241",
  "productName": "TechShield Commuter Pack",
  "embedding": [0.019, -0.174, 0.451, 0.065, "...", 0.298]
}

Paso 2: Crear un índice de búsqueda vectorial

Define el índice en MongoDB Atlas:

{
  "type": "vectorSearch",
  "fields": [
    {
      "path": "embedding",
      "type": "vector",
      "numDimensions": 768,
      "similarity": "cosine"
    }
  ]
}

El numDimensions debe coincidir con el tamaño de salida de tu modelo de embedding. La similitud de coseno es la elección estándar para los embeddings de texto.

Paso 3: Consultar con $vectorSearch

En el momento de la búsqueda, incrusta la consulta del usuario con el mismo modelo y pásala a la etapa de agregación $vectorSearch:

db.products.aggregate([
  {
    $vectorSearch: {
      index: "product_vector_index",
      path: "embedding",
      queryVector: embedQuery("laptop bag"),
      numCandidates: 100,
      limit: 20
    }
  },
  {
    $project: {
      productName: 1,
      brand: 1,
      category: 1,
      score: { $meta: "vectorSearchScore" }
    }
  }
])

Una búsqueda de "laptop bag" ahora devuelve:

ClasificaciónProductoPuntuación
1TechShield Commuter Pack0.92
2SlimGuard Notebook Sleeve 15"0.89
3UrbanGear Padded Carrying Case0.86
4ProTravel Tech Backpack0.81

El producto TechShield aparece — no por una coincidencia de cadena, sino porque el modelo entiende que una "mochila de viaje con compartimento acolchado para dispositivos" es semánticamente lo que alguien quiere decir cuando busca "laptop bag".

Por qué esto es fundamentalmente mejor

DimensiónBúsqueda por palabras clave / RegexBúsqueda vectorial
Mecanismo de coincidenciaCoincidencia exacta de subcadenaSimilitud semántica
Maneja sinónimosNo ("bag" ≠ "case" ≠ "sleeve")Sí (entiende la equivalencia)
Maneja la parafraseoNoSí ("algo para llevar mi portátil" → bolsos)
Requiere desnormalizaciónSí — debe copiar datos en campos buscablesNo — el significado se captura en el embedding
Carga de mantenimientoAlta — mantener campos redundantes sincronizadosBaja — re-embed solo cuando el texto fuente cambia
Tolerancia a errores tipográficosNo ("laptpo bag" falla)Parcial (los embeddings son robustos a variaciones menores)
Consultas conceptualesImposibleSí ("equipo para viajeros tecnológicos" muestra productos relevantes)
Calidad de clasificaciónBinaria (coincidencia o no coincidencia)Puntuación de relevancia continua

La ventaja más significativa es la última. La búsqueda por palabras clave es binaria: un documento contiene la cadena o no. La búsqueda vectorial produce una puntuación de relevancia, lo que significa que los resultados pueden clasificarse por cuán estrechamente coinciden con la intención del usuario.

Búsqueda híbrida: la elección pragmática

La búsqueda vectorial pura tiene una debilidad: las coincidencias exactas. Si un usuario escribe el nombre preciso del producto — "TechShield Commuter Pack 15-inch Black" — la búsqueda por palabras clave lo encontrará inmediatamente, mientras que la búsqueda vectorial podría clasificarlo alto, pero no necesariamente primero.

MongoDB Atlas soporta la búsqueda híbrida — combinando puntuaciones de búsqueda de texto completo con puntuaciones de similitud vectorial utilizando Reciprocal Rank Fusion (RRF):

db.products.aggregate([
  {
    $vectorSearch: {
      index: "product_vector_index",
      path: "embedding",
      queryVector: embedQuery("laptop bag"),
      numCandidates: 100,
      limit: 50
    }
  },
  {
    $unionWith: {
      coll: "products",
      pipeline: [
        {
          $search: {
            index: "product_text_index",
            text: {
              query: "laptop bag",
              path: ["productName", "brand", "description", "category"]
            }
          }
        },
        { $limit: 50 }
      ]
    }
  }
  // Reciprocal Rank Fusion to merge and re-rank results
])

Esto te ofrece lo mejor de ambos mundos:

  • Las búsquedas exactas de nombres de productos se manejan de forma nítida mediante la coincidencia de palabras clave.
  • Las consultas exploratorias o conceptuales ("algo impermeable para hacer senderismo con mi portátil") se manejan mediante la similitud vectorial.
  • Ambas señales se fusionan en un único conjunto de resultados clasificados.

Enriquecimiento de embeddings para mejores resultados

Si bien la búsqueda vectorial resuelve el problema de los resultados ocultos sin desnormalización, puedes mejorar aún más la calidad incluyendo datos relacionados en el texto fuente del embedding. Esto es un primo más ligero del enfoque de desnormalización — en lugar de reestructurar documentos para el escaneo de palabras clave, añades contexto al texto que se incrusta:

def build_enriched_embedding_text(product, variant_names):
    base = build_embedding_text(product)
    variants = " | ".join(variant_names)
    return f"{base} | Variants: {variants}"

Esto proporciona al modelo de embedding un contexto más rico, fortaleciendo la señal semántica para los términos que aparecen en los detalles de las variantes pero no en la lista principal del producto. La estructura del documento permanece inalterada — solo el embedding se beneficia.

Cuándo adoptar esto

Si tu búsqueda está respaldada por regex o consultas $text básicas en MongoDB, el camino a seguir está claro:

  1. Inmediato: Audita tus consultas de búsqueda de mayor tráfico. Identifica cuáles devuelven menos resultados de los que deberían. Esto cuantifica el problema de los resultados ocultos en tu sistema.

  2. A corto plazo: Si la adopción de la búsqueda vectorial necesita tiempo, considera la desnormalización dirigida para las consultas con peor rendimiento. Esto te da tiempo sin cambios arquitectónicos.

  3. A medio plazo: Implementa MongoDB Atlas Vector Search. Genera embeddings a partir de los metadatos de tus documentos, crea el índice y valida con pruebas A/B contra la búsqueda actual.

  4. A largo plazo: Adopta la búsqueda híbrida combinando señales de palabras clave y vectoriales. Extiende a superficies adicionales — descubrimiento de productos, recomendaciones, búsqueda conversacional.

El resultado es una experiencia de búsqueda donde los usuarios encuentran lo que buscan, incluso cuando no utilizan las palabras exactas que aparecen en tus datos. Eso no es un lujo. Para cualquier sistema donde la búsqueda impulse el engagement o los ingresos, es la diferencia entre un producto que se siente inteligente y uno que se siente roto.


Este artículo forma parte de una serie sobre bases de datos e infraestructura de datos.

Etiquetasmongodbvector-searchsearchsemantic-searchembeddings