OpenZFS direct I/O: Como o bypass do ARC elimina o duplo cache em bancos de dados

      Roberto Holanda 12 min de leitura
      OpenZFS direct I/O: Como o bypass do ARC elimina o duplo cache em bancos de dados

      Descubra como a implementação de Direct I/O no OpenZFS resolve o gargalo de duplo cache em bancos de dados, contornando o ARC sem sacrificar a integridade dos dados.

      Compartilhar:

      A arquitetura de sistemas de arquivos modernos é uma maravilha da engenharia de software, mas quando colocamos um gigante como o OpenZFS para rodar sob um banco de dados relacional pesado, presenciamos um choque de titãs. De um lado, temos o ZFS com seu apetite voraz por memória RAM para alimentar o ARC (Adaptive Replacement Cache). Do outro, temos motores como PostgreSQL ou MySQL (InnoDB), que possuem seus próprios gerenciadores de memória e algoritmos sofisticados de cache. O resultado desse encontro, se não for devidamente orquestrado, é um desperdício colossal de recursos de hardware e uma degradação silenciosa de performance.

      Resumo em 30 segundos

      • Bancos de dados e o ZFS competem ferozmente por memória RAM, gerando o temido cenário de duplo cache.
      • Soluções paliativas como limitar o ARC ou alterar o primarycache mitigam o problema, mas introduzem gargalos de CPU e cópias desnecessárias de memória.
      • O suporte nativo a Direct I/O no OpenZFS permite que aplicações ignorem o ARC, mantendo a integridade estrutural, os checksums e a magia do Copy-on-Write intactos.

      A busca pela latência zero em infraestruturas de storage exige que entendamos exatamente como os dados fluem do disco físico (seja um HDD rotacional ou um NVMe de altíssima performance) até o espaço de usuário da aplicação. Vamos dissecar o problema do duplo cache e entender como a implementação do Direct I/O no OpenZFS resolveu um dos maiores dilemas da administração de bancos de dados.

      A tempestade de evicção de memória e os picos de latência no PostgreSQL

      Para compreender o gargalo, precisamos olhar para o comportamento padrão de um servidor de banco de dados. O PostgreSQL, por exemplo, utiliza uma área de memória chamada shared_buffers para manter as páginas de dados mais acessadas (hot data) o mais perto possível da CPU. Quando uma query solicita um registro, o banco verifica primeiro se o bloco está no shared_buffers. Se não estiver, ele emite uma chamada de leitura para o sistema operacional.

      É aqui que o OpenZFS entra em cena. O ZFS intercepta essa chamada e verifica seu próprio cache, o ARC. Se o dado estiver no ARC, ele é copiado para o shared_buffers. Se não estiver, o ZFS busca o dado no disco, armazena uma cópia no ARC e, em seguida, entrega o dado para o banco de dados, que também fará uma cópia no shared_buffers.

      Esse comportamento cria uma redundância arquitetural severa. O mesmo bloco de dados exato passa a residir em dois locais diferentes da memória RAM do servidor. Em um ambiente de alta transação, a memória física se esgota rapidamente. Quando isso acontece, o sistema operacional entra em pânico e inicia uma tempestade de evicção (memory eviction storm). O kernel começa a varrer agressivamente as páginas de memória para liberar espaço, forçando o ZFS a encolher o ARC e o banco de dados a descartar seus próprios buffers. Esse processo consome ciclos preciosos de CPU e gera picos de latência de cauda (tail latency) que destroem a previsibilidade da sua aplicação.

      A anatomia do duplo cache entre o ARC do ZFS e o buffer pool do banco de dados

      O ARC não é um cache de página (page cache) comum como o encontrado no Ext4 ou XFS. Ele é um cache de substituição adaptativa, patenteado originalmente pela Sun Microsystems, que equilibra a frequência de acesso (LFU) e a recência de acesso (LRU). Ele é incrivelmente inteligente para cargas de trabalho genéricas de servidores de arquivos. No entanto, essa inteligência tem um custo computacional.

      Quando um banco de dados faz uma leitura, o dado viaja pelo seguinte pipeline:

      1. O disco NVMe lê o bloco físico.

      2. O ZFS verifica os checksums criptográficos (como fletcher4 ou sha256) para garantir a integridade.

      3. O bloco é descompactado (se o LZ4 ou ZSTD estiver ativo).

      4. O bloco é inserido nas estruturas de dados do ARC.

      5. O dado é copiado do espaço de kernel (ARC) para o espaço de usuário (Buffer Pool do banco).

      Diagrama ilustrando o desperdício de memória RAM causado pelo duplo cache entre o ZFS e o banco de dados Figura: Diagrama ilustrando o desperdício de memória RAM causado pelo duplo cache entre o ZFS e o banco de dados

      Essa cópia final (passo 5) exige uma operação de memcpy na CPU. Em servidores modernos com dezenas de gigabytes de throughput de leitura por segundo, o simples ato de copiar dados de um lado para o outro na RAM se torna um gargalo de barramento de memória. O banco de dados já sabe o que quer manter em cache, logo, a inteligência do ARC torna-se redundante e prejudicial.

      ⚠️ Perigo: Deixar o ZFS e o banco de dados competirem livremente pela RAM sem nenhum ajuste de tuning é a receita certa para acionar o OOM Killer (Out of Memory Killer) do Linux, que fatalmente encerrará o processo do seu banco de dados no momento de maior pico de acesso.

      Por que limitar o tamanho do ARC ou usar primarycache=metadata não resolve o gargalo

      Historicamente, administradores de sistemas tentavam contornar esse problema usando duas abordagens principais, ambas com falhas arquiteturais significativas.

      A primeira tentativa comum é estrangular o ZFS limitando o parâmetro zfs_arc_max. A lógica parece sólida: se limitarmos o ARC a apenas 4GB em um servidor de 128GB, deixamos o resto livre para o banco de dados. O problema é que o ARC não armazena apenas dados de usuário, ele armazena metadados críticos do sistema de arquivos (tabelas de blocos, diretórios, mapas de alocação espacial). Ao asfixiar o ARC, você expulsa os metadados da memória. O resultado é que cada operação de I/O passa a exigir leituras adicionais no disco apenas para descobrir onde os dados reais estão localizados, destruindo a performance de IOPS (Input/Output Operations Per Second).

      A segunda abordagem é alterar a propriedade do dataset usando o comando zfs set primarycache=metadata. Isso instrui o ZFS a não armazenar blocos de dados de usuário no ARC, armazenando apenas os metadados. Isso resolve o problema da pressão de memória e mantém os metadados rápidos. Parece perfeito, certo? Errado.

      Mesmo com primarycache=metadata, o OpenZFS legado ainda precisava alocar buffers temporários na memória (conhecidos como dbufs anônimos) para processar a leitura, verificar os checksums e entregar o dado à aplicação. O dado não ficava residente no cache, mas a operação de cópia de memória (memcpy) entre o kernel e o espaço de usuário ainda acontecia. O overhead de CPU continuava lá.

      Abaixo, detalhamos como essas abordagens se comparam:

      Método de Tuning Impacto na Memória RAM Impacto na CPU (Overhead) Integridade ZFS (Checksums) Veredito Arquitetural
      Padrão (Duplo Cache) Altíssimo desperdício Médio 100% Mantida Péssimo para Bancos de Dados
      Limitar zfs_arc_max Reduz a pressão Médio 100% Mantida Prejudica leitura de metadados
      primarycache=metadata Elimina duplo cache Alto (Exige memcpy) 100% Mantida Paliativo ineficiente
      Direct I/O (O_DIRECT) Elimina duplo cache Baixo (Zero-copy) 100% Mantida Solução Definitiva

      A engenharia do direct I/O no OpenZFS para contornar o cache sem sacrificar os checksums

      A verdadeira revolução para cargas de trabalho de banco de dados no ecossistema ZFS chegou com a implementação madura do Direct I/O. Em sistemas operacionais baseados em POSIX, aplicações podem abrir arquivos passando a flag O_DIRECT. Essa flag é um pedido explícito da aplicação: "Por favor, leia e escreva diretamente no disco, ignore qualquer cache de página do sistema operacional".

      Implementar O_DIRECT em sistemas de arquivos tradicionais como Ext4 é relativamente simples, pois eles mapeiam blocos lógicos diretamente para blocos físicos. Mas o ZFS não é um sistema de arquivos tradicional. Ele é um gerenciador de volumes lógicos e um sistema de arquivos transacional baseado em Copy-on-Write (CoW). No ZFS, um bloco lógico pode ser comprimido, criptografado, fragmentado e espalhado por múltiplos discos em um vdev (Virtual Device).

      O desafio da equipe de engenharia do OpenZFS foi: como permitir que os dados fluam diretamente do disco para a aplicação sem passar pelo ARC, mas garantindo que a árvore de Merkle (que valida a integridade de cada byte) não seja quebrada?

      A solução foi uma obra-prima de engenharia de estruturas de dados. Quando uma leitura com O_DIRECT é solicitada, o OpenZFS aciona um caminho de código otimizado. O SPA (Storage Pool Allocator) busca os blocos físicos nos discos. Em vez de alocar memória no ARC, o ZFS utiliza a técnica de DMA (Direct Memory Access) ou mapeamento zero-copy para colocar os dados diretamente nos buffers fornecidos pela aplicação (o shared_buffers do banco de dados).

      Fluxo de dados do Direct I/O no OpenZFS, contornando o ARC mas mantendo a verificação de integridade Figura: Fluxo de dados do Direct I/O no OpenZFS, contornando o ARC mas mantendo a verificação de integridade

      O pulo do gato ocorre na DMU (Data Management Unit). Enquanto os dados fluem para a aplicação, a DMU calcula o checksum em tempo real (on-the-fly). Se o checksum bater com o hash armazenado no bloco pai, a leitura é concluída com sucesso. Se houver corrupção silenciosa de dados (bit rot) no disco físico, o ZFS detecta a falha imediatamente, aciona o mecanismo de self-healing (se houver redundância como espelhamento ou RAID-Z), corrige o bloco em tempo real e entrega o dado íntegro para o banco de dados.

      Tudo isso acontece sem que o bloco de dados toque nas estruturas de cache do ARC. O Copy-on-Write continua funcionando perfeitamente para as escritas, garantindo que o banco de dados nunca sofra com blocos parcialmente gravados (torn pages) em caso de queda de energia. É o melhor dos dois mundos: a performance bruta do hardware aliada à integridade paranoica do ZFS.

      💡 Dica Pro: Para que o PostgreSQL tire proveito dessa arquitetura no OpenZFS, você precisa garantir que o parâmetro io_direct esteja habilitado nas configurações do banco, e que a versão do seu OpenZFS seja recente o suficiente para suportar o bypass completo de leitura e escrita.

      Validando a eficiência do bypass com métricas de I/O e monitoramento do arcstat

      Acreditar na teoria é bom, mas em infraestrutura de storage, nós confiamos apenas em métricas. Após configurar seu banco de dados para utilizar Direct I/O sobre um dataset ZFS, é imperativo validar se o bypass está realmente ocorrendo.

      A ferramenta principal para essa auditoria é o arcstat, um utilitário nativo do OpenZFS que exibe estatísticas em tempo real do ARC. O que você deve procurar ao monitorar um servidor de banco de dados com Direct I/O ativo é uma mudança drástica no perfil de hits (acertos) do cache.

      Antes do Direct I/O, você veria a métrica demand_data_hits (acertos de dados solicitados pela aplicação) extremamente alta, indicando que o ZFS estava servindo os dados do banco. Após a ativação do O_DIRECT, essa métrica deve despencar para quase zero. Isso não é um erro, é a prova do sucesso. Significa que os dados estão contornando o ARC.

      Simultaneamente, a métrica demand_metadata_hits deve permanecer altíssima (idealmente acima de 95%). Isso comprova que o ARC está fazendo exatamente o que deveria fazer neste cenário: armazenar apenas a estrutura de diretórios e os mapas de blocos, permitindo que o ZFS encontre os dados no disco na velocidade da luz.

      Monitoramento de métricas do arcstat evidenciando a queda na latência após a adoção do Direct I/O Figura: Monitoramento de métricas do arcstat evidenciando a queda na latência após a adoção do Direct I/O

      Além do arcstat, o uso do comando zpool iostat -l (que exibe a latência de I/O) revelará uma redução substancial na latência de leitura. Sem o overhead de alocação de memória no kernel e sem as cópias de memcpy, o tempo de resposta entre a requisição do banco de dados e a entrega do dado pelo disco NVMe se aproxima do limite teórico do hardware.

      Recomendação arquitetural para infraestruturas críticas

      A adoção do Direct I/O no OpenZFS não é uma bala de prata para todos os cenários. Se você administra um servidor de arquivos genérico (Samba/NFS) ou um repositório de backups, o ARC continua sendo seu melhor amigo e desativá-lo destruirá sua performance.

      No entanto, para arquiteturas de dados de alta densidade, onde motores como PostgreSQL, MySQL ou hipervisores como KVM/QEMU gerenciam seus próprios estados de memória, o bypass do ARC é mandatório. Ele devolve o controle da RAM para a aplicação que realmente entende a semântica dos dados, elimina a latência induzida por cópias de memória e permite que o ZFS faça o que faz de melhor: garantir que nenhum bit seja corrompido, desde o momento em que toca o disco até o momento em que é processado pela CPU. Ajuste seus datasets, monitore suas métricas e deixe o hardware respirar.


      Referências & Leitura Complementar

      • OpenZFS Pull Request #10018: Implementação base do Direct I/O (O_DIRECT) detalhando a arquitetura de bypass do ARC.

      • PostgreSQL Documentation: Seção sobre Resource Consumption e o uso de shared_buffers em conjunto com caches de sistema operacional.

      • SNIA (Storage Networking Industry Association): Documentações técnicas sobre o impacto do Zero-copy e Direct Memory Access (DMA) em storages transacionais.


      O que é Direct I/O (O_DIRECT) no contexto de sistemas de arquivos? É uma flag de sistema operacional que instrui o sistema de arquivos a ler e escrever dados diretamente no disco físico, ignorando completamente o cache de página (page cache) do kernel. No ecossistema OpenZFS, isso significa realizar o bypass do ARC (Adaptive Replacement Cache), evitando que os dados consumam memória RAM desnecessariamente.
      O Direct I/O no ZFS desativa os checksums ou o Copy-on-Write? Não. A genialidade da implementação do OpenZFS é que o Direct I/O ignora apenas o armazenamento em cache (ARC), mas os blocos de dados ainda passam pelo pipeline transacional do ZFS, especificamente pela DMU e pelo SPA. Isso garante que os checksums criptográficos, a compressão e a integridade estrutural do Copy-on-Write permaneçam 100% intactos e funcionais.
      Quais aplicações mais se beneficiam do bypass do ARC? Bancos de dados relacionais pesados, como PostgreSQL e MySQL (InnoDB), além de hipervisores de virtualização como KVM/QEMU. Essas aplicações já possuem seus próprios mecanismos sofisticados de gerenciamento de memória e cache interno. Para elas, o cache adicional do sistema de arquivos é redundante, consome ciclos de CPU com cópias de memória e é altamente prejudicial à performance geral.
      #OpenZFS #Direct I/O #ARC cache #PostgreSQL #Storage enterprise #ZFS performance
      Roberto Holanda
      Assinatura Técnica

      Roberto Holanda

      Guru de Sistemas de Arquivos (ZFS/Btrfs)

      "Dedico minha carreira à integridade dos dados. Para mim, o bit rot é o inimigo e o Copy-on-Write é a salvação. Exploro a fundo ZFS, Btrfs e a beleza dos checksums."