viernes, noviembre 30, 2007

Amazon apuesta a Erlang

Amazon acaba de lanzar SimpleDB, un nuevo servicio de almacenamiento estructurado, con las características de alta disponibilidad y concurrencia de S3. Este es el segundo servicio basado en Erlang que lanza Amazon al mercado, el otro es el servicio de colas (Simple Queue Service), y es emocionante ver el rendimiento de la plataforma Erlang para la implementación de sistemas que deben operar confiablemente 24x7 y además proveer escalabilidad infinita, tanto en rendimiento como en capacidad.

Amazon, con su plataforma basada en Perl, Linux, Xen, y ahora Erlang, realmente le está sacando punta al software libre.

Pero veamos de que se trata este nuevo servicio que es el sueño mojado de los desarrolladores de la web 2.0.

SimpleDB no funciona como una base de datos relacional, no se necesita definir un esquema, en este sentido parece seguir la tendencia a la desaparición de las bases de datos relacionales, que mencioné en un artículo anterior.

Las características que más me gustan de este servicio:
  • Manipulación de conjuntos de datos realmente grandes (10GB por dominio y hasta 100 dominios por usuario)
  • Muy rápido (espero que se mantenga así)
  • Modelo de computación por demanda (solo se paga por lo que se usa)
  • Modelo de datos similar al utilizado por los lenguajes de scripts (Perl, Python, etc.), sin necesidad de definir esquemas.
  • Interfaz REST y también SOAP.
  • Fácil de usar, como los hashes en Perl :-)
  • Hecho en Erlang, me pregunto si usaron CouchDB ;-)

Sin embargo, hay que considerar algunos detalles operacionales:
  • Al igual que el servicio S3, los objetos almacenados no están disponibles inmediatamente, hasta que están replicados y se puede garantizar la disponibilidad de los mismos, de este modo Amazon se olvida de la C en ACID, para garantizar la durabilidad.
  • Todo es texto, las consultas se hacen lexicográficamente, así que los números deben formatearse apropiadamente, las fechas deben almacenarse en ISO-8601 (pero todos ustedes ya hacen esto, no?).
El modelo de datos es sencillamente espectacular y hace honor a su nombre, los datos se organizan en dominios, donde se pueden almacenar colecciones de atributos, donde cada atributo tener uno o más valores. Las colecciones de atributos pueden ser diferentes para cada objeto del dominio.

Para todos aquellos acostumbrados a lenguajes como Perl, Ruby, Python, etc. pueden imaginarse la estructura de datos como un arreglo que contiene hashes cuyos valores pueden ser escalares o listas.

El lenguaje de búsqueda provee los operadores: =, !=, <, >, <=, >=, STARTS-WITH, AND, OR, NOT, INTERSECTION AND UNION, pero las consultas están limitadas a un máximo de 5 segundos (aunque ninguna consulta de las que pude probar en las 8 horas que tiene el servicio, tardo más de algunos milisegundos).

Hay que mencionar que los costos de almacenamiento son muy superiores a los de S3, mientras que en este último almacenar un 1GB cuesta $0.15, en SimpleDB cuesta $1.50, es decir 10 veces más caro, aunque se puede transferir datos gratuitamente entre S3 y SimpleDB.

jueves, noviembre 08, 2007

Un lenguaje de ciencia ficción

Desde hace algún tiempo vengo trabajando cada vez más con Erlang, un lenguaje de programación de propósito general diseñado específicamente para la implementación de sistemas robustos, distribuidos y masivamente paralelos.

Erlang es un lenguaje funcional de evaluación estricta con tipos dinámicos y asignación única. En términos más prácticos, esto significa que no existe la asignación destructiva, las variables se declaran con un valor y no pueden cambiar (como en las matemáticas), así que se parecen más a las constantes de otros lenguajes, tampoco tiene estructuras de control de ciclos, ya que son inútiles en ausencia de la asignación destructiva.

Si piensa que un lenguaje así es prácticamente inútil, verifique si en su lenguaje favorito puede expresar el factorial de manera más concisa que en Erlang:

1 factorial(0) -> 1;
2 factorial(N) -> N * factorial(N-1).

El paradigma funcional, utilizando recursión, en conjunto con el reconocimiento de patrones (pattern matching), permiten expresar los algoritmos de manera clara y concisa, haciendo de la asignación un mal del pasado.

El ambiente de ejecución del lenguaje denominado: Open Telecom Platform (OTP), similar en funciones al JRE en Java, provee servicios de concurrencia, distribución, tolerancia a fallas, compilación, carga y reemplazo incremental de código a tiempo de ejecución, que en conjunto con los tipos dinámicos propician un ambiente para el desarrollo rápido de aplicaciones, compitiendo directamente con lenguajes como Perl y Ruby.

En el pasado se consideraba a los lenguajes funcionales como una curiosidad académica, se argumentaba que el paradigma funcional no escalaba como para implementar los sistemas complejos requeridos por la industria y el comercio.

Erlang/OTP demolió este mito, cuando Ericsson desarrolló el switch ATM: AXD-301, cuyo software contiene 1.7 millones de líneas en Erlang, adicionalmente Ericsson ha desarrollado otros sistemas de hasta 3.5 millones de líneas, que funcionan confiablemente desde hace años.

El lenguaje fue diseñado por Ericsson hace unos 20 años, para limitar la complejidad y mejorar la productividad del software desarrollado internamente, particularmente las centrales telefónicas, que además de ser masivamente paralelas, deben ser sistemas escalables y tolerar casi cualquier tipo de falla.

El resultado de todo esto es un plataforma de ejecución madura, cuyo lema es:
Evitar la programación defensiva
es decir, no maneje los errores, deje que alguien más lo haga.

En los demás lenguajes de programación se consume una cantidad considerable de recursos de diseño intentando manejar las anomalías durante la ejecución del programa, cuando en realidad se pueden presentar fallas que ni siquiera imaginamos, y que inútilmente intentamos manejar acomodando ligeramente los datos y delegando el problema.

En consecuencia obtenemos un montón de código de verificación de errores, que obscurece los algoritmos, complicando el mantenimiento del software y creando una pesadilla en la depuración, si el problema delegado finalmente causa un error, pues generalmente es difícil de relacionar con el evento original.

Erlang sigue el principio de diseño que expresa:
Si se va a fallar, se debe fallar rápido y ruidosamente

Facilitando la determinación del origen de la falla. Por ello los programas se diseñan para manejar el caso general, asumiendo que todo va a salir bien, abortando inmediatamente ante cualquier falla.

A primera vista esta estrategia no parece muy efectiva para lograr sistemas que deben operar continuamente tolerando cualquier tipo de fallas, pero lo hace, debido a una combinación de características del lenguaje y su plataforma de ejecución.

En Erlang el código de las aplicaciones se divide en procesos, definidos como unidades de ejecución autónomas, independientes y aisladas, que no comparten memoria, ni pueden afectarse entre sí. Por eso la falla de algunos no causa problemas a los demás, convirtiéndolos en un excelente mecanismo para contener los efectos de una falla.

Utilizando inteligentemente estas propiedades, se puede ingeniar un sistema donde además de procesos trabajadores, existan procesos supervisores, atentos a reparar las posibles fallas de otros procesos, OTP provee un sistema completo de supervisión de tareas, que permite la reactivación de procesos abortados e incluso cambiar el código de la aplicación o partes de ella, en caliente, es decir sin detener la ejecución de la misma. ¿Alguna vez su teléfono dejó de funcionar durante una actualización de software de la central telefónica?

Los supervisores se aseguran de registrar los eventos de falla y reiniciar a sus supervisados, según las políticas programadas, si alguno de los trabajadores muere con una frecuencia superior a la permitida, el supervisor mata al resto de sus trabajadores y aborta, escalando el problema al supervisor padre. Así se establece una jerarquía o árbol de supervisión, donde el máximo supervisor es parte de OTP, está muy bien depurado y maneja graciosamente casi cualquier tipo de falla. Garantizando que las aplicaciones diseñadas según la filosofía Erlang/OTP no fallan, aunque partes de la aplicación pueden fallar.

¿Será que esto es eficiente?

Si, considerando el paradigma, OTP es un sistema operativo completo que funciona dentro de un sistema operativo anfitrión como Linux, de hecho todo OTP es un solo proceso del S.O. anfitrión, aun cuando el kernel de OTP gestione muchos micro procesos internamente.

A diferencia de la semántica de protección de los sistemas operativos POSIX, los procesos de OTP están aislados automáticamente por la semántica de Erlang, carente de estado global y mutabilidad, permitiendo la implementación eficiente de los mismos, por ejemplo en mi laptop (un Centrino 1.7GHz, con 1GB RAM):

OperaciónErlangLinux
Cambiar de proceso400 ns2 us (5 veces más)
Arrancar un proceso5 us500 us (100 veces más)
Enviar un mensaje2 us800 us (400 veces más)

Adicionalmente un proceso recién creado consume solo unos 1500 bytes, que en las máquinas de hoy es prácticamente nada, otros sistemas como pthreads suelen consumir mucho más memoria, aún para programas de demostración sin utilidad práctica (pthreads en linux por defecto usa como 10MB! de RAM, aunque esto se puede ajustar cuando se crea un thread).

Dado que los procesos no pueden afectarse entre si, ni compartir memoria es pertinente la pregunta: ¿cómo cooperan?.

Erlang utiliza el modelo de actores, donde cada proceso es un actor que intercambia mensajes con los demás actores del sistema, el mecanismo de intercambio de mensajes está eficientemente implementado en el kernel de OTP y plenamente integrado con el lenguaje, mediante un operador y una estructura de control. Por ejemplo: si la variable Pid contiene una referencia a un proceso, podremos enviarle un mensaje con la expresión:

1 Pid ! "Este es un mensaje en un string"

En realidad cualquier término de datos del lenguaje puede enviarse en un mensaje, así que se puede intercambiar cualquier estructura arbitrariamente compleja, en este caso una tupla que contiene el átomo "dibujar" y una lista de otras 2 tuplas, etc.:

1 Mensaje = { dibujar, [ {elipse,1,1,4,6},
2 {cuadro,4,8,7,9} ] },
3 Proceso ! Mensaje

Este mensaje puede ser recibido por el Proceso referido, utilizando la instrucción receive:

1 receive
2 Msg -> procesar_mensaje(Msg)
3 end

Utilizando el reconocimiento de patrones, receive puede discriminar con facilidad entre diversos tipos de mensajes, para tomar las acciones correspondientes:

1 receive
2 {dibujar, Lista} ->
3 dibujar_elementos(Lista);
4 X when is_string(X) ->
5 dibujar_string(X);
6 X ->
7 alerta("Mensaje no identificado", X)
8 end

Si ningún patrón coincide, receive lanza una excepción que de no ser capturada, abortará la ejecución del proceso.

Al modularizar una aplicación como un conjunto de procesos que intercambian mensajes, se obtiene una aplicación dividida en celdas que contienen la propagación de los efectos de las fallas, pero además estos procesos podrían efectuar muchas de las operaciones concurrentemente, beneficiándose automáticamente de las arquitecturas de procesamiento paralelas existentes hoy en día.

OTP permite la operación distribuida, donde varias instancias forman una comunidad de nodos que se ejecutan en máquinas posiblemente separadas y se ofrece un servicio de páginas amarillas que permite el registro y ubicación de procesos por nombre. Una vez obtenida una referencia a un proceso mediante las páginas amarillas, no se diferencia entre las referencias a procesos remotos y a procesos locales, brindando un sistema de comunicación totalmente transparente.

OTP enruta, serializa y deserializa eficientemente los datos contenidos en cualquier mensaje. El protocolo de comunicación entre estos nodos es adaptable y puede ser reemplazado para aplicaciones específicas (si esta dispuesto a programar código de esa complejidad en C), la plataforma soporta TCP/IP por defecto, pero hay implementaciones para algunos otros protocolos.

Una aplicación diseñada para aprovechar el modo distribuido de Erlang, puede funcionar en una máquina de un procesador o escalar a varias máquinas con múltiples procesadores, sin cambiar una sola línea de código. La ausencia de estado global y mutabilidad permite que procesos diseñados bajo ciertos parámetros sencillos, sean reemplazados por nuevas versiones de código, mientras se ejecutan, sin perder la secuencia o estado de ejecución de los mismos!

El gestor de aplicaciones permite la especificación estática de un sistema distribuido, con capacidad de ejecutar las aplicaciones en un número fijo de nodos, con un sistema de supervisión que en segundos reconoce las fallas en un nodo, y arranca todos los actores desaparecidos durante la falla en otros nodos (failover), permitiendo la operación continua del sistema (con rendimiento degradado), además si el nodo original vuelve a funcionar, los procesos vuelven a su lugar como si nada hubiera pasado (takeover). Todo ello sucede automáticamente, sin necesidad de intervención del administrador.

Estas técnicas llevadas al extremo, combinadas con el uso del servicio de monitoreo de carga distribuido de OTP, permiten el diseño de sistemas con escalabilidad dinámica, en los cuales simplemente agregar un nodo a una comunidad causaría que los agentes en diversos nodos del sistema decidan mudarse para aprovechar el nuevo poder de procesamiento agregado a la comunidad. Esto es mucho más difícil de lograr que los failovers y takeovers, sin embargo, el que se pueda hacer, nos pone en el terreno de la ciencia ficción, muy cerca de historias como "Piso 13" o "La Matriz".

jueves, noviembre 01, 2007

Interés por el paralelismo

He notado a través del servicio de Feedjit que tengo a un lado del blog, que hay una buena cantidad de gente que llega a este blog mediante búsquedas en google, sobre tópicos sobre programación concurrente. Así que me voy a dedicar un poquito más a mostrar técnicas y código.

Por supuesto la probabilidad de que estas técnicas incluyan Java o C++ son bajas, aunque no nulas, porque por allí esta Hadoop una librería del proyecto apache hecha en Java por el proyecto apache, y que implementa el algoritmo Map/Reduce de google.

Aunque en realidad hacer algún ejercicio sobre Java me parece aburridisimo, Hadoop es una excelente alternativa para aprovechar el poder de computo de las nuevas nuves de computación elástica que están apareciendo por allí, liderizadas por la EC2 de Amazon.

Así que estén pendientes.

Trafico de influencias

En vista del giro de decisiones tomadas por Nigeria, evidenciadas en la carta abierta del presidente de Mandriva a Steve Ballmer (original en inglés), me impresiona como un país que asumo tiene un bajo nivel de vida como Nigeria, puede tomar la decisión de botar un producto que ya compró, para reemplazarlo por otro equivalente para sus fines, que deberá pagar nuevamente, sin haber siquiera probado el que ya compró. No me quejo de que cambien un producto libre por uno propietario, la desfachatez va mas allá, llegando al nivel de pagar dos veces por un producto.

El poder de lobby de las transnacionales es especialista en inyectar este tipo de corrupción, donde algún funcionario por meterse unos reales, causa graves daños a su nación, porque no solo es lo que se roba, sino que para robarselo debe hacer un gasto que seguramente es 5 veces mayor.

Adicionalmente estás transnacionales son mucho más eficientes en este juego en las naciones menos desarrolladas, donde la gente necesita más esos recursos, pero hay mucho menos capacidad de control de los mismos.