Todo lo que deberías saber sobre APC (II)

Configurando APC para un rendimiento óptimo
Aviso: si no has leído antes el primer artículo de la serie deberías hacerlo.

CONFIGURANDO APC para una óptima optimización optimizada

Como ya sabrás, las directivas de configuración se pueden cambiar en los ficheros .ini de configuración de PHP y sus extensiones. En el caso de Linux, probablemente el fichero esté en /etc/php5/apache2/conf.d/apc.ini, aunque también puedes añadir directivas en php.ini o cualquier otro fichero .ini del directorio conf.d/, ya que todos son cargados por el motor de PHP.

De todas las directivas de configuración que APC ofrece, creo que estas son las más jugosas:

  • apc.stat: indica si APC debe comprobar, a cada petición, si el archivo solicitado ha cambiado. En un servidor de producción es interesante desactivar esta opción ya que nos ahorraremos esa llamada al sistema. Pero ¡cuidado con los cambios de código! Para recompilar un script debes limpiar manualmente el APC o bien reiniciar Apache (con FastCGI no serviría).
  • apc.stat_ctime: comprueba que un archivo ha cambiado a través de los inodes, en lugar de con mtime.
  • apc.shm_size: especifica la memoria reservada para APC, en MB. Por defecto es 30.
  • apc.max_file_size: tamaño máximo de script cacheable.
  • apc.lazy_functions y apc.lazy_classes: cargan las funciones y clases de los includes sólo cuando son necesarios.
  • num_files_hint: aproximadamente cuántos scripts tienes en tu servidor. Ayuda a APC a optimizar sus índices internos. Si no estás seguro, mejor dejarlo a 0.
  • user_entries_hint: aproximadamente cuántas entradas de usuario guardarás. No estás seguro, mejor déjalo en 0.
  • apc.cache_by_defaultapc.filters: si pones apc.cache_by_default = 1, APC sólo cacheará los scripts cuyo nombre coincida con la expresión regular que especifiques en apc.filters. Interesante si tienes algunos scripts que sabes que van a cambiar muy frecuentemente (por ejemplo, durante un período de actualización) y has sido tan machote de desactivar apc.stat.
  • apc.include_once_override: se trata de una característica experimental, que se supone que optimiza las llamadas include_once y require_once para que consuman menos recursos. Dentro de algún tiempo tendremos más información sobre esta funcionalidad.

La extraña pero maravillosa directiva apc.rfc1867

Entre todas estas directivas de configuración destaca rfc1867, que obviamente hace referencia al documento RFC 1867, el cual especifica la subida de ficheros por HTML/HTTP. Pero ¿esto a qué viene?

Pues viene a que con APC, además de cachear los opcodes y guardar valores en la cache, también puedes controlar el progreso de las subidas de ficheros.

Sí, amigos. Si activamos la directiva apc.rfc1867, por cada fichero que se suba a tu servidor APC creará e irá actualizando una user entry con información sobre la subida. Esta entrada contendrá algo así como:

Array
(
    [total] => 1142543
    [current] => 1142543
    [rate] => 1828068.8
    [filename] => test
    [name] => file
    [temp_filename] => /tmp/php8F
    [cancel_upload] => 0
    [done] => 1
)

Es decir, el tamaño total del fichero, los bytes transferidos (current), la velocidad de subida (rate) y otros parámetros. Con esta valiosa información puedes hacer una bonita barra de progreso para indicar a tus usuarios cómo van sus subidas. Si quieres más detalles sobre cómo utilizar esta característica, te remito a este artículo y, como siempre, al manual oficial.

Si te has quedado con ganas de más

  • apc@facebook: presentación de 2007 donde se explica el funcionamiento de APC y se exponen los valores de configuración en los servidores de Facebook. Además explican su interesantísimo sistema para distribuir configuraciones en múltiples servidores a través de APC.
  • 2bits cuentan un caso interesante con Drupal y la discusión es también muy rica.
  • ¡El manual! No me canso de decirlo.

Esto no termina aquí

Te toca a ti. ¿Tienes experiencia con APC? ¿Has notado la mejora de rendimiento en tus servidores de producción? ¿Cómo lo has configurado? ¿Me das la contraseña de root?

Todo lo que deberías saber sobre APC (I)

Una de las formas más fáciles, a la vez que efectivas (un 300% de media), de hacer que tu sitio web PHP sea más rápido es usar un opcode cache. Hay varios en el mercado: APC, eAccelerator, XCache… vamos a ver APC por ser libre, el más fácil de manejar, extendido y el que mejores resultados arroja en los benchmarks.

Pero… ¿qué es un opcode cache?

Diagrama de flujo de APCComo seguramente sabrás, PHP es un lenguaje interpretado. Esto significa que cada vez que se solicita la ejecución de un script (o sea, cuando el usuario navega a una página concreta), este se compila y se ejecuta de forma automática, dando la sensación de que “no hay que compilar PHP”. Es un mito bastante extendido que PHP no se compila. Falso. PHP sí se compila, pero de forma automática. “Just In Time” (JIT), que dicen los anglosajones.

Este sistema de compilación JIT tiene la ventaja de una gestión más simple, sobre todo cuando tienes un script ya funcionando en el servidor y quieres modificarlo. Con PHP simplemente tienes que sustituir el fichero… y listo, la próxima vez que alguien acceda ejecutará la nueva versión.

Pero también tiene una desventaja: a cada visita se debe compilar el script. Y es una pérdida de tiempo, ya que si todos los visitantes de tu web ejecutan el mismo script, ¿para qué compilarlo una y otra vez? ¿No sería mejor compilarlo la primera vez, guardar ese script compilado (opcode) y ejecutarlo cuando alguien lo solicite?

Pues eso es exactamente lo que hacen los opcode cache: compilan el script la primera vez, lo guardan en memoria (RAM), y cuando se solicita, simplemente ejecutan el script compilado, logrando un aumento de velocidad bastante considerable.

Me has convencido. ¡Usemos APC!

APC (Another PHP Cache) es un sistema automático que compila los script sólo cuando es necesario. Viene empaquetado como una extensión de PHP, así que su instalación es sencilla. Y más si estamos usando Ubuntu o Debian Linux:

$ sudo apt-get install php-apc

Con sólo instalar este paquete (suponiendo que ya tengamos instalados Apache y PHP) ya tendremos APC funcionando correctamente. Si quieres más información sobre la instalación de APC puedes ir a la documentación oficial o en Google. Si tu proveedor de hosting no te ofrece APC… lo siento, es lo que tiene el hosting compartido :-P

Para saber si APC está funcionando correctamente, ejecutamos phpinfo():

APC como cache genérico

Además de “cachear” los scripts, APC sirve como almacenamiento genérico en memoria. Esto quiere decir que puedes guardar datos en la memoria de APC que estarán en RAM (por tanto, será de más rápido acceso que si los guardas en el disco duro o en una base de datos) y que podrás recuperar en cualquier momento. Eso sí, recuerda que si reinicias Apache o el sistema operativo, todos los datos que APC haya guardado en memoria se eliminarán.

Para almacenar una variable en la cache de APC puedes usar la función apc_add(), y para leerla, apc_fetch(). Por ejemplo:

/*
 * AVISO: este código tiene fines exclusivamente didácticos, y no debe ser
 * utilizado en aplicaciones reales en producción
 */
 
function leerPerfil() {
    $idUsuario = intval($_GET['idUsuario']);
 
    /**
     * Lee de la BD los datos del usuario $idUsuario. Si los datos están en cache,
     * no hacemos la consulta. Si no están, hacemos la consulta y guardamos los
     * datos en cache
     */
    if (empty($datosUsuario = apc_fetch("perfil_$idUsuario"))) {
        $query = mysql_query("SELECT * FROM usuarios WHERE id = '$idUsuario'");
        $datosUsuario = mysql_fetch_assoc($query);
        apc_add("perfil_$idUsuario", $datosUsuario);
    }
 
    return $datosUsuario;
}

Este sistema te permite hacer optimizaciones adicionales en tus aplicaciones (por ejemplo, guardar el resultado de una consulta a la base de datos para no tener que hacerla dos veces), pero tendrás que currártelas a mano. Tú eliges qué almacenar en APC. Pero ojo, si vas a hacer un uso intensivo de la caché, es mejor que uses un sistema de caché diferente al de los scripts. Por ejemplo, el famoso memcached (usado por sitios como Google o Facebook), que es un sistema de caché genérico, muy eficiente y muy fácil de usar (en serio, es muy fácil). Más información sobre funciones de APC en el manual de PHP.

Monitorizar APC

Otra de las ventajas de APC es que tiene un precioso panel de control. Es un script PHP al que puedes acceder haciendo un enlace desde tu directorio www público hasta la ubicación del script (en los directorios del sistema de PHP):

gunzip -c /usr/share/doc/php-apc/apc.php.gz > /var/www/apc.php

Si no funciona, o si no usas Linux puedes leer la documentación para instalar el panel de control de APC. Si no encuentras el script en tu sistema de ficheros puedes sencillamente descargarlo desde el Subversion de APC.

Aviso de seguridad: este script permite ver el estado de APC, revelando alguna información sobre el servidor que puede ser sensible por temas de seguridad. Es tu responsabilidad proteger el acceso al panel de control a través de configuraciones del servidor web, etc.

El panel de control tiene algunas opciones “públicas” y otras a las que sólo puedes acceder iniciando sesión con nombre de usuario y contraseña. Para cambiar el nombre de usuario y la clave puedes editar el propio script del panel de control, apc.php:

Cambiar la contraseña al panel de control de APC

El panel de control nos ofrece tres apartados:

  1. View Host Stats, donde tenemos un cuadro de control de APC con gráficas de consumo de memoria, información del sistema y datos de uso del cache.
  2. System Cache Entries: la lista de scripts cacheados, así como el número de hits (ejecuciones) que han tenido, su tamaño y fechas de último acceso/última modificación/creación.
  3. User Cache Entries: las variables que se hayan guardado en APC.

En el próximo artículo explicaremos más detalles sobre el panel de control de PHP y cómo interpretar los datos que nos brinda de cara optimizar aún más nuestro sitio web. Aunque si has llegado hasta aquí y has instalado APC en tu servidor ya has conseguido un gran ahorro de recursos en tu servidor :-)

Modelos experimentales de persistencia para aplicaciones web (II): Object Freezer-Relational

En el anterior post vimos un invento de Sebastian Bergmann llamado Object Freezer, que consiste en almacenar objetos PHP en CouchDB de forma automática. Es un buen método para no tener que definir esquemas de bases de datos, escribir sentencias SQL, hacer mapeo objeto-relacional… en definitiva, es una solución que nos puede ahorrar un montón de trabajo.

Pero, una vez aceptadas las ventajas de la congelación, quizá sea necesario guardar esos objetos en una base de datos diferente a CouchDB. Por ejemplo, en MySQL. Y de esto trata el presente artículo: de congelar objetos y guardarlos en MySQL.

Un brevísimo repaso a la congelación

Congelar un objeto (“freeze”) es convertirlo en un array manteniendo los objetos hijos y evitando duplicidad de los mismos. La operación inversa, la descongelación (“thaw”), sería recoger ese array y convertirlo de nuevo en el objeto original.

¿Por qué MySQL, si con NoSQL nos ahorramos todo el trabajo?

  1. A veces no es viable o rentable cambiar de servidor de bases de datos, por la inversión económica y de formación que requieren los sistemas tradicionales.
  2. Interoperabilidad: si en el futuro queremos migrar la aplicación, quizá poder manipular la información con SQL facilite la tarea.
  3. Rendimiento: las bases de datos relacionales se basan en algoritmos muy evolucionados y rápidos, y sistemas como MySQL-MyISAM han demostrado un rendimiento muy alto.

Entonces… ¿Se trata de guardar arrays en MySQL?

Exacto. Eso es lo que hace Object Freezer-Relational, una extensión de Object Freezer escrita por un servidor :-)

Lo primero que debemos pensar a la hora de guardar objetos en MySQL es: ¿cuál es el esquema de tablas? Porque si con CouchDB éramos NoSQL y guardábamos la información en arrays JSON, ahora tenemos que ceñirnos al álgebra relacional, las tablas, los índices y las claves primarias.

Así que este es el esquema que podemos usar:

  • Tabla objects
    • id: el ID único del objeto.
    • className: indica la clase de la cual es instancia el objeto almacenado.
    • isDirty y isRoot: dos atributos que utiliza Object Freezer internamente.
  • Tabla properties
    • name: nombre del atributo.
    • value: contiene el valor del atributo o bien un ID si el atributo hace referencia a otro objeto.
    • type: tipo de dato (int, float, string, boolean, array, objeto…).
    • object_id: ID del objeto al que pertenece el atributo. Hace referencia a objects.id.

Llegados a este punto uno podría pensar: “hey, te estás cargando el modelo relacional”. Efectivamente. Si queremos guardar objetos sin definir su esquema de BD, no nos queda más remedio que soluciones experimentales como la que te presento hoy. Sí, es una “des-normalización”, pero gracias a ella te ahorras escribir SQL.

Object Freezer-Relational se encarga de gestionar este esquema. De hecho, no es siquiera necesario crearlo, ya que lo hace automáticamente.

Vale, ya veo que mola. ¿cómo se usa?

Usar Object Freezer-Relational es tan sencillo como usar Object Freezer indicando los datos de acceso a MySQL:

$storage = new Object_Freezer_RelationalStorage(
    new Object_Freezer,
    NULL,
    FALSE,
    new MysqlStorage(
        "localhost",                    //Servidor MySQL
        "freezer",                      //Usuario
        "passw0rd",                     //Contraseña
        "freezer",                      //Base de datos
        3306,                           //Puerto
        MysqlStorage::ENGINE_INNODB));  //Motor MySQL

A partir de ahí, podemos usar el objeto $storage de la misma forma que el Object Freezer original, con CouchDB, ya que es la misma API.

Object Freezer-Relational está disponible en SourceForge. Puedes descargarlo y probarlo, echar un vistazo al código o leer la documentación si te interesa. Y por supuesto, comentar qué te parece la idea de guardar objetos en una base de datos MySQL :-)

Modelos experimentales de persistencia para aplicaciones web (I): congelación de objetos con Object Freezer

Hoy comienzo una serie de artículos dedicados a exponer diferentes patrones y arquitecturas de software para un problema tradicional: el almacenamiento de datos. Hacer consultas SQL a una base de datos relacional es una de las soluciones más extendidas, pero no es la única. Hay alternativas como las que te voy a presentar en este artículo y los siguientes.

Disclaimer

Las ideas que voy a presentar son experimentales. Son conceptos, con su discusón y su implementación (casi siempre parcial), pero no son estándares ampliamente probados y usados en el mundo real. El objetivo de estos artículos es reflexionar, discutir, probar y ver que algunas soluciones pueden ser útiles en ciertos escenarios, y otras quizá no valgan para nada. :-P

La congelación de objetos

La primera idea que voy a presentar es un concepto de Sebastian Bergmann. Los datos que se almacenan deben ser objetos de PHP. Se trata de “congelar” los objetos de una forma similar a como lo hacen las funciones de serialización. La diferencia es que el método de congelación entiende las relaciones entre objetos (es decir, que un atributo de un objeto sea, a su vez, otro objeto), de tal forma que si el objeto A tiene una referencia a B y el objeto C tiene una referencia a B también, al congelar los objetos A y C la referencia a B será un puntero al objeto congelado B, en lugar de congelar B dentro de A y B dentro de C, duplicando los datos, como hace serialize().

Pero ¿qué quiere decir congelar objetos? Pues sencillamente convertirlos en arrays para poder guardarlos en CouchDB, una base de datos orientada a documentos en la que los datos se almacenan en JSON.

Para comprender el funcionamiento de Object Freezer, nada mejor que una presentación de su autor: Cool Objects Sleep on the Couch.

¿Qué problemas soluciona Object Freezer?

  1. La definición y uso de esquemas relacionales. Ya no son necesarias las tablas, claves primarias, foreign keys, etc.
  2. El mapeo objeto-relacional. No hay que definir la conversión de tablas a objetos y viceversa.
  3. El almacenamiento sencillo de atributos vectoriales: es una forma fácil y automática de guardar arrays en la base de datos.
  4. ¡Tipado dinámico! En PHP se pueden añadir nuevos atributos a un objeto ya existente en tiempo de ejecución. No sería posible guardar este tipo de objetos en esquemas relacionales fijos, pero con Object Freezer es posible.

Además, tiene la ventaja de que, al guardar los objetos en CouchDB se maneja el versionado de los objetos, ya que CouchDB controla los cambios de todos sus registros.

Dame algo de código

//Una clase cualquiera
class Coche {
    private $color = 'rojo';
} 

//Una instancia de una clase cualquiera
$ferrari = new Coche();

require_once 'Object/Freezer/Storage/CouchDB.php'; 

//Instancia el freezer indicando los datos de conexión a CouchDB
$storage = new Object_Freezer_Storage_CouchDB('test', new Object_Freezer, TRUE, 'localhost', 5984); 

//Congela el objeto y lo almacena en CouchDB, devolviendo el ID auto-generado
$id = $storage->store($ferrari);

Así de sencillo. CouchDB asigna un ID único a cada nuevo registro. Para leer un registro:

//Devuelve el objeto de tipo Coche con $id
$objeto = $storage->fetch($id);

¿Es adecuado Object Freezer para mi proyecto?

La decisión de almacenar datos sin un esquema fijo es tan importante como la de usar una base de datos relacional o una NoSQL. ¿Es NoSQL bueno? No vamos a discutirlo aquí, pero sí citaré un tweet que leí una vez:

Usar NoSQL es como ir sin calzoncillos. Es cómodo, pero peligroso.

No obstante, hay una cuestión a tener en cuenta en la decisión: si usamos Object Freezer es cierto que la base de datos será schema-less, pero no es cierto que nuestros datos no estén estructurados, ya que ¡usamos objetos! Sería más correcto decir que los datos no están estructurados en la base de datos, pero sí en el código.

Si hemos hecho un buen diseño de las clases y sus relaciones, Object Freezer puede ser una solución rápida y fácil para guardar esos objetos en una base de datos muy interesante como CouchDB.

Sin embargo, no creo que sea buena idea para proyectos…

  • con fuerte necesidad de integridad referencial, o
  • que no estructuran toda la información con objetos, o
  • grandes, o
  • si crees que el modelo almacenamiento de datos cambiará, o
  • si no sabes usar las vistas de CouchDB para obtener listas de datos filtradas, indizadas, etc.

Creo que con esto es suficiente introducción. Puedes consultar la presentación que he citado arriba y el blog de Sebastian Bergmann para tener más información sobre Object Freezer, que por cierto es software libre :-)

Esto es todo por hoy. Ahora te toca a ti: ¿qué te parece el concepto de “congelar los objetos”? ¿Usarías Object Freezer? ¿Usarías CouchDB? ¿Conoces soluciones similares para otros lenguajes de programación?

Mejor rendimiento de Drupal con GraphicsMagick

Un pequeño (y espero útil) apunte antes de que penséis que he abandonado completamente el blog :-(

Se trata de un consejo para mejorar el rendimiento de nuestro sitio Drupal. El sistema utiliza por defecto la biblioteca GD para manipular imágenes (por ejemplo, generar miniaturas). El módulo ImageAPI inserta una capa de abstracción en Drupal que permite elegir qué biblioteca gráfica usar, GD o ImageMagick. Es mejor utilizar ImageMagick, ya que es una biblioteca independiente de PHP y, por ello obtendremos mejor rendimiento total en Drupal, y además es más estable (si falla GD puede caer todo el motor de PHP, sin embargo, si falla ImageMagick no caerá, ya que es un binario diferente).

Pero aún mejor: existe un proyecto llamado GraphicsMagick, que es un fork de ImageMagick y que es más estable y, sobre todo, con mejor rendimiento, ya que consume menos memoria y es más rápida. Y lo que es mejor, tiene la misma API que ImageMagick (el comando

convert), con lo cual podemos engañar a ImageAPI diciéndole que use ImageMagick cuando en realidad tenemos instalado GraphicsMagick.

Para lograr esto no tienes más que instalar GraphicsMagick y el módulo ImageAPI de Drupal, en dos sencillos pasos. Supongamos que trabajamos con un servidor Debian/Ubuntu (en otros sistemas operativos no debería ser muy diferente):

  1. Instalamos GraphicsMagick:
    apt-get install graphicsmagick-imagemagick-compatPodemos probar que GraphicsMagick funciona escribiendo convert en la consola:

isra@salon:~$ convert
GraphicsMagick 1.3.5 2009-01-26 Q8 http://www.GraphicsMagick.org/
Copyright (C) 2002-2009 GraphicsMagick Group.
Additional copyrights and licenses apply to this software.
See http://www.GraphicsMagick.org/www/Copyright.html for details.

Usage: convert [options ...] file [ [options ...] file …] [options ...] file

[más opciones]

  • Instalamos el módulo ImageAPI y lo activamos en /admin/build/modules (módulos ImageAPI e ImageAPI ImageMagick).
  • Accedemos a la configuración de ImageAPI en Administración > Configuración del sitio > ImageAPI (/admin/settings/imageapi) y veremos el siguiente mensaje: “The ImageAPI ImageMagick module is the only enabled image toolkit. Drupal will use it for resizing, cropping and other image manipulations“.
  • Si también hemos activado el módulo ImageAPI GD2, nos dará a elegir (seleccionamos ImageAPI ImageMagick como opción por defecto):

¡Y listo! Fácil y rápido. Podemos comprobar que funciona accediendo a Administración > Configuración del sitio > ImageAPI > Configurar (/admin/settings/imageapi/config), lo que nos mostrará información sobre GraphicsMagick (versión, tipos soportados, etc):

¿He hecho benchmarks para comprobar la ganancia de rendimiento con respecto a ImageMagick?

Pues no, lo siento. Pero los chicos de GraphicsMagick sí los han hecho, y son bastante jugosos.

Pero… ¿no es necesaria la extension imagick de PHP?

No, no es necesaria, ya que ImageAPI interactúa con ImageMagick/GraphicsMagick a través del binario
convert

Iré escribiendo algunos artículos más sobre técnicas fáciles para optimizar el rendimiento de Drupal.