Fundamentos da JVM para Elasticsearch: métricas, memória e monitoramento

O Elasticsearch é um mecanismo de análise de dados e buscar baseado em Java e um banco de dados vetorial criado sobre o Apache Lucene, e é o núcleo da Elasticsearch Platform. Para executar o Elasticsearch em suas plataformas suportadas, você precisa de uma máquina virtual Java (JVM). A JVM fornece um ambiente de tempo de execução independente de Platform, e você pode executar o Elasticsearch em um ambiente virtual sobre o sistema operacional existente. A JVM abstrai o sistema operacional e o hardware subjacentes, para que as aplicações Java possam executar em qualquer Platform.

Entender o gerenciamento de memória da JVM e a coleta de objetos via coleta de lixo (GC) é a chave para solucionar problemas como java.lang.OutOfMemoryError (código de saída 127). Esses problemas ocorrem quando a JVM executa sem memória ou quando há um código de saída 137, o que normalmente indica que o OOM (out-of-memory) killer do host encerrou o processo da JVM devido ao uso excessivo de memória. Este blog guiará você sobre como examinar os padrões de uso de memória e solucionar esses problemas da JVM.

Também explicarei a função da JVM e como correlacioná-la com a ajuda das APIs existentes do Elasticsearch, fornecendo insights valiosos sobre o estado da JVM do Elasticsearch e ajudando você a decidir se deseja ajustá-la ainda mais de acordo com seu próprio caso de uso. 

Conforme mencionado na documentação da JVM do Elasticsearch, os valores padrão das opções da JVM que são enviados com o produto funcionam de forma eficiente em todos os ambientes de produção suportados; esses valores padrão podem lidar com a maioria dos casos de uso para operações de busca e indexação. Não é recomendada a alteração de quaisquer valores ou a adição de valores personalizados para as opções ou configurações da JVM.

Se você acha que alguma alteração seria benéfica para sua carga de trabalho, entre em contato com o suporte da Elastic antes de fazê-lo.

O que é uma JVM?

Uma máquina virtual Java é parte integrante do Java Runtime Environment (JRE), que vem incluído em um Java Development Kit (JDK).

fluxograma da JVM

A JVM é semelhante a um programa de computador que ajuda a executar aplicações Java. Ela não existe como uma máquina física, mas age como uma ao traduzir código Java em instruções que seu sistema operacional compatível consegue entender. A JVM também cuida de tarefas importantes como gerenciamento de memória, tratamento de coleta de lixo e manutenção da segurança de suas aplicações.

Em seguida, vamos focar no gerenciamento de memória da JVM e na coleta de lixo — um ponto importante no Elasticsearch que verificamos frequentemente ao solucionar problemas de desempenho de memória ou relacionados a OOM.

Para entender o gerenciamento de memória e a coleta de lixo, primeiro você precisa entender a representação lógica da área de heap (ou memória heap) onde todos os objetos residem quando uma JVM é iniciada.

Entendendo a memória heap da JVM: gerações young e old

Uma área de heap, ou memória heap, é uma combinação de pools de memória onde os objetos residem com base em seu ciclo de vida e idade. A memória heap da JVM consiste principalmente em uma young generation e uma old generation. A young generation é dividida ainda mais em regiões Eden e Survivor, conforme ilustrado abaixo. Os objetos Java são alocados nessas áreas com base em sua idade e referência, e então são movidos das regiões young para as old. 

A maior parte da utilização de heap no espaço Eden é rapidamente coletada. Qualquer heap que não seja liberado é movido para um dos espaços survivor (S0 ou S1), onde é normalmente recuperado seguindo um padrão de decaimento exponencial. Se persistir além disso, ele é movido para o espaço Tenured.

Memória heap total da JVM = Young generation (regiões Eden e Survivor S0 e S1) + Old generation (Tenured)

Representação lógica da área de heap (sem foco em qual coletor de lixo você está usando)
Representação lógica da área de heap (sem foco em qual coletor de lixo você está usando)

Área/região young generation
Quando qualquer aplicação Java inicia, ela cria novos objetos e aloca esses novos objetos na área chamada memória heap. O Eden space da young generation é o primeiro lugar onde a aplicação aloca objetos recém-criados. Esta área é reservada para a alocação de novos objetos. E quando ela fica cheia, os objetos elegíveis serão coletados (garbage collected) ou promovidos para outras regiões.

Quando ocorre uma coleta de lixo menor, os objetos referenciados serão movidos para outra subseção, que é a região Survivor S0. E os objetos não referenciados serão excluídos para liberar espaço no Eden. Esta região contém os objetos que sobreviveram ao processo de coleta de lixo, daí o nome região Survivor

O mesmo ciclo se repete quando o espaço Eden está cheio novamente. Desta vez, os objetos que sobreviveram tanto ao Eden quanto ao S0 serão movidos para a região S1, e esse processo continua. 

Os objetos que sobreviveram às coletas de lixo em Eden, S0 e S1 serão promovidos para a geração antiga com base no calculador de idade.

Área/região da old generation
A old generation (também conhecida como tenured generation) é uma coleção de objetos de longa duração que sobreviveram aos múltiplos eventos de garbage collection na young generation (minor GCs). Objetos que sobreviveram a um certo número de ciclos no survivor space são movidos pelo algoritmo para a old generation, também conhecida como promoção de objetos.

gráficos de taxa de gc e heap da JVM

O padrão de GC em dente de serra acima, proveniente do Stack Monitoring, mostra que, quando a coleta de lixo secundária está ocorrendo, a memória heap da JVM é reduzida porque o GC está removendo os objetos indesejados da memória. Após alguns ciclos, os objetos se acumulam e o GC entra em ação novamente para limpar os objetos. 

Método de coleta de lixo

O Elasticsearch é enviado com uma versão do JDK integrada compatível, e os detalhes estão documentados nas notas de lançamento. Até a v6.4, o Elasticsearch usava o coletor de lixo concurrent mark sweep (CMS), que foi descontinuado no JDK9. O Elasticsearch agora usa o coletor de lixo Garbage-First (G1) como o método de GC padrão. Ele também tem um desempenho melhor quando comparado ao método de GC CMS. O mecanismo interno do G1GC é complexo e envolve muitas fases, mas vamos analisar o básico dele.

Para usar o G1GC, use a flag da JVM -XX:+UseG1GC na opção da JVM do Elasticsearch. No G1GC, a área total do heap será dividida em regiões de tamanho igual -XX:G1HeapRegionSize=1m que podem variar de 1 MB a 32 MB, dependendo do tamanho do heap. As gerações Eden, Survivor e old são conjuntos lógicos dessas regiões e não são contíguas. Além disso, há uma região humongous e livre/disponível dentro da área do heap. A região humongous é usada para alocar objetos que têm mais da metade do tamanho da região.

Alocação de heap (região) no G1GC
Alocação de heap (região) no G1GC

O G1GC tem um alvo de tempo de pausa (-XX:MaxGCPauseMillis=200) que ele tenta atingir durante a coleta de lixo. Isso significa que, durante a coleta de lixo, ele garante que a JVM não sofra uma pausa superior a esse valor, tornando-o melhor que o CMS ou outros coletores onde essa não é uma opção. No geral, recomenda-se manter as configurações de GC com os valores padrão, o que manterá a pausa do GC dentro dos limites para um desempenho aprimorado.

APIs do Elasticsearch para o estado da JVM

Qualquer tarefa que o Elasticsearch queira realizar precisará solicitar à JVM que aloque um pouco de memória para executá-la. Conforme mencionado acima, a JVM lida com o gerenciamento de memória em nome do Elasticsearch para que ele não precise se preocupar com isso. No entanto, os administradores podem ter interesse em verificar o status da JVM por meio das APIs do Elasticsearch para garantir que as coisas estejam progredindo conforme o esperado.

Para verificar todas as configurações que foram especificadas ou fornecidas como argumentos de entrada para a JVM do Elasticsearch, use a API de informações de node com o atributo JVM (também descrito na documentação).

GET _nodes/_all/jvm 

Esta API fornecerá os detalhes da configuração do Node, incluindo argumentos de entrada da JVM, pools de memória e tamanho do heap. 

Além disso, se você quiser verificar as métricas da JVM do Elasticsearch para o tempo de funcionamento de um Node específico, o heap usado em todos os pools de memória ou estatísticas sobre a coleta de lixo, você pode usar a API de estatísticas de Node. Ela fornecerá métricas detalhadas sobre cada uma das métricas mencionadas.

GET _nodes/stats/jvm
or
GET _nodes/stats/jvm?filter_path=nodes.*.jvm

Nota: O Elasticsearch não recomenda tomar decisões com base no valor de heap_percent obtido da API de estatísticas de Node. Nossa UI calcula a pressão de memória, e o usuário deve tomar as medidas necessárias com base no cálculo descrito neste blog.

Verificando métricas de JVM usando ferramenta integrada do JDK

Se você for um usuário avançado e quiser verificar estatísticas em tempo real sobre uma JVM em execução sem usar APIs do Elasticsearch, você pode usar o Java incluído e executar o jstat para verificar as estatísticas ao vivo. Para fazer isso, primeiro encontre o diretório home do Java ou o diretório bin e o PID do processo do Elasticsearch. A partir do diretório bin, use a ferramenta jstat:

./jstat -gcutil <PID> 2000

Aqui, 2000 é o intervalo em milissegundos após o qual as estatísticas serão buscadas e impressas na CLI. Este comando é útil para identificar com que frequência o GC está ocorrendo em todas as áreas e quão rápido os pools de memória estão sendo preenchidos pelos objetos (taxas de criação e promoção de objetos). 

Esta não é uma alternativa às APIs mencionadas acima, mas é mais voltada para a depuração em nível de desenvolvedor.

E é isso!

Neste post, abordamos os conceitos básicos de máquinas virtuais Java, vários pools de memória, a importância da coleta de lixo (garbage collection) e como verificar as estatísticas da JVM do Elasticsearch. No próximo post, discutiremos as configurações importantes da JVM e como coletar suas métricas atuais dentro do Elasticsearch.  

Se tiver alguma dúvida ou precisar de mais suporte, não hesite em entrar em contato conosco. Estamos sempre aqui e teremos o maior prazer em ajudar!

O lançamento e o tempo de amadurecimento de todos os recursos ou funcionalidades descritos neste artigo permanecem a exclusivo critério da Elastic. Os recursos ou funcionalidades não disponíveis no momento poderão não ser entregues ou não chegarem no prazo previsto.