O banco de dados de série temporal (TSDB) no Prometheus 2 é um excelente exemplo de solução de engenharia que oferece grandes melhorias em relação ao armazenamento v2 no Prometheus 1 em termos de velocidade de acumulação de dados, execução de consultas e eficiência de recursos. Estávamos implementando o Prometheus 2 no Percona Monitoring and Management (PMM) e tive a oportunidade de entender o desempenho do Prometheus 2 TSDB. Neste artigo falarei sobre os resultados dessas observações.
Carga de trabalho média do Prometheus
Para aqueles acostumados a lidar com bancos de dados de uso geral, a carga de trabalho típica do Prometheus é bastante interessante. A taxa de acumulação de dados tende a ser estável: normalmente os serviços que você monitora enviam aproximadamente o mesmo número de métricas e a infraestrutura muda de forma relativamente lenta.
Os pedidos de informação podem provir de diversas fontes. Alguns deles, como os alertas, também buscam um valor estável e previsível. Outros, como solicitações de usuários, podem causar intermitências, embora esse não seja o caso da maioria das cargas de trabalho.
Teste de carga
Durante os testes, concentrei-me na capacidade de acumular dados. Implantei o Prometheus 2.3.2 compilado com Go 1.10.1 (como parte do PMM 1.14) no serviço Linode usando este script:
Todos os testes a seguir foram realizados em um servidor Linode com oito núcleos virtuais e 32 GB de memória, executando 20 simulações de carga monitorando duzentas instâncias MySQL. Ou, nos termos do Prometheus, 800 alvos, 440 arranhões por segundo, 380 mil registros por segundo e 1,7 milhão de séries temporais ativas.
Projeto
A abordagem usual dos bancos de dados tradicionais, incluindo aquele usado pelo Prometheus 1.x, é storage.tsdb.min-block-duration
, que determina por quanto tempo as gravações serão mantidas na memória antes de serem descarregadas no disco (o padrão é 2 horas). A quantidade de memória necessária dependerá do número de séries temporais, rótulos e raspagens adicionadas ao fluxo de entrada líquido. Em termos de espaço em disco, o Prometheus pretende utilizar 3 bytes por registro (amostra). Por outro lado, os requisitos de memória são muito maiores.
Embora seja possível configurar o tamanho do bloco, não é recomendado configurá-lo manualmente, portanto você é forçado a fornecer ao Prometheus a quantidade de memória necessária para sua carga de trabalho.
Se não houver memória suficiente para suportar o fluxo de entrada de métricas, o Prometheus ficará sem memória ou o assassino OOM chegará até ele.
Adicionar swap para atrasar a falha quando o Prometheus fica sem memória realmente não ajuda, porque usar esta função causa um consumo explosivo de memória. Acho que tem algo a ver com Go, seu coletor de lixo e a maneira como ele lida com a troca.
Outra abordagem interessante é configurar o bloco principal para ser descarregado no disco em um determinado momento, em vez de contá-lo desde o início do processo.
Como você pode ver no gráfico, as liberações no disco ocorrem a cada duas horas. Se você alterar o parâmetro min-block-duration para uma hora, essas redefinições ocorrerão a cada hora, começando após meia hora.
Se quiser usar este e outros gráficos em sua instalação do Prometheus, você pode usar este
Temos um bloco ativo chamado bloco principal que está armazenado na memória; blocos com dados mais antigos estão disponíveis via mmap()
. Isso elimina a necessidade de configurar o cache separadamente, mas também significa que você precisa deixar espaço suficiente para o cache do sistema operacional se quiser consultar dados mais antigos do que o bloco principal pode acomodar.
Isso também significa que o consumo de memória virtual do Prometheus parecerá bastante alto, o que não é motivo de preocupação.
Outro ponto interessante de design é o uso do WAL (write ahead log). Como você pode ver na documentação de armazenamento, o Prometheus usa WAL para evitar travamentos. Os mecanismos específicos para garantir a sobrevivência dos dados não estão, infelizmente, bem documentados. O Prometheus versão 2.3.2 libera o WAL para o disco a cada 10 segundos e esta opção não pode ser configurada pelo usuário.
Compactações
O Prometheus TSDB foi projetado como um armazenamento LSM (Log Structured Merge): o bloco principal é descarregado periodicamente no disco, enquanto um mecanismo de compactação combina vários blocos para evitar a varredura de muitos blocos durante as consultas. Aqui você pode ver a quantidade de blocos que observei no sistema de teste após um dia de carga.
Se quiser saber mais sobre a loja, você pode examinar o arquivo meta.json, que contém informações sobre os blocos disponíveis e como eles surgiram.
{
"ulid": "01CPZDPD1D9R019JS87TPV5MPE",
"minTime": 1536472800000,
"maxTime": 1536494400000,
"stats": {
"numSamples": 8292128378,
"numSeries": 1673622,
"numChunks": 69528220
},
"compaction": {
"level": 2,
"sources": [
"01CPYRY9MS465Y5ETM3SXFBV7X",
"01CPYZT0WRJ1JB1P0DP80VY5KJ",
"01CPZ6NR4Q3PDP3E57HEH760XS"
],
"parents": [
{
"ulid": "01CPYRY9MS465Y5ETM3SXFBV7X",
"minTime": 1536472800000,
"maxTime": 1536480000000
},
{
"ulid": "01CPYZT0WRJ1JB1P0DP80VY5KJ",
"minTime": 1536480000000,
"maxTime": 1536487200000
},
{
"ulid": "01CPZ6NR4Q3PDP3E57HEH760XS",
"minTime": 1536487200000,
"maxTime": 1536494400000
}
]
},
"version": 1
}
As compactações no Prometheus estão vinculadas ao momento em que o bloco principal é descarregado no disco. Neste ponto, várias dessas operações podem ser realizadas.
Parece que as compactações não são limitadas de forma alguma e podem causar grandes picos de E/S no disco durante a execução.
Picos de carga da CPU
É claro que isso tem um impacto bastante negativo na velocidade do sistema e também representa um sério desafio para o armazenamento LSM: como fazer a compactação para suportar altas taxas de solicitação sem causar muita sobrecarga?
O uso da memória no processo de compactação também parece bastante interessante.
Podemos ver como, após a compactação, a maior parte da memória muda de estado de Cache para Livre: isso significa que informações potencialmente valiosas foram removidas de lá. Curioso se é usado aqui fadvice()
ou alguma outra técnica de minimização, ou é porque o cache foi liberado de blocos destruídos durante a compactação?
Recuperação após uma falha
A recuperação de falhas leva tempo e por boas razões. Para um fluxo de entrada de um milhão de registros por segundo, tive que esperar cerca de 25 minutos enquanto a recuperação era realizada levando em consideração a unidade SSD.
level=info ts=2018-09-13T13:38:14.09650965Z caller=main.go:222 msg="Starting Prometheus" version="(version=2.3.2, branch=v2.3.2, revision=71af5e29e815795e9dd14742ee7725682fa14b7b)"
level=info ts=2018-09-13T13:38:14.096599879Z caller=main.go:223 build_context="(go=go1.10.1, user=Jenkins, date=20180725-08:58:13OURCE)"
level=info ts=2018-09-13T13:38:14.096624109Z caller=main.go:224 host_details="(Linux 4.15.0-32-generic #35-Ubuntu SMP Fri Aug 10 17:58:07 UTC 2018 x86_64 1bee9e9b78cf (none))"
level=info ts=2018-09-13T13:38:14.096641396Z caller=main.go:225 fd_limits="(soft=1048576, hard=1048576)"
level=info ts=2018-09-13T13:38:14.097715256Z caller=web.go:415 component=web msg="Start listening for connections" address=:9090
level=info ts=2018-09-13T13:38:14.097400393Z caller=main.go:533 msg="Starting TSDB ..."
level=info ts=2018-09-13T13:38:14.098718401Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536530400000 maxt=1536537600000 ulid=01CQ0FW3ME8Q5W2AN5F9CB7R0R
level=info ts=2018-09-13T13:38:14.100315658Z caller=web.go:467 component=web msg="router prefix" prefix=/prometheus
level=info ts=2018-09-13T13:38:14.101793727Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536732000000 maxt=1536753600000 ulid=01CQ78486TNX5QZTBF049PQHSM
level=info ts=2018-09-13T13:38:14.102267346Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536537600000 maxt=1536732000000 ulid=01CQ78DE7HSQK0C0F5AZ46YGF0
level=info ts=2018-09-13T13:38:14.102660295Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536775200000 maxt=1536782400000 ulid=01CQ7SAT4RM21Y0PT5GNSS146Q
level=info ts=2018-09-13T13:38:14.103075885Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536753600000 maxt=1536775200000 ulid=01CQ7SV8WJ3C2W5S3RTAHC2GHB
level=error ts=2018-09-13T14:05:18.208469169Z caller=wal.go:275 component=tsdb msg="WAL corruption detected; truncating" err="unexpected CRC32 checksum d0465484, want 0" file=/opt/prometheus/data/.prom2-data/wal/007357 pos=15504363
level=info ts=2018-09-13T14:05:19.471459777Z caller=main.go:543 msg="TSDB started"
level=info ts=2018-09-13T14:05:19.471604598Z caller=main.go:603 msg="Loading configuration file" filename=/etc/prometheus.yml
level=info ts=2018-09-13T14:05:19.499156711Z caller=main.go:629 msg="Completed loading of configuration file" filename=/etc/prometheus.yml
level=info ts=2018-09-13T14:05:19.499228186Z caller=main.go:502 msg="Server is ready to receive web requests."
O principal problema do processo de recuperação é o alto consumo de memória. Apesar de em uma situação normal o servidor poder funcionar de forma estável com a mesma quantidade de memória, se travar poderá não se recuperar devido ao OOM. A única solução que encontrei foi desabilitar a coleta de dados, abrir o servidor, deixá-lo recuperar e reiniciar com a coleta habilitada.
Aquecendo
Outro comportamento a ter em conta durante o aquecimento é a relação entre baixo desempenho e alto consumo de recursos logo após o arranque. Durante algumas inicializações, mas não todas, observei uma grande carga na CPU e na memória.
Lacunas no uso de memória indicam que o Prometheus não pode configurar todas as coleções desde o início e algumas informações são perdidas.
Não descobri os motivos exatos da alta carga de CPU e memória. Suspeito que isso se deva à criação de novas séries temporais no bloco principal com alta frequência.
Picos de carga da CPU
Além das compactações, que criam uma carga de E/S bastante alta, notei sérios picos na carga da CPU a cada dois minutos. As rajadas são mais longas quando o fluxo de entrada é alto e parecem ser causadas pelo coletor de lixo do Go, pelo menos alguns núcleos estão totalmente carregados.
Esses saltos não são tão insignificantes. Parece que quando isso ocorre, o ponto de entrada interno e as métricas do Prometheus ficam indisponíveis, causando lacunas de dados durante esses mesmos períodos de tempo.
Você também pode notar que o exportador do Prometheus é desligado por um segundo.
Podemos notar correlações com a coleta de lixo (GC).
Conclusão
O TSDB no Prometheus 2 é rápido, capaz de lidar com milhões de séries temporais e ao mesmo tempo milhares de registros por segundo usando hardware bastante modesto. A utilização de CPU e E/S de disco também é impressionante. Meu exemplo mostrou até 200 métricas por segundo por núcleo usado.
Para planejar a expansão, você precisa se lembrar de quantidades suficientes de memória, e esta deve ser memória real. A quantidade de memória usada que observei foi de cerca de 5 GB por 100 registros por segundo do fluxo de entrada, o que junto com o cache do sistema operacional gerou cerca de 000 GB de memória ocupada.
Claro, ainda há muito trabalho a ser feito para controlar os picos de CPU e E/S de disco, e isso não é surpreendente considerando o quão jovem o TSDB Prometheus 2 é comparado ao InnoDB, TokuDB, RocksDB, WiredTiger, mas todos eles tinham desempenho semelhante. problemas no início do seu ciclo de vida.
Fonte: habr.com