Symfony 1.0, la guía definitiva

18.4. Optimizando la cache

El Capítulo 12 describe cómo guardar en la cache partes de la respuesta o incluso la respuesta completa. Como guardar la respuesta en la cache mejora mucho el rendimiento de la aplicación, esta técnica debería ser una de las primeras a considerar para optimizar las aplicaciones. En esta sección se muestra cómo sacar el máximo partido a la cache e incluye algunos trucos muy interesantes.

18.4.1. Borrando partes de la cache de forma selectiva

Durante el desarrollo de una aplicación, se dan muchas situaciones en las que se debe borrar la cache:

  • Cuando se crea una clase nueva: añadir la clase a un directorio para el que funciona la carga automática de clases (cualquier directorio lib/ del proyecto) no es suficiente para que Symfony sea capaz de encontrarla. En este caso, es preciso borrar la cache de la carga automática para que Symfony recorrar otra vez todos los directorios indicados en el archivo autoload.yml y pueda encontrar las nuevas clases.
  • Cuando se modifica la configuración en el entorno de producción: en producción, la configuración de la aplicación solamente se procesa durante la primera petición. Las siguientes peticiones utilizan la versión guardada en la cache. Por lo tanto, cualquier cambio en la configuración no tiene efecto en el entorno de producción (o en cualquier otro entorno donde SF_DEBUG esté desactivado) hasta que se borre ese archivo de la cache.
  • Cuando se modifica una plantilla en un entorno en el que la cache de plantillas está activada: en producción siempre se utilizan las plantillas guardadas en la cache, por lo que todos los cambios introducidos en las plantillas se ignoran hasta que la plantilla guardada en la cache se borra o caduca.
  • Cuando se actualiza una aplicación mediante el comando sync: este caso normalmente comprende las 3 modificaciones descritas anteriormente.

El problema de borrar la cache entera es que la siguiente petición tarda bastante tiempo en ser procesada, porque se debe regenerar la cache de configuración. Además, también se borran de la cache las plantillas que no han sido modificadas, por lo que se pierde la ventaja de haberlas guardado en la cache.

Por este motivo, es una buena idea borrar de la cache solamente los archivos que hagan falta. Las opciones de la tarea clear-cache pueden definir un subconjunto de archivos a borrar de la cache, como muestra el listado 18-14.

Listado 18-14 - Borrando solamente algunas partes de la cache

// Borrar sólo la cache de la aplicación "miaplicacion"
> symfony clear-cache miaplicacion

// Borrar sólo la cache HTML de la aplicación "miaplicacion"
> symfony clear-cache miaplicacion template

// Borrar sólo la cache de configuración de la aplicación "miaplicacion"
> symfony clear-cache miaplicacion config

También es posible borrar a mano algunos archivos del directorio cache/ o borrar las plantillas guardadas en la cache desde la acción mediante el método $cacheManager->remove(), como se describe en el capítulo 12.

Todas estas técnicas minimizan el impacto negativo sobre el rendimiento de todos los cambios mostrados anteriormente.

Truco Cuando se actualiza Symfony, la cache se borra de forma automática, sin intervención manual (si se establece la opción check_symfony_version a true en el archivo de configuración settings.yml).

18.4.2. Generando páginas para la cache

Cuando se instala una nueva aplicación en producción, la cache de las plantillas está vacía. Para que una página se guarde en la cache, se debe esperar a que algún usuario visite esa página. En algunas aplicaciones críticas, no es admisible el tiempo de procesamiento de esa primera petición, por lo que se debe disponer de la versión de la página en la cache desde la primera petición.

La solución consiste en navegar de forma automática por las páginas de la aplicación en un entorno intermedio que se suele llamar "staging" y que dispone de una configuración similar a la del entorno de producción. De esta forma, se genera la cache completa de páginas y plantillas. Después, se puede transferir la aplicación a producción junto con la cache llena.

Para navegar de forma automática por todas las páginas de la aplicación, una opción consiste en utilizar un script de consola que navegue por una serie de URL mediante un navegador de texto (como por ejemplo "curl"). Otra opción mejor y más rápida consiste en utilizar un script de Symfony que utilice el objeto sfBrowser mostrado en el capítulo 15. Se trata de un navegador interno escrito en PHP y que utiliza el objeto sfTestBrowser para las pruebas funcionales. A partir de una URL externa, devuelve una respuesta, teniendo en cuenta la cache de las plantillas, como haría cualquier otro navegador. Como sólo se inicializa Symfony una vez y no pasa por la capa HTTP, este método es mucho más rápido.

El listado 18-15 muestra un script que genera la cache de plantillas en un entorno de tipo "stagging". Se puede ejecutar mediante php batch/generar_cache.php.

Listado 18-15 - Generando la cache de las plantillas, en batch/generar_cache.php

<?php

define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_APP',         'myapp');
define('SF_ENVIRONMENT', 'staging');
define('SF_DEBUG',       false);

require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');

// Array de URL a navegar
$uris = array(
  '/foo/index',
  '/foo/bar/id/1',
  '/foo/bar/id/2',
  ...
);

$b = new sfBrowser();
foreach ($uris as $uri)
{
  $b->get($uri);
}

18.4.3. Guardando los datos de la cache en una base de datos

Por defecto, los datos de la cache de plantillas se guardan en el sistema de archivos: los trozos de HTML y los objetos serializados de la respuesta se guardan en el directorio cache/ del proyecto. Symfony también incluye un método de almacenamiento alternativo para la cache: la base de datos SQLite. Este tipo de base de datos consiste en un archivo simple que PHP es capaz de reconocer como base de datos para buscar información en el archivo de forma muy eficiente.

Para indicar a Symfony que debería utilizar el almacenamiento de SQLite en vez del sistema de archivos, se debe modificar la opción view_cache del archivo de configuración factories.yml:

view_cache:
  class: sfSQLiteCache
  param:
    database: %SF_TEMPLATE_CACHE_DIR%/cache.db

La ventaja de utilizar el almacenamiento en SQLite es que la cache de las plantillas es mucho más fácil de leer y de escribir cuando el número de elementos de la cache es muy grande. Si la aplicación hace un uso intensivo de la cache, los archivos almacenados en la cache acaban en una estructura de directorios muy profunda, por lo que utilizar el almacenamiento de SQLite mejora el rendimiento de la aplicación.

Además, borrar una cache almacenada en el sistema de archivos requiere eliminar muchos archivos, por lo que es una operación que puede durar algunos segundos, durante los cuales la aplicación no está disponible. Si se utiliza el almacenamiento de SQLite, el proceso de borrado de la cache consiste en borrar un solo archivo, precisamente el archivo que se utiliza como base de datos SQLite. Independientemente del número de archivos en la cache, el borrado es instantáneo.

18.4.4. Saltándose Symfony

La mejor forma de mejorar el rendimiento de Symfony consiste en saltárselo por completo, aunque sea de forma parcial. Algunas páginas no cambian con cada petición, por lo que no es necesario procesarlas cada vez mediante el framework. Aunque la cache de las plantillas acelera el procesamiento de las páginas, todavía debe hacer uso de Symfony.

El capítulo 12 muestra algunos trucos con los que se puede evitar Symfony por completo para algunas páginas. El primer truco consiste en utilizar las cabeceras HTTP 1.1 para solicitar a los proxies y a los navegadores de los usuarios que guarden la página en sus propias caches y que no la soliciten la próxima vez que el usuario quiera acceder a la página. El segundo truco es la cache super rápida (que se puede automatizar mediante el plugin sfSuperCachePlugin) que consiste en guardar una copia de la respuesta en el directorio web/ y la modificación de las reglas de reescritura de URL para que Apache busque en primer lugar la versión de la página en la cache antes de enviar la petición a Symfony.

Estos dos métodos son muy efectivos, aunque solamente se puedan aplicar a las páginas estáticas, ya que evita que estas páginas sean procesadas por Symfony, permitiendo a los servidores dedicarse al procesamiento de las peticiones complejas.

18.4.5. Guardando en la cache el resultado de una función

Si una función no depende del contexto de ejecución ni de variables aleatorias, al ejecutar 2 veces la misma función con los mismos parámetros, el resultado será el mismo. De esta forma, se podría evitar la segunda ejecución de la función si se ha almacenado el resultado de la primera ejecución. Esto es exactamente lo que permite hacer la clase sfFunctionCache. Esta clase dispone de un método llamado call(), al que se le pasa un elemento de PHP que se pueda ejecutar y una serie de parámetros. Cuando se ejecuta, este método crea una huella digital mediante el método MD5 de todos los argumentos que se le han pasado y busca en la cache un archivo cuyo nombre coincida con esta huella digital. Si se encuentra el archivo, se devuelve el resultado almacenado en el archivo. Si no se encuentra, sfFunctionCache ejecuta la función, almacena su respuesta en la cache y devuelve esta respuesta. Por tanto, la segunda ejecución del código del listado 18-16 es más rápida que la primera.

Listado 18-16 - Guardando el resultado de una función en la cache

$directorio_cache_para_funciones = sfConfig::get('sf_cache_dir').'/function';
$fc = new sfFunctionCache($directorio_cache_para_funciones);
$resultado1 = $fc->call('cos', M_PI);
$resultado2 = $fc->call('preg_replace', '/\s\s+/', ' ', $input);

El constructor de sfFunctionCache espera como argumento una ruta absoluta a un directorio (el directorio debe existir antes de que se instancie el objeto). El primer argumento del método call() debe ser cualquier elemento PHP que se pueda ejecutar, por lo que se puede indicar el nombre de una función, un array con el nombre de una clase y un método estático o un array con el nombre de un objeto y el de un método público. Respecto al resto de argumentos que se pueden pasar al método call(), se pueden indicar tantos argumentos como sean necesarios, ya que son procesados por el elemento PHP que realmente se ejecutará.

Este objeto es muy útil para las funciones que requieren mucha CPU, ya que las operaciones de lectura y escritura de archivos requieren mucho más tiempo que el necesario para ejecutar una función sencilla. El objeto se basa en la clase sfFileCache, que es el componente que utiliza el mecanismo de cache de plantillas de Symfony. La documentación de la API dispone de más detalle sobre estas clases.

Advertencia La tarea clear-cache solamente borra los contenidos del directorio cache/. Si se establece el directorio de cache de las funciones en otro directorio diferente, no se borrará automáticamente cuando se borre la cache mediante la línea de comandos.

18.4.6. Guardando datos en la cache del servidor

Los aceleradores de PHP proporcionan unas funciones especiales para almacenar datos en la memoria, de forma que se puedan reutilizar entre diferentes peticiones. El problema es que cada acelerador utiliza su propia sintaxis y cada uno realiza esta tarea de una forma diferente. Symfony dispone de una clase llamada sfProcessCache que abstrae todas las diferencias en el funcionamiento de los diferentes aceleradores. Su sintaxis se muestra en el listado 18-17.

Listado 18-17 - Sintaxis de los métodos de sfProcessCache

// Guardando datos en la cache de los procesos
sfProcessCache::set($nombre, $valor, $tiempoVida);

// Accediendo a los datos
$valor = sfProcessCache::get($nombre);

// Comprobando si un valor existe en la cache
$existe_valor = sfProcessCache::has($nombre);

// Borrar la cache de los procesos
sfProcessCache::clear();

El método set() devuelve un valor false si no funciona la cache. El valor guardado en la cache puede ser de cualquier tipo (cadena, array, objeto); la clase sfProcessCache se encarga de la serialización automática. El método get() devuelve un valor null si la variable solicitada no existe en la cache.

Los métodos de la clase sfProcessCache funcionan incluso cuando no se encuentra instalado ningún acelerador. Por lo tanto, no existe ningún riesgo en intentar obtener datos de la cache de los procesos, siempre que se proporcione un valor alternativo. El listado 18-18 por ejemplo muestra cómo obtener una opción de configuración de la cache de procesos.

Listado 18-18 - Utilizando la cache de procesos de forma segura

if (sfProcessCache::has('miaplicacion_parametros'))
{
  $parametros = sfProcessCache::get('miaplicacion_parametros');
}
else
{
  $parametros = obtener_parametros();
}

Truco Si se quiere profundizar en el uso de la cache en memoria, se debería utilizar la extensión memcache para PHP. Esta extensión permite reducir la carga en la base de datos para las aplicaciones en las que se aplica el balanceo de carga y PHP 5 proporciona una interfaz con esta extensión (http://www.php.net/memcache/).