Sin duda alguna cada vez nos enfrentamos a retos tecnológicos que implican mayor volumen y velocidad (frecuencia) de datos, dos de las cinco V’s del Big Data. Y en muchos de estos caso una de la decisiones de arquitectura que debe tomarse es la persistencia. En los últimos años con el crecimiento de IoT en las diferentes industrias y sectores, se ha empezado a popularizar un tipo bases de datos que permiten mantener un histórico del estado a través del tiempo de los diferentes sensores y sus mediciones. Además, permiten soportar grandes volúmenes de datos, con una alta escalabilidad y bajo tiempo de respuesta tanto en la ingesta como en el consumo. Nos referimos a las base de datos basados en series de tiempo o “Time Series”. En esta publicación vamos a ver en detalle Apache Druid, y algunas recomendaciones para poder optimizar su uso.

Apache Druid es un proyecto Open Source, y forma parte del Fundación Apache desde 2015. Se describe como una base de datos analítica en tiempo real para grandes volúmenes de datos, donde el rendimiento y la alta disponibilidad son factor vital de la solución final. Algunos de los caso de uso puede ser:

  • Streaming de eventos (web and mobile analytics).
  • Analítica de redes (monitoreo y performance).
  • Métricas de servidores.
  • IoT.
  • Datos Financieros como Bolsa o Forex.
  • Y en general cualquier tipo de datos cuyo hilo central sea el tiempo.

Entre algunas de sus principales características podemos destacar:

  • Base de datos columnar.
  • Sistema escalable y distribuido. 
  • Procesamiento paralelo a través de cluster.
  • Ingesta en tiempo real y batch.
  • Auto balanceo y auto recuperación.
  • Soporte de almacenamiento de segmentos en HDFS o S3.
  • Particionado por tiempo.
  • Agregaciones en tiempo de ingesta.
  • Business intelligence / OLAP.

Comprendiendo mejor Apache Druid

A pesar de las múltiples ventajas que ofrece, existen algunas consideraciones que se deben tener en cuenta en el momento de implementar como solución de persistencia. En esta ocasión vamos presentar los conceptos más relevantes  a la hora de modelar los datos en Apache Druid. 

  • Datasource: De la misma manera que en la Base de Datos tradicionales utilizan tablas para almacenar los datos, Druid hace uso de los datasources.
  • Timestamp: recordemos que Druid es una base de datos tipo Timeseries, por lo cual todo gira alrededor del tiempo. Siendo esta columna no sólo obligatoria por cada registro, sino como pieza central de su funcionamiento, la cual se utiliza para el particionado y además es obligatorio en todas las consultas. Cabe mencionar que aunque al momento de ingestar la columna que actuará como Timestamp puede tener cualquier nombre, Druid la almacenará con el nombre de “__time”. Asimismo, el formato esperado se puede especificar con el parámetro timestampSpec, como veremos en el ejemplo.
  • Dimensiones: Son todas las columnas de uso general, que nos permiten agrupar, filtrar e inclusive agregar al momento de consulta. En caso de no hacer uso de rollups, se puede entender a las dimensiones como las columnas o atributos de bases de datos tradicionales. Las dimensiones se configuran a través de la propiedad dimensionsSpec.
  • Métricas: Son aquellas columnas que se almacenan, generalmente, de forma agregada. En este sentido requieren de una función de agregación  que aplicará al momento de ingesta. Esto permite precomputar agregaciones sobre los datos, lo que incrementa el rendimiento de las consultas. Las métricas se configuran mediante la propiedad de metricsSpec.

Ya que hemos mencionado el concepto de Rollup, vale la pena definirlo. Es además, una de la funcionalidades más relevantes de Apache Druid. Esta no da la posibilidad de pre agregar los datos durante la ingestión. Es decir, podemos indicarle a Druid que agregue registros que tengan el mismo timestamp y dimensiones por diferente tipo de granularidad (minuto, hora, día, etc) y función de agregación(suma, mínimo, máximo). Esto trae consigo dos ventajas: la reducción del espacio de almacenamiento y el incremento del rendimiento durante las consultas.

Modelando un ejemplo de IoT

Ahora que entendemos un poco los conceptos en los que se cimienta el modelo de datos de Apache Druid, podemos revisar un ejemplo de esquema, y algunas recomendaciones adicionales al diseñarlo. Para ello vamos a recurrir a un ejemplo del mundo de IoT. Supongamos que tenemos el sensor de una fábrica que emite cada segundo los siguientes campos:

  • timestamp
  • tag (ID del sensor)
  • unidades
  • descripción
  • valor

Antes de empezar a describir el JSON del esquema, podemos ver que el type está definido como kafka. Este uno de los tres tipos de forma de ingestar en tiempo real, los otros dos son Kinesis y Tranquility (este último prácticamente deprecado). Referente al esquema, empezamos definiendo el nombre del datasource, como mencionamos antes, es la manera en la que Druid llama a las tablas. A continuación, el timestampSpec nos indica la columna que se usará como índice de tiempo, la cual en este caso se llama “timestamp”. El formato está definido como auto. Este reconoce automáticamente los tipos ISO8601 y unix time. Enseguida tenemos la definición de las dimensiones mediante la propiedad dimensionsSpec. En esta, definimos los campos, los cuales pueden ser tipados si queremos asegurar el tipo de datos a insertar, o si necesitamos que alguno de los campos sea de otro formato diferente a string. En caso contrario, podríamos utilizar un array con los nombres de los campos. Tras las dimensiones, podemos definir las métricas mediante la propiedad metricsSpec. En este caso, debemos indicarle a través de una lista de objetos, el nombre del campo que contiene la métrica durante la ingesta (fieldName), el nombre de la columna, como se va almacenar (name), y la función de agregación a utilizar (type). Esto último, independientemente si tenemos activo o no el Rollup. Finalmente, tenemos la propiedad granularitySpec, en la cual definimos qué tamaño tendrán los segmentos en que se particionan los datos mediante el atributo segmentGranularity. Druid recomienda ajustar este parámetro para alcanzar los 300-500MB por segmento. Además, también podemos definir la mínima granularidad con la que se almacenarán y consultarán los datos (queryGranularity).

El esquema para este sería un JSON de la siguiente forma:


{
  "type": "kafka",
  "dataSchema": {
    "dataSource": "sensor-fabrica",
    "timestampSpec": {
      "column": "timestamp",
      "format": "auto"
    },
    "dimensionsSpec": {
      "dimensions": [
        {
        "name": "tag",
        "type": "STRING"
},
{
        "name": "units",
        "type": "STRING"
},
{
        "name": "description",
        "type": "STRING"
}
]
    },
    "metricsSpec": [
      {
        "name": "count",
        "type": "count"
      },
      {
        "name": "value_sum",
        "fieldName": "value",
        "type": "doubleSum"
      },
      {
        "name": "value_min",
        "fieldName": "value",
        "type": "doubleMin"
      },
      {
        "name": "value_max",
        "fieldName": "value",
        "type": "doubleMax"
      }
    ],
    "granularitySpec": {
      "type": "uniform",
      "segmentGranularity": "DAY",
      "queryGranularity": "NONE"
    }
  },
  "tuningConfig": {
    "type": "kafka",
    "maxRowsPerSegment": 5000000
  },
  "ioConfig": {
    "topic": "sensor-fabrica",
    "inputFormat": {
      "type": "json"
    },
    "consumerProperties": {
      "bootstrap.servers": "localhost:9092"
    },
    "taskCount": 1,
    "replicas": 1,
    "taskDuration": "PT1H"
  }
}

Recomendaciones finales

  • Druid no cuenta con una operación nativa de promedio, por lo cual la documentación recomienda incluir un campo de tipo count para poder realizar esta operación mediante la agregación por suma de este campo y la métrica que nos interesa. Y una post agregación que nos permite dividirlos para obtener el promedio.
  • La pre agregación de los datos durante la ingesta es una funcionalidad muy potente de Druid. Sin embargo, en el caso que se requieran los datos crudos, es decir sin agregar, se puede definir la granularidad a None. Por otro lado, si se requiere tener múltiple granularidades, la documentación recomienda crear distintos datasources con sus respectivas granularidades.
  • Si dejamos como listas vacías las dimensiones y las métricas, Druid puede ser usado de forma schema-less. En este caso, todos los campo diferentes al timestamp serán considerados dimensiones de tipo string.

Druid solo permite un timestamp como tal en los registros. En el caso de que los datos tuviesen otro timestamp adicional, se recomienda insertarlo como unix time en milisegundos. Y al momento de consulta, es posible utilizar la función MILLIS_TO_TIMESTAMP.

Author

  • Juan Pablo López

    Cloud Engineer en Keepler. "Enthusiast of technology and innovation. I am passionate about data analysis and insights discovery, so I am constantly learning new technologies that allow me to identify patterns and behaviors in data."