Engenharia

Canonical, Elastic e Google unem-se para evitar a corrupção de dados no Linux

Na Elastic, estamos constantemente inovando e lançando novos recursos. Enquanto lançamos novos recursos, também trabalhamos para garantir que eles sejam testados, sólidos e confiáveis — e, às vezes, encontramos falhas ou outros problemas.

Ao testar um novo recurso, descobrimos uma falha no kernel do Linux que afetava discos SSD em certos kernels do Linux. Neste artigo do blog, vamos contar a história da investigação e como ela envolveu uma grande colaboração com dois parceiros próximos: Google Cloud e Canonical.

A investigação resultou no lançamento de novos kernels do Ubuntu para resolver o problema.

Como tudo começou

Em fevereiro de 2019, realizamos um teste de estabilidade de 10 dias para replicação entre clusters (CCR) com a ferramenta de benchmark padrão do Elasticsearch, Rally, usando uma carga de trabalho de ingestão com uso intenso de atualizações. A versão do Elasticsearch era o commit mais recente do branch 7.0 não lançado (naquela época). Depois de alguns dias, o teste falhou com o relatório de logs do Elasticsearch:

… 
Caused by: org.apache.lucene.index.CorruptIndexException: checksum failed (hardware problem?)

Ao detectar[1] um índice corrompido, o Elasticsearch tenta se recuperar com uma réplica. Em muitos casos, a recuperação é perfeita e, a menos que o usuário analise os logs, a corrupção passa despercebida. Mesmo com a redundância oferecida pelas réplicas, a perda de dados é possível em certas circunstâncias. Portanto, queríamos entender por que a corrupção estava ocorrendo e explorar como poderíamos corrigir o problema.

A princípio, suspeitamos que um novo recurso do Elasticsearch, CCR/exclusões reversíveis, estivesse causando o problema. Porém, essa suspeita foi rapidamente descartada quando descobrimos que poderíamos reproduzir a corrupção do índice usando um único cluster e desabilitando o recurso CCR/exclusões reversíveis. Após uma pesquisa adicional, descobrimos que conseguíamos reproduzir o problema em versões anteriores do Elasticsearch que precederam o lançamento beta do recurso CCR/exclusões reversíveis, sugerindo que CCR/exclusões reversíveis não eram a causa da corrupção do índice.

Observamos, no entanto, que os testes e pesquisas que resultaram na corrupção do índice tinham sido conduzidos no Google Cloud Platform (GCP) usando a imagem Ubuntu Xenial da Canonical e SSDs locais com kernel 4.15-0-*-gcp e que não foi possível reproduzir o problema em um ambiente bare metal com o mesmo sistema operacional e discos SSD (usando kernels HWE 4.13 ou 4.15).

Nossa atenção imediatamente se voltou para o Elastic Cloud para obter mais dados sobre o problema e conhecer seu impacto. Em um fluxo de trabalho paralelo, descartamos várias suspeitas testando diferentes sistemas de arquivos, desabilitando o RAID e, finalmente, usando um kernel de linha principal mais recente; nenhuma dessas tentativas eliminou as corrupções.

Exploração mais a fundo com o suporte do Google Cloud

Nesse ponto, criamos um conjunto de scripts de reprodução simples para facilitar as reproduções e envolvemos nosso parceiro Google para começar a analisar problemas do ambiente mais profundamente.

O suporte do GCP conseguiu reproduzir o problema de maneira confiável usando os scripts fornecidos, com um exemplo de mensagem de erro:

org.apache.lucene.index.CorruptIndexException: codec footer mismatch (file 
truncated?): actual footer=-2016340526 vs expected footer=-1071082520 
(resource=BufferedChecksumIndexInput(MMapIndexInput(path="/home/dl/es/data/nodes/0/indices/IF1vmFH6RY-MZuNfx2IO4w/0/index/_iya_1e_Lucene80_0.dvd")))

Durante o período em que o problema ocorreu, a taxa de transferência de E/S ficou acima de 160 MB/s com 16 mil IOPS. Essa observação foi consistente em vários testes. Como o Elasticsearch (por padrão) usa arquivos mapeados na memória para parte de seu armazenamento, suspeitamos que alguns acessos a arquivos estivessem causando mais falhas de página importantes, resultando em um aumento nas operações de E/S para o disco e desencadeando o problema. Para reduzir a ocorrência de uma falha de página, tentamos aumentar a memória das instâncias do GCP de 32 GB para 52 GB. Com o aumento da memória, o problema não ocorreu mais, e a taxa de transferência de E/S ficou em 50 MB/s com 4 mil IOPS. 

A primeira descoberta aconteceu quando observamos uma diferença entre o GCP e o ambiente bare metal: o kernel do GCP tinha um recurso chamado camada de bloqueio de várias filas (blk_mq) habilitado, que o kernel do bare metal não tinha[2]. Para complicar ainda mais, após uma determinada versão[3] não é mais possível desabilitar o blk_mq[4] na imagem Linux Ubuntu -gcp por meio das opções do kernel. O suporte do GCP nos mostrou como desabilitar o blk_mq exportando a imagem Ubuntu do GCP e recriá-la sem o recurso VIRTIO_SCSI_MULTIQUEUE guestOS, que habilita o SCSI de várias filas[5].

A segunda descoberta foi conseguir reproduzir a corrupção no bare metal: isso se tornou possível apenas habilitando explicitamente o blk_mq, mesmo usando um kernel mais antigo: 4.13.0-38-generic. Também verificamos que os discos NVMe não apresentavam esse problema.

Nesse ponto, sabíamos que as corrupções aconteciam quando as duas condições a seguir eram atendidas:

  • Unidades SSD usando a interface SCSI (discos NVMe não são afetados)
  • blk_mq habilitado

O suporte do GCP compartilhou duas soluções alternativas adicionais (além de usar apenas discos NVMe): aumentar a memória da instância ou criar imagens de instância customizadas com SCSI de várias filas desabilitado.

A Canonical junta-se ao esforço

Embora tivéssemos algumas soluções alternativas, ainda não estávamos satisfeitos:

  • O problema não era específico das imagens Ubuntu no GCP; ele também aconteceu no bare metal.
  • Não sabíamos qual commit do kernel introduziu o problema.
  • Não sabíamos se já havia uma correção disponível em um kernel mais recente.

Para abordar esses pontos, entramos em contato com nossa parceira Canonical para nos aprofundarmos um pouco mais.

A Canonical iniciou um grande esforço de teste usando os scripts de reprodução da Elastic, primeiro confirmando que a corrupção não ocorreu nos kernels de linha principal do Ubuntu iguais ou superiores ao 5.0 com unidades SSD (usando programadores de E/S de várias filas mq-deadline ou nenhum).

A próxima etapa foi retroceder nas versões do kernel para encontrar o delta mínimo entre um kernel que exibe corrupção e outro que não exibe. Usando vários ambientes de teste paralelos (já que um teste completo pode levar até cinco dias), a Canonical descobriu que o 4.19.8 foi o primeiro kernel de linha principal do Ubuntu a incluir as correções de corrupção[6].

Os backports ausentes para o kernel 4.15.0 e derivados são descritos no controle de falhas da Canonical em LP#1848739, e mais detalhes podem ser encontrados neste artigo e na ocorrência de falha do kernel.org.

Depois que a Elastic e a Canonical confirmaram que um kernel GCP com patch incluindo todos os backports necessários corrigia o problema, eles foram mesclados ao kernel principal do Ubuntu 4.15.0 e, consequentemente, todos os kernels derivados (incluindo o -gcp) receberam as correções.

Conclusão

A Elastic está empenhada em desenvolver novos recursos do Elastic Stack que aprimorem cada uma de nossas três soluções principais. Esses esforços têm o suporte de engenheiros e parceiros talentosos que estão sempre vigilantes para que você não precise se preocupar. Se e quando encontrarmos problemas durante os testes, saiba que a Elastic e sua rede de parceiros próximos não medirão esforços para garantir que você tenha a melhor experiência possível.

Por meio de nossa estreita colaboração com o Google e a Canonical, fomos capazes de chegar ao fundo do problema, o que levou ao lançamento dos seguintes kernels HWE do Ubuntu corrigidos:

O uso das versões acima ou mais recentes evitará corrupções quando discos SSD forem usados junto com o blk-mq com SCSI habilitado.

Se você não quer se preocupar se seu ambiente está protegido contra essa corrupção de dados, experimente o Elastic Cloud — nossos usuários já estão protegidos.

Notas de rodapé

[1] O Elasticsearch não verifica os checksums o tempo todo, pois é uma operação cara. Certas ações podem disparar a verificação de checksum com mais frequência, como a realocação de shard ou a geração de um snapshot, fazendo com que corrupções silenciosas subjacentes pareçam ter sido causadas por essas ações.

[2] Para verificar se o blk_mq está em uso para discos SCSI ou de mapeamento de dispositivos, use cat /sys/module/{scsi_mod,dm_mod}/parameters/use_blk_mq

[3] Após https://patchwork.kernel.org/patch/10198305, o blk_mq é forçado para dispositivos SCSI e não pode ser desabilitado por meio das opções do kernel. Foi feito o backport desse patch no Ubuntu linux-gcp.

[4] Para desabilitar o blk_mq, os seguintes parâmetros precisam ser passados para o kernel, por exemplo, via grub: GRUB_CMDLINE_LINUX="scsi_mod.use_blk_mq=N dm_mod.use_blk_mq=N". A habilitação pode ser feita definindo essas opções como N, mas cuidado com [3].

[5] Exemplo de comandos gcloud para desabilitar o recurso VIRTIO_SCSI_MULTIQUEUE guestOS recriando a imagem Ubuntu:

# gcloud compute images export --image-project ubuntu-os-cloud --image-family ubuntu-1604-lts --destination-uri=gs://YOUR_BUCKET/ubuntu_16.04.tar.gz 
# gcloud compute images create ubuntu-1604-test --family ubuntu-1604-lts --source-uri=gs://YOUR_BUCKET/ubuntu_16.04.tar.gz

[6] Backports

    - blk-mq: quiesce queue during switching io sched and updating nr_requests 
    - blk-mq: move hctx lock/unlock into a helper 
    - blk-mq: factor out a few helpers from __blk_mq_try_issue_directly 
    - blk-mq: improve DM's blk-mq IO merging via blk_insert_cloned_request feedback 
    - dm mpath: fix missing call of path selector type->end_io 
    - blk-mq-sched: remove unused 'can_block' arg from blk_mq_sched_insert_request 
    - blk-mq: don't dispatch request in blk_mq_request_direct_issue if queue is busy 
    - blk-mq: introduce BLK_STS_DEV_RESOURCE 
    - blk-mq: Rename blk_mq_request_direct_issue() into 
      blk_mq_request_issue_directly() 
    - blk-mq: don't queue more if we get a busy return 
    - blk-mq: dequeue request one by one from sw queue if hctx is busy 
    - blk-mq: issue directly if hw queue isn't busy in case of 'none' 
    - blk-mq: fix corruption with direct issue 
    - blk-mq: fail the request in case issue failure 
    - blk-mq: punt failed direct issue to dispatch list