¿Qué son los índices?
Los índices en Elasticsearch son unidades lógicas fundamentales para organizar y almacenar datos de manera eficiente. En términos sencillos, un índice actúa como una estructura donde se agrupan registros, como logs o métricas, que comparten una estructura o campos similares.
Una de las formas más comunes de usar índices es crear un índice para cada conjunto de logs o métricas con características similares. Sin embargo, al centrarnos en la gestión de logs y métricas en Elasticsearch, esta estrategia puede generar varios problemas y desafíos a largo plazo.
- ¿Cómo vamos a controlar que los índices/shards no crezcan demasiado?
- ¿Cómo borramos datos antiguos?¿Mediante un delete by query? Puede ser un proceso bastante lento.
Si los índices (y concretamente los shards) crecen demasiado, vamos a tardar bastante en recuperar los shards ante un fallo o reinicio.
Para simplificar estos problemas, es muy común generar nuevos índices cada día o cada semana. En Logstash se puede hacer de forma sencilla. Por ejemplo, con el siguiente output el nombre del índice en el que escribimos dependerá del día actual:
output {
elasticsearch {
hosts => ["http://elasticsearch.datadope.io:9200"]
index => "app-%{+YYYY.MM.dd}"
}
Ventajas de utilizar índices diarios
- Si queremos borrar datos de un día en concreto, es tan sencillo como borrar el índice de ese día.
- Los índices ya no crecen tan descontroladamente. Como mucho, un índice contendrá todos los datos que se hayan generado en un día.
- Búsquedas más eficientes, ya que si necesitamos hacer una búsqueda sobre los datos de hoy o ayer, podemos consultar únicamente los índices referentes a estos días, sin necesidad de buscar en todos los datos.
Inconvenientes
Datos Variables y Espacio en Disco
La cantidad de datos que recibimos puede variar considerablemente, especialmente con logs. A pesar de tener un sistema automatizado para borrar índices antiguos, si llegan grandes cantidades de datos, los discos de los nodos pueden llenarse rápidamente, lo que puede requerir intervención manual y revisión periódica de las políticas de retención de logs.
Sobrecarga de Índices y Shards
Tener un índice diario para cada tipo de log puede resultar en la creación de una gran cantidad de índices, lo que genera una alta carga de shards. En Elasticsearch, cuantos más shards tengas (ignorando aquellos con pocos datos), más memoria será necesaria en los nodos. Esto puede afectar negativamente el rendimiento, especialmente si tienes muchos índices pequeños, por lo que es recomendable mantener el tamaño de los shards entre unos pocos GB y decenas de GB.
Solución: Índices Rollover e ILM (Index Lifecycle Management)
Funcionamiento de los Índices Rollover e ILM
- Creación de un Índice Bootstrap y Alias: Primero se crea un índice inicial (bootstrap index) y un alias que apunta a este índice.
- Rollover Automático: Cuando el índice supera el tamaño configurado en la política ILM, Elasticsearch ejecutará un rollover, creando un nuevo índice. A partir de ese momento, todas las nuevas peticiones de indexación se realizarán en el nuevo índice, mientras que las lecturas se siguen realizando sobre todos los índices gestionados por el alias.
- Fases del Ciclo de Vida del Índice: Los índices atraviesan diferentes fases a medida que envejecen. Por ejemplo, los índices recientes y con alta actividad pueden almacenarse en discos SSD rápidos, mientras que los índices antiguos y menos consultados pueden enviarse a discos mecánicos para optimizar el uso del espacio.
El alias siempre contará con un índice para añadir nuevos datos (parámetro is_write_index). El resto de índices sólo se usarán para lecturas y actualizaciones de documentos. Y cada vez que se produzca el rollover, se generará un nuevo índice que pasará a ser el write_index.
Data streams
Los Data Streams son una abstracción introducida en Elasticsearch 7.9 diseñada específicamente para gestionar datos de tipo timeseries. Son la forma recomendada para almacenar datos de tipo append-only (es decir, datos que no se modificarán ni eliminarán una vez almacenados).
¿Cómo Funcionan los Data Streams en Elasticsearch?
Cuando trabajamos con Data Streams, todas nuestras operaciones de lectura y escritura se dirigen al propio data stream, similar a cómo se manejan los alias en Elasticsearch. Detrás de este data stream, se gestionan una serie de índices rollover, los cuales pueden rotar siguiendo políticas de ILM (Index Lifecycle Management), lo que optimiza la gestión y almacenamiento de datos.
Además, los Data Streams tienen un solo índice activo al que se destinan las escrituras de nuevos documentos, mientras que el resto de los índices permanecen disponibles en modo lectura. Esto facilita las consultas al data stream sin comprometer el rendimiento.
Usos de los Data Streams en Elasticsearch
Los Data Streams pueden ser utilizados directamente como fuentes de datos al crear Index Patterns (ahora llamados Data Views desde Kibana 8), para realizar búsquedas en Watcher o como data sources en Grafana. Por lo tanto, su funcionamiento es similar al de los alias con políticas ILM e índices rollover.
Diferencias Clave entre Data Streams y Alias con ILM
Aunque los Data Streams comparten algunas características con los alias, existen diferencias importantes que conviene destacar:
Creación de Índices:
- Con los alias, cuando utilizamos ILM y rollover, debíamos crear manualmente el índice inicial (bootstrap index) y el alias que apuntaba a dicho índice.
- En cambio, con los Data Streams, la creación del índice es automática. Solo necesitamos enviar las peticiones de escritura al data stream, siempre y cuando exista una plantilla (template) configurada en Elasticsearch.
Nomenclatura de los Índices:
- Con alias, los índices se nombraban de forma secuencial, como log-demo-000001, log-demo-000002, etc., con cada rollover creando un nuevo índice.
- En los Data Streams, los índices subyacentes siguen una nomenclatura automática:
.ds-<data-stream>-<yyyy.MM.dd>-<generation>, donde:<data-stream>es el nombre del data stream.<yyyy.MM.dd>es la fecha de creación del índice.<generation>es un número de 6 dígitos, comenzando en000001, que se incrementa con cada rollover.
Operaciones de Actualización y Eliminación:
- Con los alias, podíamos ejecutar operaciones de update y delete directamente sobre los índices.
- Con los Data Streams, no es posible realizar estas operaciones directamente. Sin embargo, podemos usar las APIs _update_by_query y delete_by_query para actualizar o eliminar documentos en los data streams. Es importante destacar que estas operaciones son limitadas a nivel de data stream. Si intentamos realizar las operaciones directamente sobre los índices subyacentes, sí funcionarán, siempre y cuando la configuración de los índices lo permita.
Requisitos para los Data Streams
Para que los documentos sean ingeridos correctamente en un Data Stream, deben incluir un campo @timestamp que permita Elasticsearch identificar la serie temporal de los datos.
Nomenclatura Recomendada para los Data Streams
Cuando crees Data Streams, se recomienda seguir una nomenclatura coherente para facilitar su gestión. Asegúrate de que el nombre del data stream sea descriptivo y siga las mejores prácticas de organización y formato de nombres.
{type}-{dataset}-{namespace}
El campo type representa el tipo de datos (en el estándar de Elastic actualmente este campo pede tener los valors logs o metrics). El campo dataset es un nombre que identifica el tipo de datos así como su estructura. Y por último, el campo namespace representa agrupaciones arbitrarias definidas por el usuario. Ejemplo: logs-nginx.access-prod.
Logstash e índices con nombres dinámicos
Desde Logstash 7.13.0 el output de elasticsearch soporta oficialmente el uso de data streams mediante una serie de nuevas variables para facilitar la escritura en data streams.
Además estas variables nos permiten:
- Una mejor integración con Elastic Agent, ya que ahora Elastic agent (o cualquier otro agente) puede enviar datos referentes al data stream en las variables del documento tipo data_stream.*, que logstash entenderá y usará para saber dónde y cómo indexar los datos, que podrán ser sobreescritas de forma explícita con las opciones de configuración del output de elastic data_stream_*.
- Nos empujan a emplear el Elasticsearch Common Schema que en Elasticsearch tienen definido para data streams, y que sigue la nomenclatura definida arriba.
Por tanto, si queremos seguir los estándares de elastic de la forma más rigurosa, deberíamos emplear estas variables data_stream_* en el output de elastic.
No obstante, el hecho de que logstash tenga estas nuevas opciones para data streams, no significa que estemos obligados a usarlas siempre que queramos escribir en data streams. Podemos e incluso tendremos que prescindir de ellas en algunos casos particulares, como veremos a continuación.
En ocasiones nos puede interesar usar campos del propio documento para determinar el nombre del índice en el que queremos escribir. Imaginemos que tenemos una aplicación llamada app para la cuál queremos indexar los documentos en distintos índices en base al campo app_type del documento. Podríamos emplear el indexado diario clásico de un índice diario:
output {
elasticsearch {
hosts => ["http://elasticsearch.datadope.io:9200"]
index => "app-%{app_type}-%{+YYYY.MM.dd}"
}
}
Tal y como hemos visto al inicio, los índices diarios no son la forma más óptima de gestionar los datos en elastic. ¿Y si quisiéramos emplear alias basados en índices rollover con políticas ILM?
Para usar estos alias debemos inicializar el primer índice y crear un alias que apunte a él. Si estamos hablando de casos concretos y controlados, podemos crear estos índices y alias a mano.
Pero si tenemos muchos casos diferentes y necesitamos que estos índices y alias se generen automáticamente, tenemos un problema, ya que Logstash nunca ha sido capaz de hacer esto. No obstante, con los data streams ya no tenemos esta restricción, ya que, como hemos comentado, para crear un data stream, únicamente necesitamos que haya una template que haga match con el nombre del data stream que queremos crear, y al enviar la petición de escritura, se creará el nuevo data stream si no existe.
Para escribir en data streams desde Logstash podemos usar las opciones data_stream_* del output de elastic. Pero cuando necesitamos que nombre del índice en el que queremos escribir venga de una variable, estas opciones no sirven, ya que no interpretan las variables. Pero esto no es problema, ya que podemos escribir en data streams como si fueran índices normales. Únicamente necesitamos que exista una template en elasticsearch que haga match con el nombre del data stream en el que queremos escribir y usar la siguiente opción en el output de logstash:
action => "create"
Ejemplo
Vamos a probar la generación de data streams desde logstash usando como nombres del data stream campos del documento a ingestar.
1. Primero generamos una política (en realidad este paso no es necesario, ya que elasticsearch incluye políticas por defecto que podríamos usar). Podemos hacer esto usando la api de elasticsearch mediante «Dev Tools» en kibana:
PUT _ilm/policy/demo-policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_primary_shard_size": "50gb",
"max_age": "30d"
},
"set_priority": {
"priority": 75
}
}
},
"delete": {
"min_age": "60d",
"actions": {
"delete": {
"delete_searchable_snapshot": true
}
}
}
}
}
}
2. Creamos una template para que elastic sepa que todo lo que haga match con este patrón será un data stream que use la política generada en el paso anterior.
PUT _index_template/logs-demo
{
"index_patterns": [
"logs-demo-*"
],
"template":{
"settings": {
"index.lifecycle.name": "demo-policy"
}
},
"data_stream": {},
"priority": 300
}
3. Ejecutamos la siguiente pipeline de logstash, que genera 6 mensajes distintos de prueba de forma repetida, parsea los campos app y msg, e indexa en elasticsearch en base al campo app:
input {
generator {
lines => [
"msg=Mensaje de prueba 1 app=alpha",
"msg=Mensaje de prueba 2 app=alpha",
"msg=Mensaje de prueba 1 app=beta",
"msg=Mensaje de prueba 2 app=beta",
"msg=Mensaje de prueba 1 app=gamma",
"msg=Mensaje de prueba 2 app=gamma"
]
}
}
filter {
dissect {
mapping => {
"message" => "msg=%{msg} app=%{app}"
}
}
}
output {
elasticsearch {
hosts => ["http://elastic.datadope.io:9200"]
user => "ingestor"
password => "***"
index => "logs-demo-%{app}"
action => "create"
}
}
4. Podemos comprobar que se han generado data streams para cada tipo de app automáticamente:
Si vemos la sección de índices, podemos comprobar que cada data stream ha generado un índice, donde escribe los documentos.
Es decir, hemos generado 3 data streams, que por debajo emplean índices rollover y sus políticas ILM de forma automática desde logstash, sin necesidad de crear manualmente ningún alias ni ningún índice.
Lo único que hemos necesitado es generar una template (y una política, si es que las que ya teníamos no nos valen).
Conclusión
Tal y como hemos visto, podemos organizar la información en elasticsearch de muchas formas. Si queremos optimizar el uso de recursos por parte de elasticsearch y simplificar la administración/operación de nuestros índices, evitando desequilibrios generados por un volumen de datos variable, lo más recomendable será emplear índices rollover con políticas ILM.
Para usar esta estrategia de indexación, solíamos tener que generar manualmente un índice (bootstrap index) y un alias apuntando a dicho índice. Ahora con los data streams esto no es necesario.
Los data streams no introducen muchas novedades. Más bien son una pequeña evolución. Una nueva abstracción que encapsula los alias y los índices controlados por estos alias.
Con esta nueva abstracción podemos generar nuevos grupos de datos con sus índices rollover y políticas ILM sin necesidad de intervenir manualmente cada vez que aparezca un nuevo grupo de datos.
Esto nos permite, entre otras cosas, poder generar índices rollover de forma dinámica en base a información de los documentos desde Logstash.