Mejorando el rendimiento de nuestra web cakephp

Mediante la aplicación de algunas modificaciones sencillas, ó  algunas mejoras más complejas, CakePHP se puede acelerar bastante. Una vez que apliquemos estos consejos el rendimiento de nuestra web cakephp  será comparable a muchos otros frameworks PHP más populares.

Hay dos tipos de modificaciones que se describen en este artículo . La primera es, por ejemplo, cambios en el código, éstos funcionaran para todos, incluso si se está trabajado en un entorno compartido . El segundo tipo está dirigido a los usuarios que tienen su propio servidor dedicado o virtual que se pueden agregar y quitar software según sea necesario.

Aunque sólo puedas aplicar los consejos de modificación de código, no te decepcionarán.

Actualizar la versión de cakephp

Es importante señalar que existe una concepción errónea por parte de la comunidad de desarrolladores sobre la velocidad de cakephp. Esta sensación de lentitud proviene las primeras versiones de cakephp la 1.1 y la 1.2. En la versión 1.3, el equipo de desarrollo del núcleo llevó a cabo una seria revisión de la arquitectura subyacente de cakephp. De hecho se llevaron a cabo pruebas de rendimiento entre las versiones 1.2, 1.3 y 2.0. Estas pruebas relevaron unos datos sorprendentes. Simplemente actualizando de la 1.2 a la 1.3, hay una disminución inmediata de mas del 70% en el tiempo medio de carga de una página.

Si estás retrasando el actualizar tu versión de CakePHP y  todavía está utilizando la versión 1.2 o anterior, lo primero que debes hacer es actualizarla al menos a la versión 1.3, pero sería preferible la actualización a la 2.4.0.

El Cookbook de CakePHP proporciona algunas guías muy detalladas de migración:

Desactivar el modo depuración

Esto puede parecer obvio, pero con las prisas de mover el código a  producción  puede ser muy fácil olvidarse de desactivar la depuración. Un protocolo típico que sigo es crear varios archivos  core.php: uno para mi entorno local, una para dev, uno para puesta en escena, y uno para la producción. Estos archivos contienen las diferentes configuraciones en función del entorno de destino.

Hay que cambiar la declaración: Configure::write('debug', 2) to Configure::write('debug', 0).

Este cambio hace que no se muestren los mensajes de error y ya no actualiza el modelo cachés. Esto es muy importante porque, por defecto, cada carga de la página hace que todos sus modelos se generen de forma dinámica en lugar de utilizar la caché.

Desactivar la recursividad en las declaraciones find

Al realizar una consulta utilizando el método find(), la recursividad se establece en 0 por defecto. Esto indica que CakePHP debe tratar de unirse a los modelos afines de primer nivel. Por ejemplo, si tienes  un modelo de usuario que tiene muchos comentarios de los usuarios, cada vez que se realice una consulta en la tabla de usuarios se vinculará la tabla de comentarios.

La creación de la matriz asociativa puede hacer que el tiempo de proceso de ejecución se dilate mucho, relentizando mucho la web.

Mi método método favorito para asegurarme de que se desactiva la recursión por defecto es emplazar el siguiente código en el archivo App / modelo / AppModel.php.

class AppModel extends Model {
    public $recursive = -1;
}
        

Cachear los resultados de la consultas

En muchas aplicaciones web, es probable que haya una gran cantidad de consultas que realmente no se necesitas . Al reemplazar el valor predeterminado  de la función find () dentro de app / Modelo / AppModel.php, he hecho que sea fácil de almacenar en caché los resultados  de la matriz asociativas de todas las consultas. Esto significa que no sólo evito consultar a la base de datos, incluso evito el tiempo de procesamiento que emplea CakePHP en convertir los resultados en una matriz. El código necesario es el siguiente:

<?php class AppModel extends Model { public $recursive = -1; function find($conditions = null, $fields = array(), $order = null, $recursive = null) { $doQuery = true; // check if we want the cache if (!empty($fields['cache'])) { $cacheConfig = null; // check if we have specified a custom config if (!empty($fields['cacheConfig'])) { $cacheConfig = $fields['cacheConfig']; } $cacheName = $this->name . '-' . $fields['cache'];
            // if so, check if the cache exists
            $data = Cache::read($cacheName, $cacheConfig);
            if ($data == false) {
                $data = parent::find($conditions, $fields,
                    $order, $recursive);
                Cache::write($cacheName, $data, $cacheConfig);
            }
            $doQuery = false;
        }
        if ($doQuery) {
            $data = parent::find($conditions, $fields, $order,
                $recursive);
        }
        return $data;
    }
}
    

Se necesita hacer cambios sutiles en las consultas que deseas que se cacheen. Una consulta básica podría ser esta:

$this->User->find('list');

    

requiere actualización de la información de cacheo:

    $this->User->find('list',array('cache'=> 'userList','cacheConfig'=> 'short');

    

Se han añadido dos valores adicionales: el nombre de la caché y la configuración de la caché que será utilizada.

Finalmente se han de realizar dos cambios app/Config/core.php. El cacheo debe ser activado y el valor thecacheConfig debe ser definido. Primero descomenta la siguiente linea:

Configure::write('Cache.check', true);

Después, añade la nueva configuración de caché como se indica a continuación ( actualizando los parámetros requeridos para el nombre y el tiempo de expiración ).

Cache::config('short', array(
'engine' => 'File',
'duration'=>'+5 minutes',
'probability'=> 100,
'path'=> CACHE,
'prefix' => 'cache_short_'
));

Todas las opciones anteriores se pueden actualizar para ampliar la duración, cambiar el nombre, o incluso definir dónde se debe almacenar la caché.

No consideres esta como la única utilidad de la caché. Se pueden almacenar en caché las acciones del controlador, vistas, e incluso funciones de ayuda. Para más información consulta el componente de caché en el libro CakePHP.

Instalar cache basada en memoria

Por defecto, las sesiones PHP  se almacenan en disco, generalmente en un directorio temporal. Esto hace que cada vez que se accede a los datos de sesión PHP tenga que abrir el archivo de sesión y decodificar la información que contiene. El problema reside en que la operación de acceso al disco duro puede consumir bastantes recursos. Acceder a estos datos en memoria es mucho más rápido que ir a buscarlos al disco.

Hay dos maneras mucho más elegantes de abordar las sesiones guardadas en disco.  La primera consiste en configurar un disco ram en tu servidor. Una vez configurado, la unidad se montará como cualquier otro disco, actualizando el valor de la variable session.save_path del fichero php.ini la cual indicará el punto de montaje del nuevo disco RAM.  La otra manera es instalando un software cache como Memcached, que permitirá que los objetos sean almacenados en memoria.

Si te estás preguntando qué método es mejor, la manera de decidir esto es respondiendo a la siguiente pregunta: ¿Se necesitará más de un servidor para acceder a esta memoria al mismo tiempo? Si es así,  querrás elegir Memcached, ya que se puede instalar en un sistema independiente que permite a otros servidores acceder a ella. Considerando que, se está buscando acelerar su servidor web sencillo, la elección de la solución de disco RAM es sencilla y rápida y no requiere ningún software adicional.

Dependiendo de sus sistema operativo, instalar memcached puede ser tan sencillo como escribir sudo aptitude install memcached.

Una ve instalado, puedes configurar PHP para que almacene las sesiones en memoria en vez de disco actualizando su php.ini.

session.save_handler = memcache
session.save_path = 'tcp://127.0.0.1:11211'

Si Memcached ha sido instalado en un host ó puerto diferentes, entonces actualiza esta información en consonancia.

Una vez que has finalizado la instalación de Memcached en tu servidor, necesitaás instalar el módulo PHP memcache. Dependiendo de tu sistema operativo el siguiente comando podría valer:

pecl install memcache

ó:

sudo aptitude install php5-memcache

Sustituir Apache por Nginx

De acuerdo con las últimas estadísticas Apache es el servidor web favorito. A día de hoy  la idea de la adopción Ngnix como sustituto oficial de Apache es muy prematura, pero realmente para sitios web de mucho tráfico Nginx se está convirtiendo en un sustituto muy popular de Apache.

Apache es como Microsoft Word, tiene un millón de opciones pero sólo necesitas seis. Nginx tiene estas seis cosas, y cinco de ellas hace que funcione cincuenta veces más rápido que Apache. — Chris Lea
Nginx se diferencia de Apache en que es un servidor basado en procesos, mientras que Nginx se basa en en ventos. Cuando la carga de tu servidor web aumenta, Apache rápidamente comienza comerse tu memoria. Para manejar adecuadamente las nuevas solicitudes, los procesos de  Apache crean nuevos hilos que  causan  un aumento del uso  de la memoria y del tiempo de espera en la creación de nuevos hilos. Por el contrario, Nginx se ejecuta de forma asíncrona y utiliza uno o muy pocos hilos con un tamaño muy pequeño.

Nginix es extremadamente rápido sirviendo archivos estáticos, así que si no estás usando un CDN ( Red de distribución de Contenido ) deberás  de considerar seriamente utilizar Nginx.

Al final, en cuestión de memoria, Nginx  consume de media   20-30Mb. mientras que  Apache podría estar consumiendo más de  200 Mb para la misma carga. La memoria puede ser barata en un PC, pero no cuando pagas por su uso en servidores en Cloud.

Para ver mas diferencias entre Apache y Nginx puedes visitar la página Apache vs Nginx WikiVS.

HowToForge  tiene un excelente artículo sobre como configurar Nginx en tu servido. Te aconsejo que sigas paso a paso el tutorial de instalación y configuración de Nginx con php-fpm.

Configurar Nginx para usar Memcached

Una vez que hayas instalado Nginx y Memcached,  otras páginas instaladas en tu servidor podrán mejorar también su rendimiento. A pesar de que las aplicaciones CakePHP son dinámicas, es probable que el 80-90% del contenido de la web siga siendo estático - lo que significa que sólo cambiarán algunas partes de la web.

Al hacer las siguientes modificaciones en el archivo de configuración de Nginx, Memcached comenzará atender las peticiones Nginx que ya han sido tratadas y almacenadas en la memoria. Esto significa que en realidad sólo unas pocas peticiones llamarán a PHP, lo cual  aumentará significativamente la velocidad de su sitio web.

server {
    listen 80;
    server_name endyourif.com www.endyourif.com;
    access_log /var/log/nginx/endyourif-access.log;
    error_log /var/log/nginx/endyourif-error.log;
    root /www/endyourif.com/;
    index index.php index.html index.htm;

    # serve static files
    location / {
        # this serves static files that exists without
        # running other rewrite tests
        if (-f $request_filename) {
            expires 30d;
            break;
        }
        # this sends all-non-existing file or directory requests
        # to index.php
        if (!-e $request_filename) {
            rewrite ^(.+)$ /index.php?q=$1 last;
        }
    }

    location ~ .php$ {
        set $memcached_key '$request_uri';
        memcached_pass 127.0.0.1:11211;
        default_type       text/html;
        error_page 404 405 502 = @no_cache;
    }

    location @no_cache {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+.php)(/.+)$;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }
}

La configuración anterior especifica que los archivos estáticos se deben servir inmediatamente. Lo archivos PHP se deben dirigir a Memcached para servir la página si esta ha sido almacenada en caché. Si no existe en la caché, se llega a thte@nocache que actúa como la configuración normal de PHP para servir a la página a través de servidor  php-FPM.

No olvides actualizar tu app/Config/core.php para que utilice Memcache:

1
2
3
4
5
<?php
$engine = 'File';
if (extension_loaded('apc') && function_exists('apc_dec') && (php_sapi_name() !== 'cli' || ini_get('apc.enable_cli'))) {
    $engine = 'Apc';
}

Poner:

$engine= 'Memcache';

Nuestro código para el almacenamiento en caché de consultas muy usadas también requiere actualizarse:

Cache::config('short', array(
'engine' => 'Memcache',
'duration' =>'+5 minutes',
'probability' => 100,
'path' => CACHE,
'prefix' => 'cache_short_'
));

Desinstalar  MySQL e instalar Percona

La modificación final que recomiendo en el servidor es la instalación de Percona. El equipo Percona ha invertido muchos años en el estudio, comprensión y perfeccionamiento de la arquitectura de la base de datos para un rendimiento óptimo. Lo mejor es seguir las instrucciones de instalación de Percona ya que describe diferentes opciones de instalación.

Conclusiones

Hay un montón de técnicas para asegurar CakePHP se ejecutará la velocidad del rayo. Algunos cambios pueden ser más difíciles de realizar que otros, pero no hay que desanimarse. No importa cuál sea tu framework de desarrollo, la optimización tiene un coste, y la capacidad de CakePHP para desarrollar rápidamente aplicaciones web muy complejas será mayor que  el esfuerzo empleado en la optimización.

Volver al índice del blog