Pautas para la construcción en Symfony

LIBP-0108 (Libro de pautas)

A continuación se van a ofrecer una serie de pautas destinadas a mejorar la eficiencia y el rendimiento de las aplicaciones que se desarrollan mediante el Framework Symfony.

Pautas

TítuloCarácter
Optimizar el servidorObligatoria
Limitar el número de objetos que se procesanRecomendada
Minimizando el número de consultas mediante JoinsObligatoria
Reducir los helpers por defectoObligatoria
Borrar partes de la cache de forma selectivaObligatoria
Guardar los datos de la cache en una base de datosObligatoria

Optimizar el servidor

Es importante mantener el servidor bien configurado para optimizar las conexiones

Una aplicación bien optimizada debería ejecutarse en un servidor que también estuviera muy optimizado. Para asegurar que no existe un cuello de botella en los elementos externos a Symfony, se deberían conocer las técnicas básicas para optimizar los servidores. A continuación se muestran una serie de opciones que se deben comprobar para que el rendimiento del servidor no se vea penalizado.

Si la opción magic_quotes_gpc del archivo php.ini tiene asignado un valor de on, el rendimiento de la aplicación disminuye, ya que PHP añade mecanismos de escape a todas las comillas de los parámetros de la petición y Symfony después aplica los mecanismos inversos, por lo que el único efecto de esta opción es una pérdida de rendimiento y posibles problemas en algunos sistemas. Si se tiene acceso a la configuración de PHP, se debería desactivar esta opción.

El uso de un acelerador de PHP (como por ejemplo, APC, XCache, o eAccelerator) es casi obligatorio en un servidor de producción, ya que mejora el rendimiento de PHP en un 50% y no tiene ningún inconveniente. Para disfrutar de la auténtica velocidad de ejecución de PHP, es necesario instalar algún acelerador. Por otra parte, se deben desactivar en el servidor de producción todas las herramientas de depuración, como las extensiones Xdebug y APD.

Limitar el número de objetos que se procesan

Cuando se utiliza un método de una clase peer para obtener los objetos, el resultado de la consulta pasa el proceso de "hidratación" ("hydrating" en inglés) en el que se crean los objetos y se cargan con los datos de las filas devueltas en el resultado de la consulta. Para obtener por ejemplo todas las filas de la tabla articulo mediante Propel, se ejecuta la siguiente instrucción:

$articulos = ArticuloPeer::doSelect(new Criteria());

La variable $articulos resultante es un array con los objetos de tipo Article. Cada objeto se crea e inicializa, lo que requiere cierta cantidad de tiempo. La consecuencia de este comportamiento es que, al contrario de lo que sucede con las consultas a la base de datos, la velocidad de ejecución de una consulta Propel es directamente proporcional al número de resultados que devuelve. De esta forma, los métodos del modelo deberían optimizarse para devolver solamente un número limitado de resultados. Si no se necesitan todos los resultados devueltos por Criteria, se deberían limitar mediante los métodos setLimit() y setOffset(). Si solamente se necesitan por ejemplo las filas de datos de la 10 a la 20 para una consulta determinada, se puede refinar el objeto Criteria como se muestra:

$c = new Criteria();
$c->setOffset(10); // Posición de la primera fila que se obtiene
$c->setLimit(10); // Número de filas devueltas
$articulos = ArticuloPeer::doSelect($c);

El código anterior se puede automatizar utilizando un paginador. El objeto sfPropelPager gestiona de forma automática los valores offset y limit para una consulta Propel, de forma que solamente se crean los objetos mostrados en cada página.

Minimizando el número de consultas mediante Joins

Mientras se desarrolla una aplicación, se debe controlar el número de consultas a la base de datos que realiza cada petición. Si el número de consultas crece de forma desproporcionada, seguramente es necesario utilizar un Join.

Reducir los helpers por defecto

En cada petición se cargan los grupos de helpers estándar (Partial, Cache y Form). Si se está seguro de que no se van a utilizar los helpers de algún grupo, se puede eliminar este grupo de la lista de helpers estándar, lo que evita que se tenga que procesar el archivo del helper en cada petición. En concreto, el grupo de helpers de formularios (Form) es bastante grande y por tanto, ralentiza la ejecución de las páginas que no utilizan formularios. Por tanto, es una buena idea modificar la opción standard_helpers del archivo settings.yml para no incluirlo por defecto:

all:
.settings:
standard_helpers: [Partial, Cache] # Se elimina "Form"

El único inconveniente es que todas las plantillas que utilicen formularios tienen que declarar explícitamente que utilizan los helpers del grupo Form mediante la instrucción use_helper('Form').

Borrar partes de la cache de forma selectiva

Hacer un borrado selectivo de la cache es mas rápido y eficiente que borrar la cache de forma completa

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 los entornos de ejecución que no sean el de desarrollo. En este caso, es preciso borrar la cache de la carga automática para que Symfony recorrer 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 la depuración de aplicaciones esté desactivada) 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 project:deploy: este caso normalmente comprende las tres 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 cache:clear pueden definir un subconjunto de archivos a borrar de la cache, como muestra :

// Borrar sólo la cache de la aplicación "frontend"
> php symfony cache:clear frontend
// Borrar sólo la cache HTML de la aplicación "frontend"
> php symfony cache:clear frontend template
// Borrar sólo la cache de configuración de la aplicación "frontend"
> php symfony cache:clear frontend config

Guardar 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 y le permite 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.