Mirando con perspectiva

Great web engineers are generalists. To understand the web, you need to know a lot more than how to render variables in templates or pul objects from an ORM. You need to have spent time learning about networking, software architecture, web protocols and standards, graphic design, user interfaces, and TODO. Most of all, you need to be passionate about the web and the amazing ways it’s transforming our lives, nearly 20 years after HTTP and HTML were designed.

Visto en… ¡una oferta de trabajo!

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?

Migrando el blog a WordPress

Hace algunos días el blog dejó de funcionar, probablemente por una actualización de los servidores de mi proveedor, 1&1, que invalidaba las reglas de Apache rewrite que escribí. Sí, son cosas que apestan de los servidores compartidos, pero lo he aprovechado para “matar moscas a cañonazos” y migrar todo el blog a WordPress.

El antiguo blog funcionaba con un software PHP escrito por mí siguiendo el patrón Multivista y usaba plantillas Smarty. Mis razones para migrar a WordPress:

  1. Compatibilidad con los servidores: el .htaccess de WordPress es más tolerante que el mío a los cambios caprichosos de 1&1.
  2. Tener un software más seguro, más probado y más potente.
  3. Los experimentos que haga con el blog (sobre todo de cosas relacionadas con web semántica) los escribiré como plug-ins de WordPress y así otras personas los podrán probar.
  4. El back-end de WordPress es infinitamente mejor que el mío.
  5. Utilizar la plantilla Twenty Eleven como base para el diseño, por su calidad de código HTML5 y CSS.
  6. Seguir aprendiendo WordPress.
  7. Rendimiento: WP-Cache y WP Super Cache me ahorrarán un trabajo que me daba pereza empezar.

Este ha sido, a grandes rasgos, el procedimiento que he seguido para la migración:

Uno: migrando los artículos

Esto no ha sido difícil, ya que he escrito un sencillo plug-in que se conecta a la BD antigua, recorre todos los artículos (con sus tags) y los introduce en WordPress con la función wp_insert_post().

Dos: migrar los comentarios

Cuando ya tenía todo listo me di cuenta de que había olvidado migrar los comentarios… así que los migré no programando un plug-in (como hice con los artículos) sino exportando los comentarios de la antigua base de datos a CSV, adaptando las columnas a la tabla wp_comments e importándolo a esta con phpMyAdmin.

Tres: adaptando el theme

Me he basado en Twenty Eleven, el theme por defecto de WordPress 3.2.1, que utiliza HTML5, soporta widgets, traducción y muchas otras cosas. La labor de theming, cuando ya tienes el diseño maquetado (el antiguo blog) es más laborioso que difícil, así que en un par de horas estuve despachado. También he aprovechado para hacer algunos cambios en el diseño, intentando que quede más limpio y simple, y cambiando los enlaces para compartir los posts en redes sociales.

Cuatro: organizando el contenido

Unos minutos de taxonomías, tipos de entradas, categorías, etc para tenerlo todo bien ordenadito.

Cinco: desplegando

Con el contenido migrado y organizado y el theme adaptado, ya puedo instalar en el servidor una nueva instancia de WordPress, subir el theme e importar el contenido con la herramienta Importar/Exportar de WordPress. El resto de tareas las hago directamente en el servidor de producción.

Seis: instalar plug-ins y retoques finales

Con el blog ya montado y funcionando sobre WordPress, completo la sección de contacto con Contact Form 7, enlazo el RSS con Feedburner, inserto el código de Google Analytics en el theme, añado el plug-in para resaltado de código fuente WP-Syntax y reviso que no hay enlaces rotos en los artículos con Broken Link Checker.

Siete: redirecciones 301 para no perder posicionamiento en buscadores

Con el blog en WordPress, las antiguas URL de los artículos se pierden, así que para evitar que los buscadores reindexen todo el contenido de nuevo y pierda el posicionamiento de mis artículos, he configurado redirecciones para cada uno de los artículos. La cabecera 301 “moved permanently” le indica a los buscadores que la página ha sido movida permanentemente a otra dirección, pero que sigue siendo la misma página.

Las redirecciones las he puesto en un script que es llamado desde la plantilla 404.php del theme, de forma que cuando se solicita una URL antigua se produce un error 404 controlado por WordPress, pero antes de que se lance la cabecerar 404 y página de error, mi script comprueba si esa URL es de las antiguas y redirige, con una cabecera 301, a la URL correcta.

Ajustando la cache de consultas de MySQL para un mejor rendimiento de Drupal

¡Hola de nuevo! Después de un tiempo de inactividad voy a retomar el blog, aunque no prometo publicar muy asiduamente :-P

Continuando el hilo post anterior (mayo de 2010, ¡la de cosas que han pasado desde entonces!), vamos a ver cómo aumentar el rendimiento de un sitio Drupal, en este caso a través de la cache de consultas de MySQL.

En /admin/reports/status/sql, Drupal muestra algunas estadísticas interesantes del servidor MySQL, que nos dan pistas sobre lo bien o mal optimizado que tenemos el sistema: número de consultas cacheadas, ordenaciones sin índices, etc.

En la explicación de alguno de los indicadores se indica “debe ser cero”. Estos indicadores son Select_full_join, Select_range_check y Sort_scan. En términos generales, si estos valores son altos se debería modificar el esquema de la BD añadiendo índices, aunque si estamos hablando de Drupal esta situación no debería darse, ya que el esquema tiene índices correctamente establecidos.

Otro indicador interesante es Qcache_lowmem_prunes. Indica “el número de veces que MySQL tuvo que retirar consultas del caché porque se quedó sin memoria”. En mi caso, este indicador mostraba el alarmante valor de 349102 (¡desde que se arrancó el servidor!).

Drupal

Para arreglar esta situación vamos a aumentar el tamaño asignado a la caché de consultas. Lo haremos desde el fichero de configuración de MySQL (my.cnf):

query_cache_limit = 1M query_cache_size = 32M

El primer parámetro es el tamaño máximo de las consultas “cacheables”. Lo usaremos para evitar cachear consultas lentas o muy grandes, que suelen ser infrecuentes (y por tanto no tiene mucho sentido cachearlas).

El segundo parámetro es la cantidad de memoria que queremos asignar a la caché. Cuanta más memoria RAM tenga nuestro servidor, mayor valor podremos darle. En mi caso el servidor tiene 512MB, de los cuales alrededor de 200 están ocupados por el sistema y los servicios (Apache, el propio MySQL, memcache y los servicios de correo). Así que puedo aumentar el tamaño del cache hasta 50MB.

Después de reiniciar el servidor, lleva 95 días funcionando y arroja un valor de 3451 purgas. Muchas menos que con la anterior configuración.

Esta es una medida sencilla que ayuda a optimizar la cache de consultas de MySQL, un sistema que impacta en el rendimiento global del sistema, sobre todo si no tienes un sistema de caché fuera de MySQL.