Lambda Architecture

Una vez más voy a escribir un artículo donde resumo y hago un pequeño ejemplo de otro libro sobre arquitectura que he leído últimamente. Se trata del popular Big Data: Principles and Best Practices of Scalable Real-time Data Systems de Nathan Marz y James Warren. En este libro se explican los principios básicos de las arquitecturas Big data, la arquitectura Lambda y un ejemplo de cómo implementarla. La arquitectura Lambda es utilizada para resolver problemas de Big Data, lo que quiere decir, problemas donde tenemos que gestionar grandes volúmenes de datos de manera escalable. En los últimos años la cantidad de datos que gestionan las compañías han crecido exponencialmente con el uso de las aplicaciones móviles y los dispositivos inteligentes. Los sistemas tradicionales han quedado colapsados y las compañías se ven obligadas a migrarlos a arquitecturas Big Data como Lambda.

La arquitectura Lambda contiene las siguientes capas:

  • Batch layer: esta capa procesa una copia de lo que es llamado «master data set» que es la colección de datos que componen la fuente de verdad, lo que llamamos hechos y genera a partir de ellos, las batch views, que no son más preprocesamientos de posibles futuras consultas, para poder atender peticiones al sistema de manera más rápida. Por ejemplo si en una arquitectura tradicional se quisieran consultar todos los eventos sobre la venta de un producto específico durante un periodo de tiempo sería necesario hacer una consulta a la base de datos trayendolos todos los eventos y sumarlos, lo que puede ser muy costoso si el sistema es grande y guarda cientos de miles de eventos. Sin embargo con Lambda architecture generaríamos en esta capa una vista preprocesada contando todos los eventos para cada producto y periodo de tiempo, consiguiendo que lo mismo se obtenga con una única consulta a la base de datos tendría una latencia muy baja. Como es evidente, esta capa se llama batch porque se ejecuta en segundo plano, con la frecuencia con la que se considere necesario según los requisitos del sistema. Para general las vistas se hace uso del popular algoritmo MapReduce.
  • Speed layer: esta capa es la encargada de crear las speed views que no son más que la información preprocesada que no ha sido capaz de ser generada por la batch layer desde su última ejecución. Mientras que la batch layer funciona de manera asíncrona en modo batch, la speed layer procesa los eventos en tiempo real.
  • Serving layer: responde a las consultas que tiene el sistema combinando la información preprocesada en las batch and speed views, combinándola cuando es necesario. De la combinación de todas las capas conseguimos baja latencia para consultar muchos datos (batch layer) con información muy actualizada hasta el último momento (speed layer).

La arquitectura Lambda tiene una serie de propiedades que nos son muy útiles:

  • Tolerante a fallos humanos: esta propiedad la tenemos gracias a que las vistas son generadas de nuevo en cada ejecución de la batch layer, por lo que si se detecta un error en algún algoritmo, se puede corregir y en la siguiente ejecución tendremos vistas correctas (la batch layer puede estar configurada para funcionar de manera incremental, con perjuicio de esta propiedad).
  • Tiene baja latencia en lecturas y escrituras: gracias al algoritmo Mapreduce, la capa de batch es capaz de procesar grandes cantidades de datos y crear vistas con consultas preprocesadas que son muchísimo más rápidas de consultar que su equivalencia en un sistema tradicional. Esta capa tiene el problema de que no genera vistas hasta el momento actual, ya que requiere de un tiempo de procesamiento a partir del cual no procesa hechos nuevos hasta la siguiente ejecución. Este inconveniente lo resolvemos con la speed layer que está procesando todos los eventos en tiempo real, permitiendo que la serving layer provea de respuestas con baja latencia y muy actualizadas.
  • Escalabilidad: la escalabilidad es la capacidad de mantener un nivel de rendimiento cuando aumenta el número de peticiones, añadiendo más máquinas de manera lineal, de manera que pueda ser controlada (un sistema no escalable puede requerir un aumento exponencial de recursos para aumentar la cantidad de peticiones que consume llegado cierto punto). Dado que la arquitectura Lambda utiliza el algoritmo MapReduce cumple con este requisito.
  • Fácil mantenimiento: Lambda permite un mantenimiento más sencillo y predecible que un sistema tradicional, al poder medir por adelantado cuánto tarda la batch layer en crear sus vistas con el nivel de datos que tenga de cada momento, y permitiéndonos actuar por adelantado, añadiendo más máquinas si es necesario.
  • Fácil depuración: es más fácil encontrar un error al generar las vistas, que en un sistema tradicional, ya que tenemos intactos los hechos que sirvieron de base para generarlas y podemos reproducir su generación. Esto mismo no se podría hacer con un sistema tradicional con entidades mutables en una base de datos, porque la sucesión de cambios haría imposible saber cómo era anteriormente la entidad y detectar en que punto del tiempo se produjo el error.

He preparado un ejemplo donde implemento de manera parcial la arquitectura Lambda  (batch y serving layer). El ejemplo consiste en un sistema para procesar consultas sobre la venta de productos de un comercio. Este sistema responde a dos tipos de consultas: número de productos vendidos por día y número de productos de una categoría vendidos por día. Para este ejemplo he utilizado Java y Sprint Boot como base de los proyectos, Spark para implementar el algoritmo MapReduce de la batch layer, Avro para serializar los hechos del master data set y Mongodb con base de datos No Sql. Podéis encontrar el ejemplo en mi GitHub.

El ejemplo contiene 3 proyectos.

  • sales-rest-api: esta es una sencilla API Rest que recibe mensajes por cada venta indicando el producto, la categoría de este y la fecha de compra. Esta información se almacena en el master data set que está contenido en ficheros con formato Avro.
  • sales-batch-service: este proyecto lee el master data set del fichero con formato Avro y genara las batch views usando Spark que son almacenadas en Mondodb. Este es un fragmento de código donde se implementa MapReduce:
 private JavaRDD getSaleJavaRDD() throws IOException {
        val sparkConf = new SparkConf().setMaster(LOCAL).setAppName(APP_NAME);
        val sc = new JavaSparkContext(sparkConf);
        val sales = saleRepository.getSales();
        return sc.parallelize(sales);
    }

    private List<Tuple2> countBy(JavaRDD perJavaRDD, Function function0) {
        val ones = perJavaRDD.mapToPair((PairFunction)
                s -> new Tuple2(new ProductView(function0.call(s), s.getSaleDate()), 1));
        val counts = ones.reduceByKey((Function2)
                (i1, i2) -> i1 + i2);
        return counts.collect();
    }

    private List map(List<Tuple2> nProduct, ViewType viewType) {
        return nProduct.stream().map(tuple -> {
            val productsByDayView = tuple._1;
            productsByDayView.setUnits(tuple._2);
            productsByDayView.setViewType(viewType);
            return productsByDayView;
        }).collect(Collectors.toList());
    }
  • sales-serving-service: es otra sencilla Api Rest que responde a las consultas utilizando las vistas preprocesada previamente por la batch layer y almacenadas en Mongodb.

Para completar el ejemplo faltaría implementar la speed layer y modificar la serving layer para que utilice sus vistas.

Resumiendo creo que la arquitectura Lambda es muy interesante y recomiendo la lectura de Big Data: Principles and best practices of scalable realtime data systems para tener una visión más completa de ella.

Deja un comentario