Comentábamos hace unos días con un partner al respecto de los servicios de Hosting Magento optimizado y cómo varios competidores venden un VPS con caché como una solución optimizada, que “cachear no es optimizar el rendimiento”
En un mundo perfecto, las cachés (en contexto web, se entiende) serían una “palabrota” definida como una excusa para disimular el código mal escrito e ineficiente del sistema. Idealmente, usar una caché sería el mayor pecado que se podría cometer. Pero lo cierto es que este mundo dista mucho de ser perfecto… ¿y las cachés? Son un requisito en casi todos los casos.
En la época de la demoscene, cuando aún se programaba en C con rutinas en ensamblador, había que hacer auténticas “diabluras” para encajar en 4kB de RAM código que resultara espectacular. A día de hoy, da la sensación de que lo que queda es un montón de gente apañando fragmentos de código para que encajen entre sí… y a eso se le llama desarrollo. Casi todos los programadores dignos de ese nombre que conozco reconocen la evolución en su forma de pensar a medida que iban aprendiendo más y más y admiten “avergonzarse” cuando miran código de sus primeros años.
Pero…. antes de que empiecen los improperios, me gustaría aclarar que tengo razones válidas para argumentar que cachear no es una forma de aumentar el rendimiento. De hecho, es realmente lo contrario: el empleo de caché es sólo una forma de evitar llevar a cabo trabajo repetido (que no es lo mismo que “automatizar”), disimular “cagadas” y… una forma de evitar que nuestros servidores se sobrecarguen — lo cuál es razonable considerar bueno.
Por tanto, se hace necesario contestar la pregunta incómoda: ¿por qué no usar caché? La respuesta en el fondo es bastante simple: Una aplicación sub-óptima, por mucha caché que se le añada, seguirá siendo sub-óptima; El tiempo de ejecución medio se verá notablemente reducido, pero la eficiencia sigue siendo la misma.
Aceptémoslo. El rendimiento se optimiza tras haber detectado y analizado los cuellos de botella y eliminarlos. Es un proceso de búsqueda del código escrito de forma sub-óptima y refactorizarlo o reescribirlo en una forma mejor. Obliga a re-pensar los algoritmos y su implementación, con el objetivo de hacerlo mejor. Por eso la optimización de rendimiento bien hecha es tan maravillosa… porque implica hacer el código mejor. Y esto es difícil.
Con esto no pretendo decir que el uso de caché sea una porquería, porque no sea lo mismo que una optimización del rendimiento… para nada. La cuestión es que se hace necesario entender claramente qué es y para qué sirve una caché y cuándo se utiliza… y más si hablamos del mundo de “chapucillas rápidas” que rodea a Magento (especialmente en proyectos de bajo coste).
En la definición de caché (por ejemplo, esta extractada desde la Wikipedia), leemos:
En informática, una caché es un componente que almacena información de forma que futuras peticiones de esa misma información puedan ser atendidas de forma más rápida. Esta información puede consistir en valores que han sido calculados previamente o bien tratarse de duplicados de datos que se encontraban almacenados en otro sitio. En caso de encontrarse el dato solicitado (“acierto de caché”), la petición puede responderse simplemente leyendo esta copia, lo que es significativamente más rápido. En otro caso (“fallo de caché”), la información solicitada habrá de calcularse o recuperarse desde su ubicación original, que es comparativamente lento. Por tanto, a mayor tasa de acierto, el rendimiento del sistema resultará superior.
Para resultar asequibles y hacer un uso eficiente de los datos, las cachés son relativamente pequeñas. No obstante, las cachés han demostrado ser muy necesarias y eficaces en múltiples áreas del mundo de la computación debido a la localidad de referencia en los patrones de acceso: si un dato ha sido necesario, la probabilidad de que ese mismo o datos cercanos sean precisos es máxima
Entonces, ¿las peticiones futuras pueden ser atendidas de forma más rápida? ¡ Haber empezado por ahí !
Este concepto es completamente independiente de la aplicación y plataforma: no hay ningún caso en el cuál una caché suponga una mejora real del rendimiento [intrínseco].
Pero… ¡esto no tiene sentido!, diríamos. Bueno, veamos una serie de ejemplos para argumentarlo.
Pongamos por ejemplo una consulta a la base de datos que recupera TODA la información de una tabla. Realmente, ¿cree que cachear esta consulta en MySQL, memcache o en disco la hace más eficiente? Por supuesto que no. En cualquier caso, son datos almacenados en el sistema y cachearlos no supondrá diferencia en absoluto de cara a la optimización.
En el caso de Magento, por ejemplo…. pongamos un Magento Enterprise Edition usando nginx y memcache.
En el código de Magento hay módulos como Mage_Core_Model_Resource_Db_Abstract que contienen decenas de líneas de código que se emplean para comprobar si un objeto ya está en caché, y guardarlo si no lo estuviera ya … esto puede llegar a ser incluso perjudicial para el rendimiento.
Para el caso de la generación de una página de listado de categoría, se lanza una consulta SQL como esta:
SELECT `e`.*, cat_index.position AS cat_index_position, `price_index`.`price`,
`price_index`.`tax_class_id`, `price_index`.`final_price`,
IF( price_index.tier_price IS NOT NULL,
LEAST(price_index.min_price, price_index.tier_price),
price_index.min_price) AS `minimal_price`,
`price_index`.`min_price`, `price_index`.`max_price`, `price_index`.`tier_price` FROM `catalog_product_entity` AS `e` INNER JOIN `catalog_category_product_index` AS `cat_index` ON cat_index.product_id = e.entity_id AND cat_index.store_id =1 AND cat_index.visibility IN ( 2, 4 ) AND cat_index.category_id = '10' AND cat_index.is_parent =1 INNER JOIN `catalog_product_index_price` AS `price_index` ON price_index.entity_id = e.entity_id AND price_index.website_id = '1' AND price_index.customer_group_id =0 ORDER BY `cat_index`.`position` ASC LIMIT 15
Showing rows 0 – 6 (7 total, Query took 0.0017 sec)
SELECT `e`.sku, `cat_index`.`position` AS `cat_index_position`, `price_index`.`price`,
`price_index`.`tax_class_id`, `price_index`.`final_price`, IF(price_index.tier_price IS NOT NULL,
LEAST(price_index.min_price, price_index.tier_price),
price_index.min_price) AS `minimal_price`,
`price_index`.`min_price`, `price_index`.`max_price`, `price_index`.`tier_price` FROM `catalog_product_entity` AS `e` INNER JOIN `catalog_category_product_index` AS `cat_index` ON cat_index.product_id=e.entity_id AND cat_index.store_id=1 AND cat_index.visibility IN (2, 4) AND cat_index.category_id='10' AND cat_index.is_parent=1 INNER JOIN `catalog_product_index_price` AS `price_index` ON price_index.entity_id = e.entity_id AND price_index.website_id = '1' AND price_index.customer_group_id = 0 ORDER BY `cat_index`.`position` ASC LIMIT 15
Showing rows 0 – 6 (7 total, Query took 0.0015 sec)
¿ Aprecian la diferencia ? Se tarda básicamente lo mismo en construir la consulta pero se puede mejorar mucho (aquí, un 13%) utilizando los índices adecuados o incluso personalizando el servidor MySQL. La optimización de rendimiento puede ser tan sencilla como esto.
¿ Qué significado tiene esto ?
De hecho, acabamos de demostrar que añadir una capa adicional no hace sino aumentar el tiempo global de procesamiento necesario —como todo buen ingeniero sabe–, porque se hace preciso comprobar si el objeto está cacheado o, en su caso, guardar una copia en caché para su uso futuro.
En este caso, podemos argumentar que el rendimiento de la aplicación no se ve afectado dependiendo de dónde tomemos los datos (en este caso, memcache frente a MySQL)
Todo esto empezó con una gráfica/comparativa publicada por KissMetrics en la que defienden de forma apasionada que: “El tiempo de carga afecta directamente a sus ingresos“. Tienen toda la razón.
En doblenet somos fans acérrimos los proxies inversos y de las cachés… no por vaguería sino porque somos fanáticos de la optimización de rendimiento… de verdad. Cuando pienso en optimización, lo que contemplo son mejoras y refactorización del código, no ocultar “meteduras de pata” y chapuzas en el código.
Para ilustrar nuestro argumento, tomemos un símil basado en la construcción: Cuando se construye un edificio es necesario elegir los materiales adecuados, la ubicación y las personas implicadas. Entonces… si estamos construyendo un edificio porquería, ¿pintamos las paredes para ocultar los defectos del enlucido y los materiales de bajísima calidad? Claro, lo lógico sería hacerlo y actuar como si nada hubiera pasado hasta que alguien se dé cuenta… ¿porque así funciona el juego, no? (hasta que el edificio se desploma y ocurre una desgracia)
Por contra, en doblenet llevamos años defendiendo la importancia de la Arquitectura (de Sistemas): se diseñan y optimizan los sistemas (disco, sistema operativo, bases de datos, aplicaciones, red) para obtener el máximo rendimiento posible… y se utilizan cachés para evitar trabajo repetido y dedicar la potencia disponible a generar valor para el cliente.