Engineering

Aprimoramento da resiliência de nó com o disjuntor de memória real

Você quer ter a confiança de que o Elasticsearch está manipulando adequadamente o tráfego de pesquisa, mesmo se o site está sofrendo carga significativa. Como o Elasticsearch é um sistema distribuído, ele foi projetado do zero para ser resiliente a falhas de nós individuais. Na realidade, implementamos um novo e amplamente aprimorado algoritmo de coordenação de cluster no Elasticsearch 7.0.0.

Além disso, nós individuais no Elasticsearch são criados com resiliência em mente. Se você enviar muitas solicitações para um nó ou suas solicitações forem muito longas, o nó será enviado de volta. Para fazer isso, use os disjuntores. Eles são colocados em determinados pontos no caminho de manipulação da solicitação, por exemplo, quando uma solicitação de rede entra em um nó ou antes de uma agregação ser executada. A ideia-chave é evitar erros do tipo OutOfMemoryError estimando com antecedência se uma solicitação enviará o nó sobre seu limite configurado e rejeitará a solicitação em vez de executar failover. Além dos disjuntores para aspectos individuais, como todas as solicitações in-flight ou o disjuntor de dados de campo, o Elasticsearch também tem um "disjuntor pai" que oferece uma visão global em todos os disjuntores. Isso permite que o Elasticsearch rejeite solicitações que estejam dentro do orçamento de qualquer disjuntor individual, mas que elevem o sistema para além de seu limite total em todos os disjuntores.

É inviável rastrear cada alocação, por isso os disjuntores só podem rastrear memória que seja explicitamente reservada, e às vezes não é possível estimar a utilização de memória exata com antecedência. Isso significa que os disjuntores são apenas um mecanismo paliativo, e apesar de eles oferecerem alguma resiliência contra a sobrecarga de um nó, ainda é possível que os nós fiquem inválidos com um erro OutOfMemoryError. Isso será especialmente problemático quanto menor for o heap, porque o overhead relativo da memória não rastreada é maior.

Desenvolvimento (e teste) de um disjuntor melhor

E se fosse possível saber exatamente quanta memória um nó está usando quando fazemos uma reserva em um disjuntor? Daí poderíamos rejeitar solicitações com base no estado real do sistema nesse ponto, em vez de uma estimativa baseada nas reservas atuais nos disjuntores. Fizemos exatamente isso com o novo disjuntor de memória real no Elasticsearch 7.0. Trata-se de uma implementação alternativa do disjuntor pai que usa uma funcionalidade no JVM para medir a utilização de memória atual em vez de apenas contabilizar a memória rastreada no momento. Apesar de essa ser uma abordagem mais cara do que meramente adicionar alguns números, a medição da utilização da memória ainda é uma operação bem barata: em microparâmetros de comparação, observamos overheads entre 400 e 900 nanossegundos. Executamos uma variedade de experimentos para testar a eficácia do disjuntor de memória real sob condições diferentes. Em um cenário, executamos um parâmetro de comparação de indexação de texto cheio em confronto com um nó que estava configurado com apenas 256 MB de heap. Apesar de as versões anteriores do Elasticsearch não poderem sustentar essa carga de trabalho e serem executadas quase que imediatamente com memória esgotada, o disjuntor de memória real envia de volta e o Elasticsearch pode sustentar a carga. Observe que o Elasticsearch retornará uma resposta de erro nesse casos, e fica a critério dos clientes implementar os devidos mecanismos de backoff e tentativas. Evidentemente, facilitamos essa operação desde que você já esteja usando um de nossos clientes de linguagem oficial. Os clientes de .NET, Ruby, Python e Java já implementam essas políticas de tentativa, além de oferecer extensões para manipular a indexação em lote.

Em outro experimento, executamos uma agregação que produziu intencionalmente um alto número irreal de buckets em um nó com heap de 16 GB. Da mesma maneira, as versões anteriores do Elasticsearch ficaram com memória esgotada, mas a agregação foi executada por quase meia hora até que o erro ocorreu. Com o disjuntor de memória real, o nó deu uma resposta, dependendo se permitimos resultados parciais depois de pouco mais de um minuto ou depois de aproximadamente vinte minutos. Como resultado de vários experimentos, definimos o valor padrão para o novo disjuntor como 95% do heap totalmente disponível. Isso significa que o Elasticsearch permitirá o uso de até 95% do heap até que o disjuntor de memória real seja desarmado.

Vamos considerar um exemplo em que é enviada uma solicitação em lote pequena o suficiente para passar em todas as outras verificações, mas isso causará o desarme do disjuntor de memória real porque ele enviaria o nó para além dos limites. Esse nó é executado com heap de 128 MB configurado, o que significa que o limite do disjuntor pai de 95% é de 117,5 MB. Se essa solicitação for enviada, o nó responderá com HTTP 429 com os seguintes detalhes:

{
  "error": {
    'type': 'circuit_breaking_exception',
    'reason': '[parent] Data too large, data for [<http_request>] would be [123848638/118.1mb], which is larger than the limit of [123273216/117.5mb], real usage: [120182112/114.6mb], new bytes reserved: [3666526/3.4mb]',
    'bytes_wanted': 123848638,
    'bytes_limit': 123273216,
    'durability': 'TRANSIENT'
  },
  'status': 429
}

Podemos ver que o disjuntor também indica que essa é uma falha provisória e que os clientes podem usar isso como dica ou repetir a solicitação após algum tempo. O fato de uma exceção de disjuntor ser permanente ou provisória é decidido com base na memória reservada em todos os disjuntores. Cada tipo de disjuntor tem uma durabilidade associada; se a maior parte da memória for reservada pelos disjuntores que rastreiam a utilização da memória provisória, o disjuntor de memória real tratará isso como uma condição provisória e, caso contrário, como permanente.

Resumo

Apesar de ainda ser possível que, em determinados cenários, um nó do Elasticsearch tenha memória esgotada, o novo disjuntor de memória real no Elasticsearch melhora muito a resiliência de nós individuais exercendo contrapressão com base na utilização de memória medida no momento, em vez de apenas contabilizar a memória rastreada pelos disjuntores. Em nossos experimentos, o Elasticsearch podia sustentar cargas de trabalho agora que eram inatingíveis em versões anteriores e ele também tornará os clusters de produção bem mais resilientes a picos na carga de trabalho. Para experimentar o novo disjuntor de memória real, faça download da versão beta 7.0, use-o e faça algum comentário.

A imagem na parte superior da postagem foi fornecida por Kiran Raja Bahadur SRK sob a licença CC BY-NC-ND 2.0 (fonte original).