Carga rápida y masiva de Memcache para Nginx

Hace años escribí sobre como uso Nginx como proxy de Apache en algunas instalaciones. En esa arquitectura contemplo Memcache. La configuración es muy sencilla, basta agregar a la sección location que queramos cachear lo siguiente:

set $memcached_key $uri;
memcached_pass 127.0.0.1:11211;
error_page 404 @fallback;

Y agregar el location @fallback correspondiente:

location @fallback {
proxy_pass http://localhost:8000;
}

El único problema, como algunas personas que han usado Nginx con Memcache, es que alguien tiene que llenar Memcache con objetos para que Nginx pueda leerlos.

Usualmente, los desarrolladores de la aplicación usarán las librerías del lenguaje de programación para acceder a Memcache y cargar allí algunos objetos. Esto funciona, y es como la mayoría de la gente implementa este escenario. Sin embargo, si uno quiere cargar varios archivos de forma rápida a Memcache, no hay muchas herramientas sencillas y fácilmente disponibles.

Por ejemplo, hace dos meses en la wiki de Nginx alguien publicó un método para precargar memcache con Python. Es un enfoque interesante, pero complicado de mantener y decididamente experimental.

Sin embargo, memcache ya incluye un cliente llamado memccp que permite cargar archivos en Memcache. El problema es que este cliente no permite definir la llave con la que el objeto se almacena en Memcache. Esa llave es $uri, por ejemplo algo como /wp-content/plugins/akismet/akismet.gif.

Cuando Nginx encuentra un cliente que hace GET a este archivo, lo sirve desde Memcache, lo que en este escenario nos ahorra abrir una conexión TCP a localhost, que Apache atienda y responda una petición, y potencialmente I/O de disco.

Este parche a libmemcached permite que se defina una clave con –key, lo que facilita precargar archivos como imágenes o CSS en Memcache. Su uso es sencillo y se puede invocar desde un shell script (probado en dash)

#!/bin/sh
BASE=”/var/www/mysite”
for file in `\
find $BASE -type f \
-name ‘*.jpg’ -or \
-name ‘*.gif’ -or \
-name ‘*.png’ \
| sed “s#$BASE##”`
do
echo “Adding $file to memcached…”
sudo memccp –key=$file –servers=localhost $BASE$file
done

Entre otros escenarios que puedes activar en este caso, está el poder almacenar archivos para distintos hosts virtuales. En este caso sugiero que configures $memcached_key para usar $http_host y $uri, y añadas una variable de prefijo a tu script. También puedes correr otro memcache, si en realidad lo necesitas. memccp tiene otros problemas, por ejemplo no maneja la codificación de caracteres muy bien. Pero para archivos binarios, usualmente estáticos, ahorra bastante trabajo.

El repositorio en GitHub es un paquete fuente de Debian. Si tienes las dependencias (sudo apt-get build-dep libmemcached-tools) puedes construir el paquete (dpkg-buildpackage -b) e instalar libmemcached-tools que contiene memccp.

Este escenario es uno de los que describo en mi próximo libro rápido sobre Debian para aplicaciones Web, que está actualmente en fase de edición.

Advertisements

Pure Linux-based oData producers: PHP and MySQL

I’m a big fan of oData, and if you’ve been following me for the past months, you know I’ve been fiddling around with the Ecuadorian Census data and other sources of information, kind of a stats/data freak. So open data was, of course, the natural path.

What I like about oData is that is simple, it’s Web-based, and it’s densely documented so that anyone can access the data served via oData: open data makes no sense if you need a software factory with all developers and DBAs to help you use it. And oData excels in consumer-side support, with plugins for in-memory BI solutions such as Tableau or PowerPivot and for other databases and Web-, Desktop-, Mobile- based applications.

Furthermore, govements can use free cloud services to publish their data (such as the Open Govement Data Initiative) but what abouts projects that need more tweaking and perhaps a deeper in-house integration?

I operate several servers with Debian, and I wanted to serve content via oData. My test scenario is the Ecuadorian Census data on ICT usage, based on about 70K interviews they perform every six months or so. They publish the database as a SAV file, which is an SPSS file. I can open this file with PSPP in either Windows or Linux and then puke data back in CSV format using a simple PSPP program (or, for the pragmatics among you, type the following two commands in the pspp shell):

GET FILE=”/tmp/inec.sav”
SAVE TRANSLATE /OUTFILE=”/tmp/inec.csv” /TYPE=CSV

Then, from the PSPP dictionary, I can see the fields and create a table (or two, or three) on a MySQL database. This is a lengthy process. I type really fast so I just transcribed all the column names from PSPP, but I’m sure there’s an easier way to get the column names automagically. So I create the about 150 columns of the INEC database and then I use LOAD DATA INFILE to get the CSV inside MySQL. Easy process, and I now have the 70K interviews in a database.

Let me introduce you to Microsoft’s (yes) OData Connector for MySQL. So this little guy here uses Doctrine to connect to the MySQL database and produce a set of PHP libraries that can be later converted into oData producers. The package will need PHP 5.3, and the php-xsl package in place. You will also need to download the OData Producer Library for PHP. Both libraries are open source and run on Linux, and won’t depend on .NET stuff (which of course wouldn’t be an issue, but…)

Make sure you download Doctrine ORM (I used 2.2.2) and it’s available (you can symlink) as the Doctrine directory wherever the MySQLConnector.php file is. Then run php MySQLConnector.php and follow the instructions. Note the service name you define here, you will need it later on. Make sure your tables all have primary keys defined, by the way. After it’s done, the library will puke files in ODataConnectorForMySQL/OutputFiles. We will use them later.

Now:

  1. Unpack the OData Producer Library for PHP in a convenient place of your Web server document root
  2. Delete everything in the services/ directory, as it has some samples we won’t use
  3. Move the service.config.xml generated by the MySQL Connector (OutputFiles directory) to the services/ directory
  4. Create a directory under services/ with the name of the service you defined when connecting to MySQL, and move the rest of OutputFiles in there
  5. Depending on your setup, you might need to symlink ODataProducer to library/ODataProducer in your DocumentRoot

A few backslashes made it to a couple of PHP files. I’ve reported the issue in Codeplex, but you will find error messages in error.log about them, if you want to correct them manually.

In terms of Apache configuration, you just need to make sure that all requests to resources ending in .svc get redirected to the index.php file, and that PHP searches in the DocumentRoot and DocumentRoot/library locations. I accomplish this as follows:

                RewriteEngine on
                RewriteRule (.svc.*) index.php
                php_value include_path “/var/www/odata:/var/www/odata/library”

You’re all set. Now Apache will be serving an oData feed with an URI similar to http://server/service.svc, where service is the name of the service you defined when running the MySQL Connector. You can consume the feed with several of oData consumers.

Happy hacking with open data!

Consideraciones para implementar IPv6 en Linux

Fui invitado por Amanda Mafla y la gente del FLISOL en Ibarra (Ecuador) para participar en un evento en la Universidad Técnica del Norte en el que realicé una presentación introductoria sobre IPv6.

Es la segunda vez que participo en un evento de la UTN, y nunca han fallado en la convocatoria, muy por encima de las cien personas, en esta oportunidad para compartir sobre IPv6. Así que gracias de nuevo a las autoridades y los organizadores por la invitación.

Lo primero que quiero aclarar es que el estilo de la presentación está a medio camino entre Lessig y Matz. Por eso las láminas podrían no mostrar información de manera estructurada o como referencia y les invito a que revisen los enlaces complementarios si necesitan más información.

Esta presentación es la primera en una serie de actividades que tengo preparadas sobre IPv6 durante el año, y la realizo el día antes de que se inicie la reunión anual de LACNIC en Quito.

Paralelizando clientes para pruebas de estrés en aplicaciones Web

El pasado 4 de junio tuve la oportunidad de compartir con la gente de Iguana Valley una presentación nivel 100 sobre balanceo de carga y alta disponibilidad en aplicaciones Web, con motivo del último RefreshUIO, un evento mensual de emprendimientos y tecnologías Web que se hace en la ciudad de Quito.

Además de presentar algunos escenarios de clusterización para balanceo de carga y/o alta disponibilidad, que ya he expuesto y documentado en el blog y en los artículos de mi Scribd previamente, y algunos nuevos escenarios interoperables que estaré presentando como parte de mi trabajo en los próximos días, algunas personas me preguntaron sobre el script que utilicé para generar carga sobre el cluster y demostrar el balanceo de carga que realizaba IPVS (LVS) en Linux.

Este script está en realidad basado en el concepto que desarrollé para las pruebas de stress de LDAP, utilizando Perl con threads. Recomiendo que lean ese artículo antes de utilizar este script. El código del script sigue a continuación, y lo único que deben indicar es el número de threads que deben ejecutarse y la URI a solicitar. El script utiliza LWP, y bota en STDOUT la salida decodificada de la ejecución, por lo que quizás quiera pasar la salida por grep o enviarla a algún otro sitio.

## plt-bambam.pl -- Web Server Stress Testing Adaptation#   (C) 2008-2011 José Miguel Parrella Romero ## This is free software, released under the terms of Perl itself.#use strict;use LWP::UserAgent;use threads;use threads::shared;$|=1;my @threads;## CONFIGURATION#my $uri = 'http://192.168.56.100/';my $count = 200;## END OF CONFIGURATION#my $ua = LWP::UserAgent->new;while ( $count ) {  push(@threads, threads->new(&query));  --$count;}foreach my $thread (@threads) {  $thread->join();}sub query {  my $res = $ua->get($uri);  print $res->decoded_content;}

Recapitulando escenarios de clusterización en Linux

Por allá en 2005, durante mi tiempo como consultor independiente, tuve mis primeras experiencias clusterizando servicios en Linux. Uno de los problemas que tuve para vender servicios clusterizados era la falta de experiencias documentadas en el área de servicios, y las percepciones sobre Linux siendo más capaz en el ámbito de clusterización de cómputo a nivel académico que en otros aspectos.

En 2006, cuando asumí una serie de responsabilidades en Electrificación del Caroní, ahora Corporación Eléctrica Nacional, tuve que ponerme la camiseta y empezar a evaluar ciertos escenarios de clusterización, que al final resultaron en un esquema geográficamente distribuido de servicios, tolerante a fallas y que provee servicios de calidad a una población de 8 mil usuarios y creciendo a varias decenas de miles con la integración a CORPOELEC.

Estos aprendizajes me permitieron luego, como CTO de una consultora inteacional, implementar modelos de clusterización adaptado a las necesidades de negocio de clientes en toda la región. Recientemente, un compañero de la comunidad de Linux de Paraguay me contactó pidiéndome mis opiniones sobre como hacer un cluster, y decidí recapitular aquí las alteativas.

Características del cluster

Lo primero que hay que preguntar es para qué se necesita el cluster, porque hay varias necesidades que se pueden cubrir sin necesidad de hacer un cluster. Por ejemplo, mucha gente quiere tener balanceo de carga en una aplicación Web, necesidad que se puede cubrir hoy en día con granjas de servidores y proxies reversos (para lo cual recomiendo nginx) a nivel del protocolo.

Otras personas pueden descubrir que el balanceo de carga más sencillo se logra con DNS round-robin, donde básicamente se agregan múltiples registros A para un host name, con el objeto de dejar en la pila de resolución DNS del sistema operativo de los clientes el trabajo de resolver y decidir a cuál equipo enviar las consultas.

Considere las siguientes características, y si realmente las necesita todas, entonces un cluster puede ser su solución:

  • Alta disponibilidad
  • Tolerancia a fallos
  • Balanceo de carga
  • Capacidad de cómputo distribuida
  • Almacenamiento compartido

Alta disponibilidad y tolerancia a fallos

Cuando se construye un cluster, se suele evitar que existan puntos únicos de falla. Estos puntos pueden estar presentes en el hardware o en el software, y en elementos activos o pasivos. Por ejemplo, un punto único de falla puede ser una conexión a fibra con un solo cable por cada servidor. O también puede ser un feature (o un bug) de la aplicación a clusterizar para manejar automáticamente casos de borde.

Un producto que suelo utilizar para cubrir esta necesidad es heartbeat. Es un producto maduro y estable, fácil de comprender y de configurar, que se encarga de mantener 2 o más servidores comunicados (idealmente a través de un canal independiente, como por ejemplo cables cross-over, o una red basada en Etheet con sus propios switches y múltiples cables) y de manejar servicios que los administradores del cluster han definidos como “altamente disponibles”.

Heartbeat incorpora características deseables como STONITH, que me permite hablar directamente con un regulador de voltaje, una toma multipuntos o incluso un UPS con el objetivo de “matar” a los nodos fallidos de forma definitiva (ya que solo hay algo peor que un cluster donde fallen los nodos, y eso sería un cluster donde fallen los nodos y vuelvan a la vida intermitentemente) y por supuesto Heartbeat se encarga de la parte sucia como hacer quorum entre los miembros para determinar si uno de los servidores está definitivamente desactivado.

Una estrategia para reducir puntos únicos de falla en la capa de red es utilizar channel bonding. Se trata de cargar un módulo del keel con parámetros para indicar el comportamiento deseado del bonding, y de configurar una nueva interfaz de red que se constituye de las interfaces “esclavas” tradicionales. Por ejemplo, bond0 puede ser una unión en failover de eth0 y eth1, o puede ser un agregado de ambos canales. El primer escenario, obviamente, es el que contribuye a la alta disponibilidad.

En cuanto a fibra óptica, que podemos utilizar usualmente para mirar los volumenes que presenta un arreglo de discos o una SAN, podemos utilizar multipath para garantizar que llegamos al dispositivo por múltiples caminos (múltiples cables, múltiples puertos)

Balanceo de carga y capacidad de cómputo distribuida

Además del mecanismo básico de DNS round-robin, hay veces donde no podemos confiar en que la resolución DNS del cliente sea óptima, o cuando tenemos alguna aplicación malvada que no soporta resolución DNS y debemos usar IPs directamente.

Aquí, Linux Virtual Server me ha ayudado muchísimo. Con LVS se pueden configurar “entry points” al cluster, que a su vez pueden ser otros LVS o usar DNS round-robin, o quizás distribución geográfica. Esos entry points distribuyen la carga a servicios UDP y TCP en múltiples servidores usando algoritmos que los administradores del cluster pueden seleccionar. También ofrecen herramientas para monitorizar la salud de la clusterización del servicio.

Una técnica interesante que he utilizado es la de “vistas” del DNS. BIND 9, de ISC, soporta esto. Con las vistas puedo tomar una misma zona (por ejemplo, empresa.int) y devolver respuestas distintas dependiendo del origen de la consulta. En una gran empresa, esto significa que puedo usar un mismo nombre de dominio en las aplicaciones (por ejemplo, correo.empresa.int) y que el DNS me devuelva los resultados correspondiente a los servidores lógicamente más cercanos (por ejemplo, caracas.correo.empresa.int, si la consulta vino de la subred de Caracas)

Obviamente, los servicios de LVS, así como los servicios finales que se van a clusterizar (OpenLDAP, Postfix, Apache, Dovecot, lo que usted quiera), pueden ser gestionados por Heartbeat.

Concurrencia y almacenamiento compartido

Sin embargo, no todas las aplicaciones son fácilmente clusterizables. Por ejemplo, una galería de fotos es fácil de clusterizar ya que puedo clusterizar HTTP (con Apache, por ejemplo) y puedo mantener manualmente al día la galería en ambos servidores. Pero esto me agrega complejidades administrativas, y no es cierto para todas las aplicaciones — imagine por ejemplo una aplicación Web con un servidor de bases de datos. Clusterizar la aplicación Web generará rápidamente inconsistencias en la base de datos.

Clusterizar bases de datos es un terreno específico y autocontenido, pero el estado del arte ha mejorado mucho en los últimos 5 años que empecé a tocar estos temas. Tanto PostgreSQL como MySQL ya tienen mecanismos avanzados en capa de aplicación para hacerlo (antes solo Postgres, y ha evolucionado mucho) y recomendaría considerar cosas como Slony en Postgres, o inclusive soluciones lógicas como rubyrep en bases de datos no muy complejas.

Lo que es más común es clusterizar el almacenamiento. Aquí hay tres escuelas: las que usan SAN, las que usan NAS, y las que usan el almacenamiento de cada servidor. En este último caso hay que recurrir a algún mecanismo de sincronización, que casi siempre es rsync. Es una solución realmente artesanal. SAN y NAS ofrecen distintas ventajas. Por ejemplo en SAN, es posible que yo cree sistemas de archivos que sean conscientes de que pertenecen a un cluster, como puede ser el Global File System de Red Hat, y es más elegante (nuevamente, es mi percepción) ya que, por ejemplo, utilizando fibra óptica (y multipath) puedo ver directamente los dispositivos de bloques en Linux, aunque tengo la certeza de que hay clusterización a nivel de bloque.

NAS, por otro lado, puede resultar mucho más sencillo de administrar. Simplemente elijo un protocolo, que usualmente es SMB/CIFS o NFS (y casi siempre se elige NFS) y empiezo a escribir allí. El aparato se
encarga de hacer arreglos de discos, manejar la redundancia, resistencia a fallos, entre otros. Obviamente, soluciones avanzadas en SAN y NAS me ayudan a evitar puntos únicos de falla en los discos, pero suele ser bastante costoso. Además, NFS no es “cluster aware”, por lo que necesito configurar locking para evitar que varios nodos escriban los mismos archivos al mismo tiempo. Esta situación es aun más grave si consideramos que se puede estar usando ese almacenamiento compartido para bases de datos.

Leyendo a Hardy Beltrán de Bolivia, me acordé de DRBD. En su momento evalué GFS junto a otros sistemas de archivos para SAN, como OCFS2, y finalmente nos decidimos por GFS. Pero en un proyecto de clusterización de servicios de red y directorio que ejecuté posteriormente utilizamos DRBD, que puede definirse como RAID-1 por red, siendo especialmente útil en escenarios de dos equipos. Este cluster corría DNS, DHCP, IPP (CUPS), LDAP, bases de datos MySQL y Apache en alta disponibilidad.

Productizando el cluster

Según mi experiencia, si planea utilizar Global File System para el almacenamiento compartido, vale la pena que considere toda la solución de Red Hat Cluster Suite, y que la ejecute en un sistema derivado de RHEL, como puede ser CentOS. Yo he implementado todo en Debian, y solamente GFS, pero genera más carga administrativa.

De otra forma, yo recomiendo considerar Heartbeat y Linux Virtual Server, y revisar alteativas de NAS. Si bien el objetivo es siempre evitar los puntos únicos de falla, en ocasiones las empresas tienen que hacer compromisos y normalmente eso ocurre en el espacio de almacenamiento. Si bien un punto único de falla romperá las expectativas del cluster (always on) al menos tener sanas políticas de respaldo ayudará a minimizar los tiempos de caída.

Finalmente, todo lo expuesto aquí cubre clusteres de servicios. Hacer clusteres de cómputo (Beowulf and the like) es otra disciplina, sobre la cual, si están interesados, les recomendaría consultar con mi amigo Julio César Ortega, una de las personas en Venezuela que tiene más experiencia en ambos modelos..