Alguna vez nos ha pasado a todos: publicar un sitio web en el servidor del cliente y que vaya extremadamente lento, cuando en tu servidor de desarrollo todo iba como la seda.

Nuestra tendencia es pensar que el problema está en el servidor, en la configuración de Apache, de PHP, de la base de datos… pero la experiencia dice que la mayoría de las veces el problema está en la web, en la aplicación o en el módulo que has desarrollado.

Prestashop y su ecosistema de módulos es una de esas plataformas que pone al alcance de “cualquiera” montar una tienda online, pero cuando tu negocio tiene bastante tráfico y además el proyecto es un rediseño de una tienda online ya existente, el nivel de exigencia sube.

No hace mucho, justo después de publicar una tienda online y hacer las primeras pruebas post-publicación, nos encontramos con que Prestashop iba extremadamente lento con apenas cinco usuarios activos en el sitio.

Activamos la depuración de Prestashop, esto nos ayudó a descartar errores a nivel de Apache y PHP, y pensamos que el problema estaba en MySQL así que monitorizando sesiones con SHOW PROCESSLIST detectamos que una consulta solía tardar entre 3 y 4 segundos en ejecutarse. Enseguida activamos el log de consultas lentas de MySQL (slow query log) con un umbral de 1 segundo y cazamos la siguiente consulta:

SELECT  p.*,
        product_shop.*,
        product_shop.id_category_default,
        pl.*,
        image_shop.`id_image` id_image,
        il.legend,
        m.name manufacturer_name,
        product_attribute_shop.id_product_attribute id_product_attribute,
        DATEDIFF(product_shop.`date_add`, DATE_SUB("2016-09-20 00:00:00", INTERVAL 20 DAY)) > 0 AS new,
        stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity, product_attribute_shop.minimal_quantity AS product_attribute_minimal_quantity
FROM ps_cat_filter_restriction cp
LEFT JOIN `ps_product` p 
  ON p.`id_product` = cp.`id_product`
INNER JOIN ps_product_shop product_shop
  ON (product_shop.id_product = p.id_product AND product_shop.id_shop = 1) 
LEFT JOIN `ps_product_attribute_shop` product_attribute_shop
  ON (p.`id_product` = product_attribute_shop.`id_product` AND product_attribute_shop.`default_on` = 1 AND product_attribute_shop.id_shop=1)
LEFT JOIN ps_product_lang pl 
  ON (pl.id_product = p.id_product AND pl.id_shop = 1  AND pl.id_lang = 3)
LEFT JOIN `ps_image_shop` image_shop
  ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop=1)
LEFT JOIN `ps_image_lang` il 
  ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = 3)
LEFT JOIN ps_manufacturer m 
  ON (m.id_manufacturer = p.id_manufacturer)
LEFT JOIN ps_stock_available stock
  ON (stock.id_product = p.id_product AND stock.id_product_attribute = 0 AND stock.id_shop = 1  AND stock.id_shop_group = 0  )
WHERE product_shop.`active` = 1 
  AND product_shop.`visibility` IN ("both", "catalog")
ORDER BY cp.position desc , cp.id_product LIMIT 0,24;

Buscando el código de esta consulta en los ficheros del Prestashop con el que estábamos trabajando, encontramos que se trata de una consulta perteneciente al módulo blocklayered, usado para mostrar un bloque de filtros de búsqueda (navegación facetada) en la tienda online.

Nuestra experiencia con este módulo siempre ha sido de lentitud, en el código hace cosas como seleccionar todos los campos de tres tablas. Sabiendo que el módulo blocklayered crea la tabla ps_cat_filter_restriction en memoria (ENGINE=MEMORY), analizamos el plan de ejecución de esta consulta y detectamos que los posibles focos de la lentitud podrían ser algunos JOIN.

Finalmente llegamos a crear los siguientes índices, con los que el rendimiento de la consulta aumentó considerablemente y, por tanto, la navegación por el catálogo y el uso de los filtros de búsqueda comenzaron a responder de forma más ágil y mejoró la velocidad ofreciendo una respuesta casi inmediata a la interacción de los usuarios:

create index idx_pattrshop 
on ps_product_attribute_shop 
   (id_product_attribute, default_on, id_shop);

create index idx_image_shop
on ps_image_shop 
   (id_image, cover, id_shop);

Una vez creados estos índices, también comprobamos que no afectaban negativamente otras áreas del Prestashop, así que los dimos por buenos y el rendimiento de la tienda hoy en día sigue siendo bastante bueno a pesar de los movimientos de catálogo habituales en este tipo de tiendas online.


Créditos:

  • Imagen: https://www.flickr.com/photos/khavez/