Diagnóstico de Latência NVMe: Quando a Média Mente e o eBPF Revela a Verdade
Aprenda a diagnosticar latência de cauda (tail latency) em storage NVMe usando observabilidade eBPF. Descubra por que o iostat falha e como usar histogramas in-kernel para ver o invisível.
Se você está olhando para a latência média do seu array de armazenamento e sorrindo porque o painel mostra "0.5ms", você está operando no escuro. A média é uma métrica de conforto. Ela existe para fazer gerentes se sentirem seguros e para esconder a realidade suja e caótica do que realmente acontece nos chips NAND do seu SSD.
Na engenharia de observabilidade, aprendemos que o "nines" (99.9%) não importam se o usuário final está tendo uma experiência miserável. No mundo do armazenamento de alto desempenho, especialmente com NVMe (Non-Volatile Memory Express), a latência de cauda (tail latency) é onde os dragões vivem. Um disco que responde a 100 microssegundos na maior parte do tempo, mas trava por 500 milissegundos a cada 10.000 requisições, não é um disco rápido. É um disco quebrado que está destruindo seu SLA de banco de dados silenciosamente.
Resumo em 30 segundos
- A Média é Tóxica: Em armazenamento NVMe, métricas agregadas como médias escondem picos de latência causados por Garbage Collection e contenção de filas, que são os verdadeiros culpados pela degradação da aplicação.
- Cegueira do
iostat: Ferramentas tradicionais baseadas em amostragem de segundos (comoiostatousar) são matematicamente incapazes de detectar micro-travamentos que duram milissegundos em dispositivos capazes de 500k IOPS.- eBPF é a Resposta: A única maneira de ver a verdade é instrumentar o kernel Linux com eBPF para rastrear cada bio (Block I/O) individualmente, gerando histogramas e mapas de calor que revelam a latência real percebida pela aplicação.
Quando o p99 de latência destrói o SLA enquanto a média dorme
Vamos falar sobre a matemática da decepção. Imagine um cluster Kubernetes rodando uma carga de trabalho transacional pesada em cima de volumes NVMe. Seu sistema de monitoramento coleta métricas a cada 10 ou 60 segundos. Durante esse intervalo, seu SSD Enterprise processou 500.000 operações de I/O.
Se 499.000 dessas operações levaram 80 microssegundos (µs) e 1.000 operações levaram 200 milissegundos (ms) devido a um bloqueio no controlador, qual é a média? A aritmética simples dilui esses 200ms no oceano de requisições rápidas. O painel mostrará um leve aumento, talvez para 0.4ms. Ninguém recebe um alerta. O engenheiro de plantão continua dormindo.
No entanto, para a aplicação, essas 1.000 requisições lentas não foram "diluídas". Elas foram travamentos. Se for um banco de dados relacional como PostgreSQL ou MySQL, essas latências de disco seguraram locks de linha ou tabela. O que começou como uma latência de disco transformou-se em uma fila de conexões de aplicação, estourando o pool de threads e causando timeouts em cascata nos microsserviços acima.
Fig. 1: A Ilusão da Média. Enquanto a ferramenta de monitoramento mostra estabilidade, o histograma revela que 1% das requisições estão sofrendo latência extrema.
💡 Dica Pro: Nunca configure alertas baseados em latência média de disco. Configure alertas no p95, p99 e, se você for paranoico (e deveria ser), no p99.9 ou latência máxima (max). O outlier é o único sinal que importa para a estabilidade do sistema.
A anatomia de uma parada de escrita no controlador NVMe
Para entender por que esses picos ocorrem, precisamos descer ao nível do silício. Ao contrário de um disco magnético (HDD) onde a latência é dominada pela física mecânica (rotação do prato e busca do braço), o SSD NVMe é dominado pela gestão interna da memória flash.
A memória NAND Flash tem uma característica irritante: você pode ler e escrever em páginas (geralmente 4KB ou 16KB), mas só pode apagar em blocos (que podem ter vários MBs). Quando você precisa sobrescrever dados, o controlador do SSD não pode simplesmente escrever por cima. Ele precisa ler o bloco inteiro para o cache, modificar a página, apagar o bloco físico e reescrever tudo. Isso é lento.
Para mitigar isso, os controladores modernos usam Over-provisioning e caches SLC (Single-Level Cell) que aceitam escritas muito rápidas. Mas o que acontece quando esse buffer enche? O controlador entra em pânico. Ele precisa parar de aceitar novas escritas para realizar o Garbage Collection (GC) — mover dados válidos, apagar blocos sujos e liberar espaço.
Durante esse processo, que pode durar de alguns milissegundos a segundos inteiros em drives de consumo (como QLC sem DRAM), o I/O é bloqueado. O sistema operacional envia o comando NVMe, mas o dispositivo não responde com a interrupção de conclusão. O kernel Linux coloca a thread em estado D (Uninterruptible Sleep).
Se você estiver usando SSDs de "datacenter" baratos ou drives de consumidor em um servidor de produção (comum em Home Labs e setups TrueNAS de baixo custo), você verá isso frequentemente. O datasheet promete "3.000 MB/s", mas não diz "apenas pelos primeiros 10 segundos".
A falácia de confiar no iostat para eventos de microssegundos
Muitos administradores de sistemas ainda confiam cegamente no iostat -x 1. Essa ferramenta é venerável, mas é uma relíquia de uma era onde os discos faziam 100 IOPS, não 1.000.000.
O problema fundamental do iostat é a resolução. Ele lê o arquivo /proc/diskstats. Esses contadores são incrementais. Quando você roda o comando com intervalo de 1 segundo, ele subtrai os valores atuais dos valores anteriores e divide pelo tempo.
⚠️ Perigo: O campo
%utilno iostat é matematicamente inútil para SSDs modernos. Ele mede a saturação do tempo, não a capacidade do dispositivo. Um NVMe pode estar em 100% de%util(ocupado durante todo o segundo) mas processando apenas uma fração da sua largura de banda ou IOPS máximos devido ao paralelismo interno das filas NVMe.
Além disso, o await (tempo médio de espera) sofre do mesmo problema da média que discutimos acima. Se um evento de GC de 50ms acontece dentro de uma janela de 1 segundo, o iostat vai "suavizar" esse evento. Você verá um pequeno solavanco no gráfico, enquanto sua aplicação viu uma parede de tijolos. Você não pode depurar eventos de microssegundos com ferramentas de resolução de segundos.
Instrumentando a camada de bloco com eBPF para capturar outliers
Aqui entra a observabilidade moderna. O eBPF (Extended Berkeley Packet Filter) nos permite executar programas seguros e de alto desempenho dentro do kernel Linux, sem a necessidade de recompilar módulos ou arriscar travar a máquina (ao contrário dos módulos de kernel antigos).
Para armazenamento, o eBPF é um superpoder. Podemos anexar sondas (kprobes ou tracepoints) diretamente nas funções da camada de bloco do kernel. Não precisamos adivinhar o que o disco está fazendo; podemos medir exatamente quanto tempo o kernel levou desde o momento em que decidiu fazer I/O até o momento em que o dispositivo disse "pronto".
Os pontos de inserção cruciais geralmente envolvem a estrutura struct bio ou struct request.
Fig. 2: Pontos de Inserção do eBPF. Podemos interceptar a requisição exatamente quando ela entra na fila do kernel (bio_start) e quando o dispositivo confirma a conclusão (bio_done).
Ao usar ferramentas como biolatency (do pacote BCC) ou scripts customizados em bpftrace, interceptamos dois eventos principais:
block_rq_issue: O momento exato em que a requisição é enviada ao driver do dispositivo.block_rq_complete: O momento em que o dispositivo sinaliza a conclusão.
A diferença de tempo entre esses dois eventos é a latência real do hardware, livre de ruídos do agendador de processos ou da rede.
Exemplo prático com bpftrace
Se você quiser ver a distribuição de latência de I/O em tempo real, sem médias, você pode usar um one-liner de bpftrace. Este comando cria um histograma logarítmico da latência de cada operação de disco:
bpftrace -e 'tracepoint:block:block_rq_issue { @start[args->dev, args->sector] = nsecs; } tracepoint:block:block_rq_complete /@start[args->dev, args->sector]/ { @us = hist((nsecs - @start[args->dev, args->sector]) / 1000); delete(@start[args->dev, args->sector]); }'
O resultado não é um número único. É uma distribuição. Você verá uma curva de sino (Bell Curve). Se essa curva tiver uma "cauda longa" para a direita, ou se for bimodal (duas montanhas separadas), você encontrou seu problema. A primeira montanha são os hits de cache/DRAM do SSD; a segunda montanha, muito mais lenta, são as escritas diretas na NAND ou contenção de GC.
Transformando traces de kernel em mapas de calor de latência
Histogramas são ótimos, mas eles removem a dimensão do tempo. Eles dizem "aconteceu latência alta", mas não dizem quando. Para correlacionar com logs de aplicação ou eventos de sistema, precisamos de Mapas de Calor (Heatmaps).
Um mapa de calor de latência plota:
Eixo X: Tempo.
Eixo Y: Latência (escala logarítmica).
Cor (Z): Quantidade de requisições (densidade).
Ao visualizar dados de armazenamento dessa forma, padrões invisíveis emergem.
Estrias Verticais Periódicas: Se você vê linhas verticais de alta latência a cada 5 minutos, verifique seus cron jobs. Pode ser um flush de logs ou um snapshot de backup travando o I/O.
Nuvens de Tempestade: Se a latência aumenta gradualmente e forma uma "nuvem" densa nas faixas superiores (10ms+), seu SSD está saturado. O controlador não consegue limpar blocos rápido o suficiente para acompanhar a taxa de ingestão.
O "Piso" Elevado: Se a latência mínima (a base do gráfico) sobe, você pode estar sofrendo de latência de fila (queue latency) no kernel antes mesmo de chegar ao disco, talvez por contenção de CPU no driver NVMe.
Fig. 3: Mapa de Calor de Latência. As estrias verticais revelam os momentos exatos onde o Garbage Collection do SSD bloqueou o I/O, correlacionando com a lentidão da aplicação.
Em um caso real que investiguei recentemente, um cluster de banco de dados apresentava timeouts aleatórios. O monitoramento padrão mostrava uso de disco em 60%. O mapa de calor gerado via eBPF revelou que, a cada 30 segundos, o drive NVMe (um modelo prosumer sem proteção de perda de energia adequada) travava todas as escritas por 200ms para fazer um journal flush interno forçado. Trocamos por drives Enterprise com capacitores de proteção (PLP) e o mapa de calor ficou limpo instantaneamente.
O custo da ignorância
Ignorar a latência de cauda em armazenamento é uma dívida técnica que cobra juros compostos. Você gasta horas otimizando consultas SQL, ajustando pools de conexões e reescrevendo código, quando o problema físico está na camada de bloco.
A observabilidade não é sobre ter mais dashboards. É sobre ter os dados certos para fazer as perguntas difíceis. Se você não consegue distinguir entre uma latência de rede, uma latência de fila de kernel e uma latência de Garbage Collection de SSD, você não está fazendo engenharia; você está fazendo adivinhação.
O futuro do armazenamento com tecnologias como CXL (Compute Express Link) e SSDs ZNS (Zoned Namespaces) vai tornar a pilha de I/O ainda mais complexa. A abstração do "arquivo" está vazando. Se você não começar a usar eBPF para ver através dessas abstrações hoje, amanhã você será irrelevante. Pare de olhar para a média. Olhe para o caos nas margens. É lá que a verdade reside.
Referências & Leitura Complementar
NVMe Express Base Specification 2.0: Detalhes sobre filas de submissão/conclusão e comportamento do controlador.
Gregg, Brendan. "BPF Performance Tools" (2019): A bíblia da instrumentação de sistemas Linux.
JEDEC JESD218: Padrão para requisitos de SSDs (Client vs Enterprise), essencial para entender as diferenças de endurance e latência sustentada.
Kernel.org Documentation - Block Layer: Documentação oficial sobre a estrutura
bioerequest_queue.
Perguntas Frequentes
1. O uso de eBPF causa impacto na performance do servidor de produção?
O impacto é extremamente baixo, geralmente imperceptível. O código eBPF é compilado JIT (Just-In-Time) e roda no contexto do kernel de forma otimizada. Ao contrário de ferramentas de debug antigas (como strace), ele não para a execução do processo a cada evento. No entanto, em sistemas com milhões de IOPS, rastrear cada evento pode ter um custo de CPU; nesses casos, o uso de amostragem ou histogramas in-kernel (agregando dados antes de enviar ao user-space) é a prática recomendada.
2. Por que meu SSD NVMe de consumo (ex: Samsung 990 Pro) tem latência ruim em cargas de servidor? Drives de consumo são otimizados para "bursts" (uso rápido e intermitente). Eles dependem fortemente de caches SLC dinâmicos. Em cargas de servidor sustentadas (escrita constante 24/7), o cache enche, e o drive é forçado a escrever diretamente na memória TLC/QLC lenta, enquanto tenta limpar o cache simultaneamente. Drives Enterprise (ex: Micron 7450, Intel D7) têm áreas de Over-provisioning muito maiores e firmware ajustado para consistência (QoS), não para picos de velocidade máxima em benchmarks sintéticos.
3. Como posso começar a usar eBPF sem escrever código C?
Comece com o kit de ferramentas BCC (BPF Compiler Collection) ou bpftrace. A maioria das distribuições Linux modernas (Ubuntu 22.04+, RHEL 9) já possui pacotes para eles. Ferramentas prontas como biolatency, biosnoop e biotop já vêm no pacote BCC e cobrem 90% dos casos de uso de diagnóstico de armazenamento sem que você precise escrever uma linha de código.
Lucas Ferreira
Engenheiro de Observabilidade
"Transformo o caos de logs, métricas e traces em clareza operacional. Minha missão é eliminar pontos cegos e garantir que nada permaneça invisível na infraestrutura."