Búsqueda lenta en PrestaShop: causas, filtros y medición
El módulo para este problema
Búsqueda Elasticsearch y Navegación por Facetas
Búsqueda con el motor integrado o con Elasticsearch, OpenSearch y Meilisearch, tolerancia a errores tipográficos, filtros por facetas y análisis de las búsquedas sin resultados. Se instala con el motor integrado y un motor externo se conecta cuando hace falta. PrestaShop 1.7.7, 8 y 9, 12 meses de actualizaciones incluidos, después renovación opcional.
Cómo busca la búsqueda nativa de PrestaShop
Verificado en el código de PrestaShop 9.1.5 (classes/Search.php) y del módulo ps_facetedsearch 5.0.0; los archivos están en las fuentes.
El índice
PrestaShop no busca en el texto de los productos: busca en un índice propio. La tabla search_word contiene las palabras del catálogo, distintas por idioma y por tienda, y search_index vincula cada producto con cada una de sus palabras mediante un peso. El peso depende del campo (nombre, referencias, descripciones, categoría, marca, etiquetas, atributos, características, opciones PS_SEARCH_WEIGHT_*) y un peso de 0 excluye el campo del índice. Las palabras por debajo de la longitud mínima (PS_SEARCH_MINWORDLEN) no se indexan y las «Palabras en la lista negra» (PS_SEARCH_BLACKLIST) se descartan. El índice se actualiza al guardar un producto si la indexación automática está activada y se reconstruye desde Parámetros de la tienda, Buscar, con «Añadir los productos que faltan al índice» y «Reconstruir el índice completo», o con la URL para el cron que se muestra en la misma página.
La búsqueda
Por cada palabra escrita, Search::find consulta el índice con una condición LIKE sobre la palabra. De forma predeterminada el comodín está solo al final («palabra%»). Con «Buscar en el interior de las palabras» (PS_SEARCH_START) el comodín aparece también al principio, y la interfaz advierte de que puede requerir muchos recursos; con «Buscar coincidencia exacta» (PS_SEARCH_END) desaparece el del final. Los productos encontrados para cada palabra se cruzan en PHP, después una subconsulta suma los pesos de cada producto para la relevancia y una consulta final carga los resultados y cuenta el total. Con la ordenación por precio, todo el conjunto se carga y se ordena en PHP antes de paginarse.
La búsqueda difusa
Si la búsqueda exacta no encuentra nada y «Búsqueda difusa» (PS_SEARCH_FUZZY) está activada, PrestaShop lee del índice las palabras de longitud compatible (con más de 100.000 palabras, solo las de la misma longitud) y calcula en PHP la distancia de Levenshtein respecto a la escrita, aceptando la más cercana dentro de una diferencia máxima (PS_SEARCH_FUZZY_MAX_DIFFERENCE, 5 de forma predeterminada según la interfaz). Los intentos por búsqueda están limitados por PS_SEARCH_FUZZY_MAX_LOOP, y la interfaz advierte de que la opción consume recursos.
Por qué se ralentiza cuando crece el catálogo
Son mecanismos, no cifras: los tiempos dependen del catálogo, los idiomas, los filtros y el servidor.
- El comodín al principio. Según la documentación de MySQL, una comparación
LIKEsolo puede usar un índice como rango si el argumento es una cadena constante que no empieza por un comodín; MariaDB dice lo mismo. El índice desearch_word(idioma, tienda, palabra) ayuda con «palabra%», no con «%palabra%». - El tamaño del índice.
search_indextiene una fila por cada pareja producto-palabra, con palabras distintas por idioma y por tienda: crece con los productos, las palabras por producto y los idiomas. - Joins y sumas de pesos. Cada búsqueda une las tablas del índice con las de los productos y la relevancia es una subconsulta por cada producto encontrado.
- El bucle difuso. Tras una búsqueda vacía, una lista de palabras leída de la base de datos se compara una a una en PHP.
- La ordenación por precio en PHP, que carga todos los resultados en lugar de paginarlos en la consulta.
- Los recuentos de los filtros, descritos más abajo.
Filtros: por qué un recuento puede no cuadrar
ps_facetedsearch calcula cada recuento con una consulta COUNT(DISTINCT p.id_product) agrupada por valor, aplicando los filtros seleccionados salvo el del grupo que está contando. Cuenta productos, no combinaciones. Un número puede diferir de los resultados por estos motivos, todos visibles en el código:
- Caché de los bloques de filtros. Con la opción activada (lo está tras la instalación), los bloques con sus recuentos se guardan en la tabla
layered_filter_blockpara categorías, marcas y proveedores (no para la búsqueda) y los vacían los hooks de guardado de productos, categorías, atributos, características y reglas de precio. Entre los hooks registrados no hay ninguno sobre la variación de cantidad: tras actualizaciones de stock hechas fuera de la ficha de producto, el bloque puede seguir siendo el antiguo hasta que use «Borrar la caché» o el cron de vaciado. - Índice de precios. El filtro de precio usa
layered_price_indexcon un join interno: un producto ausente del índice no aparece cuando el filtro está activo. El índice se actualiza al guardar el producto y desde el panel «Índices y cachés» («Indexar todos los precios que faltan», «Reconstruir todo el índice de precios»); el panel aconseja crons periódicos, con una reconstrucción nocturna.
Qué cambia con un motor externo
Elasticsearch, OpenSearch y Meilisearch mantienen el índice en un servicio aparte, diseñado para buscar texto.
- Índice invertido. Según la documentación de Elasticsearch, es una estructura que asocia cada término con los documentos que lo contienen, con un diccionario ordenado de términos y, para cada uno, una lista de documentos. El texto buscado se analiza igual que el texto indexado.
- Relevancia. Elasticsearch y OpenSearch usan BM25 como modelo predeterminado.
- Errores tipográficos. En Elasticsearch el parámetro
fuzzinesses una distancia de edición de Levenshtein calculada por el motor; el valorAUTOes el que se recomienda en general (de 0 a 2 caracteres la palabra debe coincidir, de 3 a 5 se admite un error, con más de 5 dos). OpenSearch tiene el mismo parámetro. En Meilisearch la tolerancia a errores tipográficos está activada de forma predeterminada: un error para términos de al menos 5 caracteres, dos a partir de 9. - Idiomas. Elasticsearch ofrece analizadores por idioma, entre ellos italiano, francés, alemán, inglés, español y neerlandés.
- Recuentos. Las agregaciones producen resúmenes como «cuántos productos hay en cada categoría» en la misma petición de búsqueda, con el parámetro
aggs. OpenSearch advierte de que consumen más CPU y memoria que las consultas. - Costes. Es un servicio más que instalar, actualizar y mantener encendido, y el índice debe mantenerse alineado con el catálogo: cada producto existe en la base de datos y en el motor. Para catálogos pequeños la búsqueda nativa suele bastar.
Cómo medir antes de decidir
- Tiempo de la página de búsqueda. Desde un terminal:
curl -o /dev/null -s -w %{time_starttransfer}\\n https://example.com/index.php\?controller=search\&s=taladro. Según la documentación de curl,time_starttransferes el tiempo en segundos hasta el primer byte, incluido el que tarda el servidor en calcular el resultado. Pruebe de 5 a 10 búsquedas reales, cada una tres veces: un código de producto, una palabra con un error tipográfico, dos palabras (con el signo más en lugar del espacio), una marca. - Registro de consultas lentas. En MySQL y MariaDB está desactivado de forma predeterminada y el límite predeterminado de
long_query_timees de 10 segundos: bájelo para la prueba y después useEXPLAINen las consultas encontradas para ver si usan un índice. - Perfil de PrestaShop. La constante
_PS_DEBUG_PROFILING_enconfig/defines.inc.php, según el comentario del archivo, muestra información de rendimiento bajo cada página. Úsela en una copia de pruebas. - Estadísticas de las búsquedas. El módulo «Búsquedas en la tienda» (
statssearch) guarda la palabra buscada, el número de resultados y la fecha en cada búsqueda; en Estadísticas enumera las palabras buscadas más de una vez. - Un filtro a mano. Anote el recuento junto a un valor, haga clic en él y compare con los productos listados; repita después de cambiar un precio o el stock.
- La demo. Pruebe las mismas búsquedas y los mismos filtros en la demo, que tiene un catálogo grande y el módulo activo.
Antes de cambiar de motor conviene también probar los ajustes: desactivar «Buscar en el interior de las palabras» si no hace falta, reducir las palabras difusas admitidas, poner a cero el peso de los campos que no necesite y reconstruir el índice.
Cómo lo hace el módulo Búsqueda Elasticsearch y Navegación por Facetas
- Motor a elegir: integrado (lee la base de datos de PrestaShop, sin ningún servicio más), Elasticsearch, OpenSearch o Meilisearch.
- Con Elasticsearch y OpenSearch, los resultados y los recuentos de los filtros llegan de la misma petición: las agregaciones viajan en el cuerpo de la búsqueda.
- Los recuentos están en modo automático: activados con un motor externo, desactivados con el motor integrado, donde costarían un recuento por grupo de filtros. Se pueden forzar desde la pestaña Facetas.
- Errores tipográficos:
fuzzinessdel motor externo; con el motor integrado, un diccionario de trigramas, consultado solo tras una búsqueda sin resultados, que elige la palabra más cercana con la distancia de Levenshtein sobre un conjunto reducido. Corrige una palabra cada vez, no ordena por similitud y no mira las descripciones. - Si el motor externo no responde, un interruptor lo excluye durante 30 segundos y, con el respaldo activado (lo está de forma predeterminada), las búsquedas recurren al motor integrado.
- El índice sigue el guardado y la eliminación de los productos y las variaciones de stock; un cron protegido por token y un asistente de reindexación lo reconstruyen.
- Análisis de las búsquedas, con las más frecuentes y las que no dan resultados, y plantillas de filtros por categoría.
Vea la ficha del módulo, lea la guía de instalación y configuración o pruebe la demo.
Fuentes
- PrestaShop 9.1.5 en GitHub: Search.php, AdminSearchConfController.php, db_structure.sql, defines.inc.php
- Módulos de PrestaShop en GitHub: ps_facetedsearch 5.0.0, statssearch 3.0.0
- MySQL 8.0: range optimization, slow query log, EXPLAIN. MariaDB: LIKE, slow query log
- Elasticsearch: full-text, similarity, fuzziness, analizadores por idioma, agregaciones
- OpenSearch: similarity, match y fuzziness, agregaciones. Meilisearch: typo tolerance
- Elasticsearch, índices y documentos. Manual de curl, opción
--write-out
Preguntas frecuentes
¿A partir de cuántos productos conviene un motor externo?
No existe un umbral válido para todos: depende de los productos, los idiomas, el número de filtros y el servidor. Para catálogos pequeños la búsqueda nativa suele bastar. Mejor medir antes, con las pruebas de arriba.
¿La búsqueda difusa de PrestaShop ralentiza todas las búsquedas?
No, solo interviene cuando la búsqueda exacta no encuentra nada: entonces lee las palabras candidatas de la base de datos y calcula en PHP la distancia de Levenshtein. El coste recae en las búsquedas sin resultados.
¿Por qué el número junto a un filtro no coincide con los productos listados?
Las causas visibles en el código son la caché de los bloques de filtros, que algunas modificaciones de stock no vacían, y un índice de precios o de atributos desactualizado. Además, el grupo ya filtrado se cuenta sin su propio filtro.
¿Un motor externo es siempre más rápido?
No necesariamente. Traslada la búsqueda a un índice pensado para el texto, pero es un servicio más que mantener encendido y alineado, y las agregaciones consumen más CPU y memoria que las consultas.
¿Basta el módulo por sí solo?
No. Con el motor integrado la búsqueda sigue siendo una búsqueda en la base de datos de PrestaShop, con filtros y corrección de errores tipográficos mejores pero sin cambiar el mecanismo de fondo. Un motor externo hay que instalarlo, actualizarlo y mantenerlo encendido, y los sinónimos y los analizadores hay que configurarlos. Conviene medir antes.
