- Implementa una arquitectura de motor de búsqueda que funciona sin servicios externos usando la base de datos existente, con enfoque en tokenización, pesos y scoring
- La idea central es tokenizar y almacenar todo el texto, y al buscar hacer coincidir los tokens del mismo modo para calcular la relevancia
- Combina tokenizadores Word, Prefix y N-Gram para cubrir coincidencia exacta, coincidencia parcial y tolerancia a errores tipográficos; cada tokenizador tiene su propio peso
- Mediante un sistema de pesos y un algoritmo de scoring basado en SQL, evalúa en conjunto la longitud del documento, la diversidad de tokens y la calidad promedio
- Tiene alta escalabilidad y transparencia, por lo que permite agregar nuevos tokenizadores o tipos de documento, ajustar pesos y modificar el scoring con libertad
Por qué crear tu propio motor de búsqueda
- Los servicios externos como Elasticsearch o Algolia son potentes, pero implican la carga de aprender APIs complejas y gestionar infraestructura
- Cuando solo se necesita una función de búsqueda que se integre con la base de datos existente y sea fácil de depurar, construirla directamente puede ser útil
- El objetivo es un motor de búsqueda simple que devuelva resultados relevantes sin dependencias externas
Concepto clave: tokenización y matching
- El principio básico es tokenizar todo el texto y almacenarlo, y al buscar generar tokens del mismo modo para hacer matching
- En la fase de indexación, el contenido se divide en tokens y se guarda junto con sus pesos
- En la fase de búsqueda, la consulta se tokeniza de la misma manera para encontrar tokens coincidentes y calcular la puntuación
- En la fase de scoring, se usan los pesos almacenados para obtener la puntuación de relevancia
Diseño del esquema de base de datos
- Se usan dos tablas:
index_tokens e index_entries
index_tokens: almacena tokens únicos y pesos por tokenizador
index_entries: conecta tokens con documentos y guarda la puntuación final reflejando pesos de campo, documento y tokenizador
- Fórmula de cálculo del peso final:
field_weight × tokenizer_weight × ceil(sqrt(token_length))
- Los índices se configuran para consulta de documentos, consulta de tokens, queries por campo y filtrado por peso
Sistema de tokenización
- WordTokenizer: separación por palabra, eliminación de palabras cortas, para coincidencia exacta (peso 20)
- PrefixTokenizer: genera prefijos de palabras, para autocompletado y coincidencia parcial (peso 5)
- NGramsTokenizer: genera combinaciones de caracteres de longitud fija, para errores tipográficos y coincidencia parcial (peso 1)
- Todos los tokenizadores realizan en común conversión a minúsculas, eliminación de caracteres especiales y normalización de espacios
Sistema de pesos
- Peso de campo: refleja la importancia de título, cuerpo, palabras clave, etc.
- Peso del tokenizador: Word > Prefix > N-Gram
- Peso del documento: se calcula combinando los dos elementos anteriores con la longitud del token
- Se usa la función
ceil(sqrt()) para suavizar el impacto de tokens largos, y si hace falta puede ajustarse con una función logarítmica o lineal
Servicio de indexación
- Solo se pueden indexar documentos que implementen
IndexableDocumentInterface
- Al crear o modificar un documento, la indexación se ejecuta mediante un listener de eventos o comandos (
app:index-document, app:reindex-documents)
- Procedimiento:
- Eliminar el índice existente y luego generar nuevos tokens
- Ejecutar todos los tokenizadores para cada campo
- Verificar si el token existe y crearlo si no (
findOrCreateToken)
- Hacer inserción por lotes (batch insert) en
index_entries con el peso calculado
- Estructura pensada para evitar duplicados, mejorar el rendimiento y responder a actualizaciones
Servicio de búsqueda
- Procesa la consulta con los mismos tokenizadores para obtener el mismo conjunto de tokens que en la indexación
- Elimina tokens duplicados, los ordena por longitud (priorizando los más largos) y los limita a un máximo de 300
- Mediante una query SQL une tokens y documentos, y calcula y ordena la puntuación de relevancia
- Devuelve los resultados con la forma
SearchResult(documentId, score)
Algoritmo de scoring
- Puntuación base:
SUM(sd.weight)
- Ajuste por diversidad de tokens:
LOG(1 + COUNT(DISTINCT token_id))
- Ajuste por peso promedio:
LOG(1 + AVG(weight))
- Penalización por longitud del documento:
1 / (1 + LOG(1 + token_count))
- Normalización: se divide por la puntuación máxima para ajustarlo al rango 0~1
- El filtro de peso mínimo de token (
st2.weight >= ?) permite eliminar coincidencias irrelevantes de muy bajo peso
Procesamiento de resultados
- Los resultados de búsqueda se devuelven como ID de documento y puntuación, y se convierten en documentos reales a través del repositorio (
repository)
- Se usa la función
FIELD() para consultar los documentos manteniendo el orden de los resultados de búsqueda
Escalabilidad del sistema
- Se pueden agregar nuevos tokenizadores implementando
TokenizerInterface
- Se pueden registrar nuevos tipos de documento implementando
IndexableDocumentInterface
- La lógica de pesos o scoring puede ajustarse solo modificando SQL
Conclusión
- Esta estructura es un motor de búsqueda simple pero realmente funcional, que ofrece rendimiento suficiente sin infraestructura externa
- Sus ventajas son la lógica clara, el control total y la facilidad para depurar
- Se enfatiza que, más que un sistema complejo, el código que puedes entender y controlar directamente puede tener más valor
1 comentarios
Opinión de Hacker News
La idea básica de la búsqueda es simple y es un campo de problemas interesante
Pero lo realmente difícil es manejar grandes volúmenes de datos y procesar consultas ambiguas
Un enfoque basado en DBMS puede estar bien para un sitio web pequeño, pero al escalar al tamaño de la Wikipedia en inglés, llega rápido a su límite
Como introducción, el e-book de SeIRP es un buen recurso gratuito
El hecho de que no haya una respuesta clara lo hace especialmente complicado
Google a veces muestra anuncios como si fueran ‘los resultados más relevantes’, así que Marginalia Search es un buen caso de contraste
Me pregunto si has revisado los papers de TREC
Los motores de búsqueda tienen que pelear constantemente contra actores adversariales que buscan ingresos por publicidad
Se vuelve un juego interminable del gato y el ratón en el que hay que cambiar continuamente las métricas de calidad para que no puedan explotarlas
Quisiera saber qué tamaño de corpus se podría buscar tomando como referencia una velocidad de 5 segundos por consulta y unas 12 consultas por minuto
Por ejemplo, es difícil juzgar cuál sería un resultado “mejor”: el artículo wiki de Gilligan’s Island o un blog de fans
Si además se suma la manipulación del ranking o el keyword stuffing, se vuelve un reto mucho más complejo que un simple problema de escalabilidad
La búsqueda es realmente difícil
Incluso empresas con enormes recursos y capacidad técnica como Apple, Microsoft y OpenAI tienen baja calidad en sus funciones de búsqueda
Esto no es solo un problema técnico
Para mejorar la calidad de búsqueda hay que ajustar con mucho detalle los parámetros de ranking, y ese tipo de trabajo es difícil de planificar con sistemas de gestión como sprints o Jira
Al final, es un área donde hace falta confianza y autonomía para los desarrolladores
Invierten miles de millones en modelos de IA, pero la webapp o la búsqueda quedan como algo secundario, y por eso salen esos resultados
Hace unos 10 años trabajé con un colega que hacía el doctorado en diseño de motores de búsqueda
Hablaba con muchísima pasión sobre la integración entre búsqueda y bases de datos, y aprendí bastante gracias a eso
Algún día me gustaría explorar a fondo el funcionamiento interno de Apache Solr y Lucene
Antes no había soluciones de búsqueda open source, así que había que construirlas uno mismo
La lección que dejó esa experiencia fue: “no construyas tu propio motor de búsqueda”
Muchísima gente ha dedicado años a este problema, y si lo haces por tu cuenta, caes en un infierno interminable de mantenimiento
En cuanto empiezan los pedidos de “agreguen corrección de errores tipográficos” o “el próximo año metamos también una taxonomía”, ya no se termina nunca
Disfruté muchísimo un curso del profesor David Evans de la University of Virginia sobre cómo construir un motor de búsqueda
Construir directamente un “motor de búsqueda clásico” fue un proyecto muy divertido
Se puede consultar el enlace del curso y la lista de reproducción en YouTube
Me molesta que los motores de búsqueda que uso con frecuencia ignoren siglas o palabras de 2 o 3 letras
Es realmente incómodo cuando eliminan palabras cortas como “mp3” o “PHP” al buscarlas
Leí Programming Collective Intelligence de Toby Segaran y me inspiró con muchas ideas sobre búsqueda, recomendación y clasificadores, entre otras cosas
Fue un artículo interesante
Me da curiosidad qué tan alto será el nivel de optimización de tokenizadores que usan los motores de búsqueda populares
Me pregunto qué tan escalable será este sistema
Elasticsearch muestra un rendimiento bastante impresionante incluso más allá de la escala recomendada
No es tan difícil crear un motor de búsqueda de texto simple
Pero crear un buen motor de búsqueda es una historia completamente distinta
No basta con implementar un algoritmo como BM25
La mayoría de las empresas a las que he asesorado terminan migrando de su solución propia a Elasticsearch u Opensearch
Una implementación propia parece simple al principio, pero con el tiempo se vuelve compleja por los problemas de ranking y la degradación del rendimiento
Se repiten síntomas como “es lento” o “arroja resultados absurdos”
Elasticsearch ya viene resolviendo estos problemas desde hace 10 años, y ahora está mucho más avanzado
Se dice que “es difícil de configurar”, pero hoy en día casi todo se configura automáticamente, y además hay muchos servicios administrados
Incluso es más fácil de manejar que Postgres
Al final, lo importante es optimizar el mapeo del índice
Hay gente que dice “no necesitamos esas funciones avanzadas”, pero en la práctica la calidad de búsqueda está directamente ligada a la supervivencia del negocio
Si de verdad quieres una búsqueda bien hecha, al final tienes que aceptar esa complejidad
También se ven interesantes alternativas emergentes como SeekStorm, que últimamente aparece mucho en HN, aunque todavía no he visto casos reales en producción
En especial, fue útil el consejo de desactivar el dynamic mapping y evitar indexar campos innecesarios
Tengo entendido que es un proyecto más antiguo que Lucene