Comprensión y ajuste del tamaño del heap JVM de Elasticsearch para el rendimiento

Guía práctica para dimensionar el heap JVM de Elasticsearch, leer síntomas de GC y evitar configuraciones de memoria que perjudiquen el rendimiento de búsqueda.

Comprensión y ajuste del tamaño del heap JVM de Elasticsearch para el rendimiento

El tamaño del heap JVM de Elasticsearch es una de esas configuraciones que la gente a menudo toca demasiado pronto y luego culpa demasiado tarde. Un clúster lento no siempre necesita más heap. A veces ocurre lo contrario: el heap es lo suficientemente grande, pero el sistema operativo tiene muy poca memoria restante para la caché del sistema de archivos, por lo que Lucene tiene que volver al disco con más frecuencia. Otras veces el heap es genuinamente demasiado pequeño, la recolección de basura se ejecuta constantemente y cada búsqueda se siente como caminar a través de cemento mojado.

El objetivo práctico no es encontrar un número mágico. El objetivo es darle a Elasticsearch suficiente heap para metadatos del clúster, buffers de indexación, agregaciones, trabajo de consultas y cachés, mientras se deja suficiente RAM fuera del heap para los archivos de segmento de Lucene y el sistema operativo. Si recuerdas solo una cosa, recuerda que el rendimiento de Elasticsearch depende tanto de la memoria heap como de la memoria fuera del heap.

Comienza separando dos tipos de presión de memoria. La presión del heap JVM se manifiesta como un alto heap.percent, pausas largas de recolección de basura, excepciones del circuit breaker padre, presión de fielddata o OutOfMemoryError. La presión fuera del heap generalmente se ve como búsquedas lentas aunque el heap parezca bien, altas lecturas de disco, actividad de swap o tasas de acierto de caché pobres después de un reinicio. Aumentar el heap puede ayudar en el primer caso. Puede empeorar el segundo caso.

Para la mayoría de los clústeres autogestionados, la vieja regla general sigue funcionando como punto de partida: establece el heap en no más de la mitad de la RAM de la máquina y mantenlo por debajo del umbral de punteros de objetos comprimidos ordinarios. La gente a menudo cita ese umbral como "aproximadamente 32 GB". En la práctica, muchos operadores se mantienen alrededor de 26-31 GB porque el límite exacto depende del JVM y la disposición en tiempo de ejecución. Elasticsearch registra si los punteros de objetos comprimidos ordinarios están habilitados al inicio. Trata el registro de inicio como la fuente de verdad para tu nodo.

En versiones modernas de Elasticsearch, el tamaño automático del heap puede establecer un valor razonable basado en los roles del nodo y la memoria disponible. Eso es útil, especialmente para clústeres pequeños y despliegues estándar. El ajuste manual aún importa cuando un nodo tiene una carga de trabajo inusual: agregaciones pesadas, mapeos grandes, muchos shards, pipelines de ingestión, trabajos de transformación, roles de aprendizaje automático o una mezcla de búsqueda intensiva y alto volumen de indexación.

Aquí hay un ejemplo simple. Supón que tienes un nodo de datos caliente de 64 GB que maneja tanto indexación como búsqueda. Un heap de 30 GB es un punto de partida común. Deja aproximadamente la mitad de la RAM para la caché de páginas del SO y la memoria nativa. Si el mismo nodo es parte de un clúster de registro con muchos shards pequeños y agregaciones de alta cardinalidad, aún podrías ver presión en el heap. La solución podría ser un mejor diseño de shards o limpieza de mapeos, no automáticamente un heap de 40 GB. Moverse por encima del umbral de punteros comprimidos puede aumentar el tamaño de los punteros de objetos y reducir la eficiencia efectiva del heap.

Establece el heap mínimo y máximo al mismo valor. Elasticsearch no debería perder tiempo redimensionando el heap mientras sirve tráfico.

-Xms30g
-Xmx30g

Usa un archivo bajo jvm.options.d/ en lugar de editar directamente el archivo jvm.options empaquetado cuando tu instalación lo soporte. Para instalaciones de paquete, eso generalmente significa algo como /etc/elasticsearch/jvm.options.d/heap.options. Para Docker, pasa la configuración del heap a través de la variable de entorno soportada o la configuración montada. Mantén la configuración consistente para nodos con el mismo rol, pero no asumas que cada rol necesita el mismo heap. Los nodos dedicados elegibles como maestro a menudo necesitan mucho menos heap que los nodos de datos ocupados, a menos que el clúster tenga metadatos muy grandes debido a demasiados índices, campos o shards.

Antes de cambiar el heap, toma una instantánea del comportamiento actual. Mira las estadísticas del JVM:

GET _nodes/stats/jvm?filter_path=nodes.*.jvm.mem,nodes.*.jvm.gc

Luego verifica los breakers y fielddata:

GET _nodes/stats/breaker,indices/fielddata?pretty

También verifica shards y mapeos. Un clúster con miles de shards diminutos puede quemar heap en sobrecarga incluso cuando el volumen de datos no es enorme.

GET _cat/shards?v&bytes=gb
GET _cluster/stats?filter_path=indices.count,indices.shards,indices.mappings

Los síntomas importan más que el número titular. El heap alrededor del 70-85% durante ráfagas puede ser normal si la recolección de basura lo reduce rápidamente. El heap que sube al 90% alto y se queda allí es diferente. Las pausas largas de GC de la generación antigua son peores que un porcentaje alto por sí mismo. Si las búsquedas se agotan cada vez que se ejecuta GC, a los usuarios no les importa que el gráfico promedio del heap se vea aceptable.

Un error común en producción es usar el heap como un parche para mapeos deficientes. Ordenar o agregar en campos text analizados con fielddata: true puede consumir mucho heap. La mejor solución suele ser un subcampo keyword, un campo de menor cardinalidad o un diseño de agregación diferente. Otro error es permitir que los mapeos dinámicos creen miles de campos a partir de claves JSON arbitrarias. El heap entonces desaparece en mapeos, estado del clúster y estructuras de consulta. Pon límites a los campos dinámicos antes de que se conviertan en un problema operativo.

Las agregaciones merecen atención especial. Una agregación de términos en un campo de alta cardinalidad puede crear grandes estructuras en memoria. Si alguien ejecuta un panel que agrupa por user_id, session_id o URL completa durante un largo rango de tiempo, la presión del heap puede aumentar incluso si las búsquedas ordinarias se ven bien. Usa ventanas de tiempo más pequeñas, filtros que reduzcan el conjunto de trabajo, agregación composite para paginación o rollups preagregados cuando sea apropiado. Aumentar el heap puede retrasar el fallo, pero no hará que una agregación ilimitada sea barata.

La indexación tiene su propio patrón de heap. Las solicitudes por lotes crean presión transitoria. Cargas útiles de lotes muy grandes pueden forzar a Elasticsearch a mantener demasiado trabajo a la vez. Si ves rechazos de indexación o picos de heap durante la ingesta, prueba con lotes más pequeños y más control de concurrencia del lado del cliente. Un rango de inicio útil suele ser unos pocos megabytes por solicitud de lote, luego prueba hacia arriba con tus documentos reales. El valor correcto depende del tamaño del documento, pipelines de ingestión, réplicas, intervalo de actualización y velocidad de almacenamiento.

No ignores el sistema operativo. Desactiva el swap o configura el host para que la memoria de Elasticsearch no se intercambie. El swapping puede convertir un problema de memoria recuperable en una larga interrupción porque las pausas del JVM se vuelven enormes. Asegúrate de que el proceso tenga permiso para bloquear la memoria si usas bootstrap.memory_lock: true, y verifícalo al inicio en lugar de asumir que la configuración funcionó.

Después de cambiar el heap, reinicia un nodo a la vez en un clúster de producción y espera a que la recuperación de shards se estabilice antes de pasar al siguiente nodo. Observa la latencia de búsqueda, las pausas de GC, las lecturas de disco y el rendimiento de indexación durante al menos un ciclo de tráfico normal. Un cambio que se ve bien durante una hora tranquila puede fallar durante la avalancha matutina del panel.

Si un nodo sigue encontrando presión en el heap después de un ajuste sensato, da un paso atrás y pregúntate qué está reteniendo el heap. Demasiados shards, demasiados campos, fielddata en texto, agregaciones sobredimensionadas, scripts pesados y roles mixtos en el mismo nodo son causas más comunes que "Elasticsearch necesita toda la RAM". El trabajo de ajuste del heap más fuerte a menudo termina con un mapeo más pequeño, menos shards, mejores límites de consulta o un nivel de ingesta dedicado.

El ajuste del heap no es una ceremonia única. Es parte de la gestión de capacidad. Cuando el volumen de datos crece, los paneles cambian o un nuevo equipo comienza a enviar documentos más amplios, el perfil de memoria también cambia. Mantén el dimensionamiento del heap aburrido: mide, cambia una cosa, reinicia de manera segura y verifica con datos de carga de trabajo real.