Ingeniería

Gestión y solución de problemas de la memoria de Elasticsearch

¡Hola! Con la expansión de Elastic de nuestra oferta de Elasticsearch Service Cloud e incorporación automatizada, ampliamos la audiencia del Elastic Stack de equipos completos de operaciones a ingenieros, equipos de seguridad y consultores. Como ingeniero de soporte de Elastic, me ha complacido interactuar con más antecedentes de usuarios y con incluso más casos de uso. 

Dada la audiencia más amplia, veo más preguntas sobre gestión de asignación de recursos, en especial sobre la mística proporción shard:heap y sobre evitar interruptores de circuito. Lo entiendo. Cuando comencé con el Elastic Stack, tenía las mismas preguntas. Fue mi primera introducción a la gestión de heap de Java y shards de base de datos temporales, y al escalado de mi propia infraestructura.

Cuando me uní al equipo de Elastic, me gustó tener blogs y tutoriales (además de la documentación) para poder incorporarme rápido. Sin embargo, durante el primer mes tuve problemas para relacionar mi conocimiento teórico con los errores que enviaban los usuarios en la cola de tickets. Eventualmente, descubrí, como muchos otros ingenieros de soporte, que muchos de los errores reportados era solo síntomas de problemas de asignación y que los mismos siete (aproximadamente) enlaces permitían a los usuarios obtener la velocidad necesaria para gestionar con éxito su asignación de recursos.

Desde el punto de vista de un ingeniero de soporte, en las secciones siguientes haré un repaso de los enlaces principales de la teoría de gestión de asignación que enviamos a los usuarios, los principales síntomas que vemos y a dónde dirigimos a los usuarios para actualizar sus configuraciones y resolver los problemas de asignación de recursos.

Teoría

Como aplicación de Java, Elasticsearch requiere la asignación de un poco de memoria lógica (heap) desde la memoria física del sistema. Debería ser de hasta la mitad de la RAM física, con un máximo de 32 GB. Por lo general, la configuración de un uso de heap más alto es en respuesta a búsquedas costosas y un almacenamiento de datos más grande. El valor predeterminado del interruptor de circuito general es 95 %, pero recomendamos escalar los recursos una vez que llegas a 85 % de forma consistente. 

Recomendamos enfáticamente estos artículos de visión general de nuestro equipo para conocer más información:

Configuración

De fábrica, la configuración predeterminada de Elasticsearch automáticamente dimensiona la memoria heap de JVM según el rol del nodo y la memoria total. Sin embargo, según sea necesario, puedes configurarla directamente de las siguientes tres maneras:

1. Directamente en el archivo config > jvm.options de tus archivos de Elasticsearch locales

## JVM configuration
################################################################
## IMPORTANT: JVM heap size
################################################################

#Xms represents the initial size of total heap space
#Xmx represents the maximum size of total heap space
-Xms4g
-Xmx4g

2. Como variable del entorno de Elasticsearch en tu docker-compose

version: '2.2'
services:
es01:
image: docker.elastic.co/elasticsearch/elasticsearch:7.12.0
environment:
- node.name=es01
- cluster.name=es
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.type=single-node
ulimits:
memlock:
soft: -1
hard: -1
ports:
- 9200:9200

3. A través de nuestro Elasticsearch Service > Deployment (Despliegue) > Edit view (Vista de edición). Nota: El control deslizante asigna memoria física, y aproximadamente la mitad se asignará a la heap.

blog-elasticsearch-memory.png

Solución de problemas

Si actualmente tienes problemas de rendimiento con tu cluster, lo más probable es que los sospechosos sean siempre los mismos:

  • Problemas de configuración: oversharding, sin política de ILM
  • Inducidos por el volumen: alto ritmo de demanda/carga, superposición de búsquedas costosas/escrituras

Todas las solicitudes de cURL/API siguientes pueden hacerse en Elasticsearch Service > API Console (Consola de API), como cURL de la API de Elasticsearch, o en Kibana > Dev Tools (Herramientas de desarrollo).

Oversharding

Los índices de datos almacenan en subshards, que usan la heap para mantenimiento y durante las solicitudes de búsqueda/escritura. El tamaño del shard debe tener un máximo de 50 GB y la cantidad debe tener un máximo determinado con esta ecuación:

shards = sum(nodes.max_heap) * 20

Tomando el ejemplo de Elasticsearch Service anterior con 8 GB de memoria física en dos zonas (que asignarán dos nodos en total)

#node.max_heap
8GB of physical memory / 2 = 4GB of heap
#sum(nodes.max_heap)
4GB of heap * 2 nodes = 8GB
#max shards
8GB * 20
160

Luego realiza una comparación cruzada de esto con _cat/allocation

GET /es/_cat/allocation?v=true&h=shards,node
shards node
41 instance-0000000001
41 instance-0000000000

o con _cluster/health.

GET /es/_cluster/health?filter_path=status,*_shards
{
"status": "green",
"unassigned_shards": 0,
"initializing_shards": 0,
"active_primary_shards": 41,
"relocating_shards": 0,
"active_shards": 82,
"delayed_unassigned_shards": 0
}

Entonces, este despliegue tiene 82 shards de la recomendación máxima de 160. Si el conteo es mayor que la recomendación, es posible que experimentes síntomas en las dos secciones siguientes (ver a continuación).

Si algún shard, excepto active_shards o active_primary_shards, reporta un valor >0, detectaste una importante causa de configuración de los problemas de rendimiento.

En general, si esto reporta un problema, será unassigned_shards>0. Si estos shards son primarios, tu cluster reportará status:red, pero si solo son réplicas, reportará status:yellow. (Por eso es importante configurar las réplicas en los índices, para que si el cluster encuentra un problema, pueda recuperarse en lugar de experimentar una pérdida de datos).

Supongamos que tenemos status:yellow con un solo shard sin asignar. A fin de investigar, observaríamos qué shard de índice está teniendo problemas a través de _cat/shards.

GET _cat/shards?v=true&s=state
index shard prirep state docs store ip node
logs 0 p STARTED 2 10.1kb 10.42.255.40 instance-0000000001
logs 0 r UNASSIGNED
kibana_sample_data_logs 0 p STARTED 14074 10.6mb 10.42.255.40 instance-0000000001
.kibana_1 0 p STARTED 2261 3.8mb 10.42.255.40 instance-0000000001

Esto sería para nuestros logs de índice que no pertenecen al sistema, con un shard de réplica sin asignar. Veamos qué le genera inconvenientes, para ello ejecutemos _cluster/allocation/explain. (Sugerencia de profesionales: Cuando escalas el problema a soporte, esto es exactamente lo que hacemos).

GET _cluster/allocation/explain?pretty&filter_path=index,node_allocation_decisions.node_name,node_allocation_decisions.deciders.*
{ "index": "logs",
"node_allocation_decisions": [{
"node_name": "instance-0000000005",
"deciders": [{
"decider": "data_tier",
"decision": "NO",
"explanation": "node does not match any index setting [index.routing.allocation.include._tier] tier filters [data_hot]"
}]}]}

Este mensaje de error apunta a data_hot, que es parte de una política de gestión de ciclo de vida del índice (ILM) e indica que nuestra política de ILM es incongruente con nuestra configuración actual del índice. En este caso, la causa de este error proviene de la configuración de una política de ILM caliente-tibia sin haber designado nodos calientes-tibios. (Debíamos asegurarnos de que algo fallara, así forzamos los ejemplos de errores para ti. Mira lo que nos obligas a hacer 😂).

A modo informativo, si ejecutas este comando sin tener ningún shard sin asignar, recibirás un error 400 que indicará unable to find any unassigned shards to explain (no se pudieron encontrar shards sin asignar para la explicación) porque no hay ningún inconveniente para reportar.

Si obtienes una causa no lógica (por ejemplo, un error de red temporal, como node left cluster during allocation [el nodo dejó el cluster durante la asignación]), entonces puedes usar el tan útil _cluster/reroute de Elastic.

POST /es/_cluster/reroute

Esta solicitud sin personalizaciones inicia un proceso asíncrono en segundo plano que intenta asignar todos los shards state:UNASSIGNED actuales. (No seas ansioso, espera a que termine antes de comunicarte con los desarrolladores porque no es instantáneo; de lo contrario, para cuando te comuniques con ellos, te dirán que no hay inconvenientes, porque de hecho ya no los habrá).

Interruptores de circuito

Maximizar la asignación de la heap puede hacer que las solicitudes a tu cluster agoten el tiempo de espera o den como resultado un error, y provocará, con frecuencia, que tu cluster experimente excepciones del interruptor de circuito. La interrupción de circuitos genera eventos elasticsearch.log como

Caused by: org.elasticsearch.common.breaker.CircuitBreakingException: [parent] Data too large, data for [] would be [num/numGB], which is larger than the limit of [num/numGB], usages [request=0/0b, fielddata=num/numKB, in_flight_requests=num/numGB, accounting=num/numGB]

A fin de investigar, echa un vistazo a tu heap.percent, ya sea desde _cat/nodes

GET /es/_cat/nodes?v=true&h=name,node*,heap*
#heap = JVM (logical memory reserved for heap)
#ram = physical memory
name node.role heap.current heap.percent heap.max
tiebreaker-0000000002 mv 119.8mb 23 508mb
instance-0000000001 himrst 1.8gb 48 3.9gb
instance-0000000000 himrst 2.8gb 73 3.9gb

o, si lo habilitaste previamente, desde Kibana > Stack Monitoring (Monitoreo del stack).

blog-elasticsearch-memory-2.png

Si confirmaste que estás llegando a los interruptores de circuito de memoria, quizá te convenga aumentar temporalmente la memoria heap para tener un margen que te permita investigar. Al investigar la causa raíz, observa en los logs de proxy del cluster o elasticsearch.log los eventos consecutivos previos. Deberás prestar atención a lo siguiente:

  • búsquedas costosas, específicamente:
    • muchas agregaciones de cubetas
      •  Me sentí muy avergonzado cuando descubrí que las búsquedas asignan temporalmente un puerto determinado de tu heap antes de ejecutar la búsqueda según el tamaño de la búsqueda o las dimensiones de la cubeta, por lo que configurar 10 000 000 realmente le estaba dando acidez a mi equipo de operaciones.
    • mapeos no optimizados
      • El segundo motivo por el cual me sentí avergonzado fue por creer que hacer reportes jerárquicos permitiría mejores búsquedas que los datos aplanados (pero no es así).
  • Volumen de solicitudes/ritmo: por lo general, búsquedas asíncronas o de batch

Momento de escalar

Si esta no es la primera vez que llegas a los interruptores de circuito o sospechas que será algo constante (por ejemplo, llegas a 85 % de forma consistente, por lo que es momento de buscar recursos de escalado), quizá te convenga analizar bien la presión de memoria JVM como tu indicador de heap de largo plazo. Puedes echar un vistazo a esto en Elasticsearch Service > Deployment (Despliegue).

blog-elasticsearch-memory-3.png

O puedes calcularlo desde _nodes/stats.

GET /es/_nodes/stats?filter_path=nodes.*.jvm.mem.pools.old
{"nodes": { "node_id": { "jvm": { "mem": { "pools": { "old": {
"max_in_bytes": 532676608,
"peak_max_in_bytes": 532676608,
"peak_used_in_bytes": 104465408,
"used_in_bytes": 104465408
}}}}}}}

Donde

JVM Memory Pressure = used_in_bytes / max_in_bytes

Un síntoma potencial de esto es la alta frecuencia y la larga duración de eventos del recolector de basura (gc) en tu elasticsearch.log.

[timestamp_short_interval_from_last][INFO ][o.e.m.j.JvmGcMonitorService] [node_id] [gc][number] overhead, spent [21s] collecting in the last [40s]

Si confirmas este escenario, deberás evaluar escalar tu cluster o reducir las demandas que le llegan. Te convendrá investigar/considerar:

  • Aumentar los recursos de heap (heap/nodo, cantidad de nodos)
  • Reducir los shards (eliminar datos innecesarios/antiguos, usar ILM para poner los datos en almacenamiento tibio/frío y así reducirlos, desactivar las réplicas de datos que no te afecta perder).

Conclusión

¡Vaya! Por lo que veo en Soporte de Elastic, ese es el resumen de los tickets más comunes de los usuarios: shards sin asignar, shard:heap desequilibrado, interruptores de circuito, alta recolección de basura y errores de asignación. Todos son síntomas de la conversación sobre gestión de asignación de recursos principal. Afortunadamente, ahora conoces la teoría y los pasos de resolución, también.

Sin embargo, si en este punto estás atascado resolviendo un problema, siéntete libre de ponerte en contacto. Estamos aquí para ayudarte ¡y nos complace hacerlo! Puedes ponerte en contacto con nosotros a través del debate de Elastic, el Slack de la comunidad de Elastic, consultoría, capacitación y soporte.

Celebremos nuestra capacidad de autogestionar la asignación de recursos del Elastic Stack "sin Operaciones" (y también "con Operaciones").