¿Cómo funciona la búsqueda de texto completo por dentro (índices invertidos) y cuándo se usa Elasticsearch en vez de la búsqueda de texto de Postgres?
Un LIKE '%term%' recorre cada fila cada vez; un índice invertido voltea la pregunta de 'esta fila contiene la palabra' a 'a qué filas apunta esta palabra,' respondida en microsegundos.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Cómo funciona la búsqueda de texto completo por dentro (índices invertidos) y cuándo se usa Elasticsearch en vez de la búsqueda de texto de Postgres?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Por qué LIKE no escala para búsqueda
WHERE description LIKE '%mouse inalámbrico%' tiene que inspeccionar el texto de cada fila, porque un índice B-tree (el tipo que construye un CREATE INDEX normal) está ordenado por el valor completo y solo ayuda con coincidencias de prefijo (LIKE 'inalámbrico%') — un comodín al inicio lo anula por completo, así que la base de datos recurre a un escaneo secuencial completo del texto de cada fila. Eso está bien para unos pocos miles de filas; con unos pocos millones es una consulta de varios segundos, y se vuelve más lenta en proporción directa al tamaño de la tabla, para siempre.
#El índice invertido: voltear la dirección de la búsqueda
Los motores de búsqueda de texto completo resuelven esto construyendo un índice invertido — invertido respecto a la dirección "fila → palabras que contiene" en la que naturalmente pensarías; en cambio mapea "palabra → qué filas la contienen":
Documentos:doc1: "mouse inalámbrico para laptop"doc2: "teclado inalámbrico y combo de mouse"doc3: "soporte de laptop de aluminio"Índice invertido (término → lista de doc ids):inalambrico -> [doc1, doc2]mouse -> [doc1, doc2]laptop -> [doc1, doc3]teclado -> [doc2]combo -> [doc2]soporte -> [doc3]aluminio -> [doc3]
Una búsqueda de "mouse inalámbrico" ahora se convierte en: buscar la lista de "inalámbrico" ([doc1, doc2]), buscar la lista de "mouse" ([doc1, doc2]), e intersectarlas — una operación sobre dos listas cortas y ordenadas, no un escaneo del texto original. Construir ese índice no es solo dividir por espacios: un pipeline real tokeniza (divide en palabras, manejando puntuación y mayúsculas), normaliza/aplica stemming (corriendo, corrió, corre → correr, para que una búsqueda por una coincida con documentos que contienen cualquiera de ellas), y elimina stop words (el, la, y — demasiado comunes para ser útiles al filtrar o rankear). El ranking de relevancia sobre el conjunto de coincidencias suele usar algo como TF-IDF o el más moderno BM25: un término que aparece a menudo en un documento pero raramente en todo el corpus (una palabra distintiva) puntúa más alto ese documento que un término común en todas partes (coincidencias con "el" no distinguen nada).
#Búsqueda de texto completo en Postgres: el índice invertido, integrado
Postgres implementa exactamente esto, de forma nativa, vía tsvector (el documento procesado e indexado) y tsquery (el término de búsqueda procesado), respaldado por un índice GIN — que es la estructura de índice invertido de propósito general de Postgres:
ALTER TABLE products ADD COLUMN search_vector tsvectorGENERATED ALWAYS AS (to_tsvector('spanish', name || ' ' || description)) STORED;CREATE INDEX products_search_idx ON products USING GIN (search_vector);-- búsqueda rankeadaSELECT id, name, ts_rank(search_vector, query) AS rankFROM products, to_tsquery('spanish', 'inalambrico & mouse') AS queryWHERE search_vector @@ queryORDER BY rank DESCLIMIT 20;
to_tsvector hace por ti el pipeline de tokenizar/normalizar/stemming/eliminar-stop-words, y @@ es el operador "este tsvector coincide con este tsquery," respaldado por el índice GIN en vez de un escaneo secuencial. Esto te da búsqueda de texto completo real, con ranking de relevancia, sin infraestructura nueva, con consistencia transaccional con el resto de tus datos (actualizar una fila actualiza atómicamente su vector de búsqueda) y sin un segundo sistema que mantener sincronizado.
#Dónde la búsqueda de Postgres deja de ser suficiente
La FTS de Postgres es genuinamente buena para una gran parte de las necesidades reales de búsqueda, pero empieza a mostrar tensión en formas específicas y predecibles:
- Tolerancia a errores tipográficos / coincidencia difusa. La FTS de Postgres coincide con tokens exactos (con stemming); "mouse inalambrico" (sin tilde, mal escrito) no coincidirá con "inalámbrico" sin agregar por separado similitud de trigramas con
pg_trgm, y aun así no es de la misma calidad que un comparador difuso construido específicamente para eso. - Búsqueda facetada y agregaciones en tiempo de búsqueda. "Mostrar conteos de resultados por categoría, por rango de precio, por marca, en vivo, mientras el usuario escribe" es el terreno natural de Elasticsearch (está construido sobre Lucene, con agregaciones como tipo de consulta de primera clase); hacer lo mismo en Postgres significa consultas
GROUP BYhechas a mano corriendo junto a la consulta de búsqueda, lo cual se vuelve costoso a escala. - Escala horizontal del índice en sí. Los índices GIN viven en una sola instancia de Postgres (las réplicas de lectura ayudan a las lecturas, no al throughput de construcción/actualización del índice); Elasticsearch particiona (shardea) un índice entre muchos nodos de forma nativa, lo cual importa cuando el corpus y el volumen de consultas superan lo que la tasa de actualización del índice de una sola máquina puede absorber.
- Ajuste de relevancia específico de búsqueda. Elasticsearch expone boosting por campo, analizadores personalizados por idioma, listas de sinónimos, "más como esto" y búsqueda vectorial/híbrida (semántica + por palabra clave) como configuración; en Postgres, casi todo eso es posible pero es un proyecto, no un ajuste.
| Búsqueda de texto completo en Postgres | Elasticsearch | |
|---|---|---|
| Infraestructura | Ninguna — ya está en tu base de datos | Un clúster separado que operar, monitorear y mantener sincronizado |
| Consistencia con los datos fuente | Transaccional, siempre sincronizada | Eventualmente consistente — se indexa de forma asíncrona tras las escrituras |
| Suficientemente buena para | Búsqueda en una lista de posts de blog, búsqueda de nombre/descripción de producto, búsqueda de tickets | Búsqueda-como-el-producto: búsqueda facetada de e-commerce, búsqueda de logs, ajuste de relevancia a gran escala |
| Tolerancia a errores, sinónimos, facetas | Posible, construido a mano (pg_trgm, agregación manual) | Integrado, configurable |
| Techo de escala | Throughput de actualización/consulta de índice de una instancia | Particionado horizontalmente, construido para eso |
🧠 Check your understanding
0/2 · 0/2 answered1. ¿Por qué un índice B-tree normal no puede acelerar WHERE description LIKE '%mouse%' de la misma forma que acelera una búsqueda por igualdad?
2. ¿Qué almacena realmente un índice invertido, y por qué hace rápida la búsqueda?