Engenharia

Como gerenciar e solucionar problemas de memória do Elasticsearch

Olá! Com a expansão da Elastic da nossa oferta Elasticsearch Service Cloud e da integração automatizada, expandimos o público do Elastic Stack de equipes de operações completas a engenheiros de dados, equipes de segurança e consultores. Como engenheira de suporte da Elastic, gostei de interagir com mais históricos de usuários e com casos de uso ainda mais amplos. 

Com um público mais amplo, estou vendo mais perguntas sobre como gerenciar a alocação de recursos, em particular a proporção mística entre shard-heap e como evitar circuit breakers. Eu entendo. Quando comecei com o Elastic Stack, tinha as mesmas perguntas. Foi minha primeira introdução ao gerenciamento de heap Java e shards de banco de dados de séries temporais e redimensionamento de minha própria infraestrutura.

Quando entrei na equipe da Elastic, adorei que, além da documentação, tínhamos blogs e tutoriais para que eu pudesse me integrar rapidamente. Mas eu sofri no primeiro mês para correlacionar meu conhecimento teórico com os erros que os usuários enviavam pela minha fila de tíquetes. Por fim, descobri, como outros engenheiros de suporte, que muitos dos erros relatados eram apenas sintomas de problemas de alocação, e os mesmos sete links dariam aos usuários informações atualizadas para eles gerenciarem sua alocação de recursos com sucesso.

Falando como engenheira de suporte, nas seções a seguir, examinarei os principais links da teoria de gerenciamento de alocação que enviamos aos usuários, os principais sintomas que vemos e para onde direcionamos os usuários a atualizar suas configurações para resolver seus problemas de alocação de recursos.

Teoria

Como uma aplicação Java, o Elasticsearch requer alguma alocação de memória lógica (heap) da memória física do sistema. Isso deve ser até metade da RAM física, com limite de 32 GB. Definir um uso de heap mais alto geralmente é um ato em resposta a consultas caras e maior armazenamento de dados. O circuit breaker principal é 95% por padrão, mas recomendamos redimensionar os recursos uma vez que se alcance 85% de forma consistente. 

Eu recomendo fortemente estes artigos de visão geral da nossa equipe para obter mais informações:

Config

As configurações padrão do Elasticsearch dimensionam automaticamente seu heap da JVM com base na função do nó e na memória total. No entanto, conforme necessário, você pode configurá-lo diretamente das três maneiras a seguir:

1. Diretamente no seu arquivo config > jvm.options dos seus arquivos locais do Elasticsearch

## 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 uma variável de ambiente do Elasticsearch no seu 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. Por meio do nosso Elasticsearch Service > Deployment (Implantação) > Edit view (Editar visualização). Observação: o controle deslizante atribui memória física, e cerca de metade será alocada para a heap.

blog-elasticsearch-memory.png

Solução de problemas

Se você está enfrentando problemas de desempenho com seu cluster, provavelmente trata-se dos suspeitos de sempre

  • Problemas de configuração: Oversharding, sem política de ILM
  • Volume induzido: alto ritmo/carga de solicitações, sobrepondo consultas caras/gravações

Todas as solicitações de cURL/API a seguir podem ser feitas no Elasticsearch Service > API Console (Console de API), como um cURL para a Elasticsearch API ou em Kibana > Dev Tools (Ferramentas para desenvolvedores).

Oversharding

Índices de dados são armazenados em subshards que usam heap para manutenção e durante solicitações de busca/gravação. O tamanho do shard deve se limitar a 50 GB, e o número de shards deve se limitar conforme determinado por meio desta equação:

shards = sum(nodes.max_heap) * 20

Tomando o exemplo do Elasticsearch Service acima com 8 GB de memória física em duas zonas (que alocará dois nós no 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

Em seguida, compare isso com _cat/allocation

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

Ou com _cluster/health

GET /pt/_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
}

Portanto, esta implantação tem 82 shards da recomendação máxima de 160. Se a contagem for maior do que a recomendada, você poderá sentir os sintomas nas próximas duas seções (veja abaixo).

Se algum shard relata >0 fora de active_shards ou active_primary_shards, você identificou uma causa importante de configuração para problemas de desempenho.

Mais comumente, se isso relatar um problema, será unassigned_shards>0. Se esses shards forem principais, seu cluster relatará como status:red e, se forem apenas réplicas, relatará como status:yellow. Por isso, é importante definir réplicas em índices para que o cluster possa se recuperar em vez de sofrer perda de dados caso encontre um problema.

Vamos fingir que temos um status:yellow com um único shard não atribuído. Para investigar, verificaríamos qual shard de índice está tendo problemas via _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

Portanto, isso será para nossos logs de índice não pertencentes ao sistema, que têm um shard de réplica não atribuído. Vamos ver o que está causando problemas executando _cluster/allocation/explain (dica profissional: quando você escala o problema para o suporte, isso é exatamente o que fazemos).

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]"
}]}]}

Essa mensagem de erro aponta para data_hot, que faz parte de uma política de gestão de ciclo de vida de índice (ILM) e indica que nossa política de ILM é incongruente com nossas configurações de índice atuais. Nesse caso, a causa desse erro é a configuração de uma política de ILM hot-warm sem ter nós hot-warm designados. (Eu precisava garantir que algo iria falhar; isso sou eu forçando exemplos de erros para vocês. Vejam só o que vocês fizeram comigo 😂.)

Só para constar: se você executar esse comando quando não tiver nenhum shard não atribuído, receberá um erro 400 dizendo que não é possível encontrar shards não atribuídos para explicar porque não há nada de errado para relatar.

Se você tiver uma causa não lógica (por exemplo, um erro de rede temporário como o nó deixou o cluster durante a alocação), poderá usar o prático _cluster/reroute do Elastic.

POST /pt/_cluster/reroute

Essa solicitação sem customizações inicia um processo assíncrono em segundo plano que tenta alocar todos os shards com state:UNASSIGNED atuais. (Não seja como eu e não espere terminar para entrar em contato com o desenvolvedor porque pensei que seria instantâneo e, coincidentemente, escalei bem a tempo de ele dizer que não há nada de errado porque não havia mais nada.)

Circuit breakers

Maximizar sua alocação de heap pode fazer com que as solicitações para seu cluster atinjam o tempo limite ou causem erros. Além disso, com frequência, farão seu cluster ter exceções de circuit breaker. A ocorrência de circuit breaker causa eventos do 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]

Para investigar, dê uma olhada em seu heap.percent, examinando _cat/nodes

GET /pt/_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

Ou, se você o habilitou anteriormente, navegue até Kibana > Stack Monitoring (Monitoramento da stack).

blog-elasticsearch-memory-2.png

Se você confirmou que está atingindo seus circuit breakers de memória, considere aumentar a heap temporariamente para ter espaço para investigar. Ao investigar a causa raiz, procure nos logs de proxy do cluster ou no elasticsearch.log os eventos consecutivos anteriores. Você estará procurando

  • consultas caras, especialmente:
    • altas agregações de bucket
      •  Eu me senti tão boba quando descobri que as buscas alocam temporariamente uma determinada porta da sua heap antes de executarem a consulta com base no tamanho da busca ou nas dimensões do bucket, então, definir 10.000.000 estava provocando azia na minha equipe de operações.
    • mapeamentos não otimizados
      • A segunda razão para me sentir boba foi quando pensei que fazer relatórios hierárquicos faria buscas melhores do que dados nivelados (não é o caso).
  • Volume/ritmo das solicitações: geralmente consultas em lote ou assíncronas

É hora de redimensionar

Se esta não é a primeira vez que você atinge os circuit breakers ou você suspeita que será um problema contínuo (por exemplo, atingir 85% consistentemente, então é hora de analisar os recursos de redimensionamento), convém examinar mais de perto a pressão de memória da JVM como seu indicador de heap de longo prazo. Você pode verificar isso em Elasticsearch Service > Deployment (Implantação)

blog-elasticsearch-memory-3.png

Ou pode calcular a partir de _nodes/stats

GET /pt/_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
}}}}}}}

onde

JVM Memory Pressure = used_in_bytes / max_in_bytes

Um possível sintoma disso é a alta frequência e a longa duração dos eventos do coletor de lixo (gc) em seu 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]

Se você confirmar esse cenário, precisará pensar em redimensionar seu cluster ou reduzir as demandas que o atingem. Convém investigar/considerar:

  • o aumento dos recursos de heap (heap/nó, número de nós)
  • a redução dos shards (excluir dados desnecessários/antigos, usar o ILM para colocar os dados em armazenamento warm/cold para que você possa encolhê-los, desativar réplicas de dados que não causarão problemas se forem perdidos)

Conclusão

Uau! Pelo que vejo no Suporte da Elastic, esse é o resumo dos tíquetes mais comuns dos usuários: shards não atribuídos, relação shard-heap desbalanceada, circuit breakers, alta coleta de lixo e erros de alocação. Todos são sintomas da conversa básica sobre gerenciamento de alocação de recursos. Espero que agora você também conheça a teoria e as etapas de resolução.

Neste ponto, porém, se você não conseguir resolver um problema, fique à vontade para entrar em contato. Teremos o maior prazer em ajudar! Você pode entrar em contato conosco via fóruns de discussão da Elastic no Discuss, comunidade da Elastic no Slack, consultoria, treinamento e suporte.

Viva a nossa capacidade de autogerenciar a alocação de recursos do Elastic Stack sem depender do pessoal de operações (mas sem deixar de amá-los)!