PostgreSQL com direct I/O evita que backups piorem a latência de consultas
A ClickHouse detalhou como troca o cache de página por leitura direta de disco nos backups do Postgres gerenciado, para parar de roubar memória e CPU das consultas em produção.

O backup lê os mesmos discos que atendem a query
Em post publicado em 2 de outubro de 2026 no blog de engenharia da ClickHouse, o engenheiro Kaushik Iska descreve um problema que qualquer DBA reconhece: o backup de um banco de produção não espera um momento de calmaria. Ele lê o diretório de dados inteiro enquanto o banco está servindo tráfego, nos mesmos discos NVMe, pelo mesmo kernel, que atendem cada consulta.
No ClickHouse Managed Postgres↳PostgreSQL11 conteúdosPostgreSQL via SSL com GolangData · abr 20195 itens legais sobre data types do PostgreSQLData · mar 20195 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data →, quando a instância tem mais de um disco NVMe local, eles são combinados com mdadm --level=0 num volume RAID0 montado em /dat, onde o Postgres vive. O agente de backup, wal-g, aponta para o mesmo diretório e envia os arquivos para um bucket de object storage por timeline. Um disco, um conjunto de drives, duas cargas de trabalho competindo ao mesmo tempo.
Leitura em cache é um imposto silencioso sobre o Postgres
Por padrão, toda leitura de arquivo no Linux↳Linux34 conteúdosKali Linux em um Servidor VPS: como, quando e por que usar?DevSecOps · dez 2024Construindo um Windows Service ou Linux Daemon com Worker Service & .NET Core – Parte 2Dev (Back & Front) · jul 2020Criando uma WebApi utilizando .NET, Linux e VSCodeDev (Back & Front) · ago 2019Ver tudo em DevSecOps → passa pela page cache: o kernel guarda uma cópia em memória para o caso de alguém pedir de novo em breve. Isso ajuda quase todo programa, menos um backup, que lê cada arquivo uma única vez, entrega ao uploader e nunca mais toca nele. Como a page cache é compartilhada e finita, o fluxo de centenas de gigabytes do backup empurra para fora dados que o Postgres tinha em memória.
O shared_buffers do Postgres fica protegido, porque vive em huge pages fixadas. Mas tudo o que depende da cache do sistema operacional (arquivos de relação que não cabem em shared_buffers, WAL recém-escrito, arquivos temporários, mapas de visibilidade) fica exposto. No teste da ClickHouse, um backup com leitura em buffer removeu os 40 GiB inteiros de uma tabela que tinha sido lida minutos antes e estava apenas ociosa, não fria.
Direct I/O is the flag that tells the kernel to skip the cache for those reads.
Direct I/O is the flag that tells the kernel to skip the cache for those reads.Kaushik Iska, engenheiro da ClickHouse
A correção óbvia cria um problema novo
Ativar direct I/O no wal-g é uma linha de configuração: WALG_DIRECT_IO=true. O processo abre o arquivo com a flag O_DIRECT, os dados vão direto do bloco de disco para o buffer do processo, e nada fica na page cache. O problema de eviction desaparece.
Mas direct I/O também desliga o readahead do kernel. Em leitura em buffer, o kernel percebe um padrão sequencial e lê adiante em blocos grandes, mantendo a fila do dispositivo cheia. Sob O_DIRECT, o processo recebe exatamente os bytes que pediu e nada mais. Num disco único isso é gerenciável; num RAID0 é um penhasco de throughput.
O RAID0 da ClickHouse divide dados em chunks de 512 KiB, distribuídos entre os discos membros. Uma leitura direta pequena cabe dentro de um único chunk, que vive em um único disco: os outros ficam ociosos para aquela requisição. O tamanho padrão de leitura do wal-g é de 32 blocos de 4 KiB, ou seja, 128 KiB, menor que o próprio chunk. Ativar direct I/O sem mais nada deixa o backup mais lento, não mais rápido.
Dimensionar a leitura pelo stripe inteiro
A saída é fazer cada leitura direta cobrir o stripe inteiro do array. Se uma leitura abrange um chunk em cada disco membro, todos os discos participam da requisição e o array se comporta como o dispositivo paralelo que é. A variável que controla isso é WALG_DIRECT_IO_BLOCK_COUNT, o número de blocos de 4 KiB por requisição:
| Discos no RAID0 | WALG_DIRECT_IO_BLOCK_COUNT | Tamanho de leitura |
|---|---|---|
| 1 | 256 | 1 MiB |
| 4 | 1024 | 4 MiB |
| 8 | 2048 | 8 MiB |
A contagem de discos vem da lista real de dispositivos de armazenamento que o servidor descobre, descontando o volume de boot. Uma leitura direta deveria ser, no mínimo, tão larga quanto o array: qualquer coisa menor deixa parte do stripe ocioso em toda requisição.
Quantos leitores em paralelo, e por que isso muda com direct I/O
O tamanho da leitura é um parâmetro; o número de leitores paralelos (WALG_UPLOAD_DISK_CONCURRENCY) é outro, e o certo depende de onde está o gargalo. Sob leitura em buffer, a ClickHouse usa metade dos vCPUs, porque o readahead do kernel já mantém o dispositivo ocupado sozinho. Sob direct I/O não há readahead: cada leitor submete uma requisição, espera, submete a próxima, e a utilização do disco depende diretamente de quantos leitores estão em voo.
Isso muda a conta em dois casos. Em famílias de NVMe denso (como as instâncias i8g, i8ge, i7i e i7ie da AWS, que carregam muito armazenamento local em relação à computação), cada dispositivo só atinge o teto com muitas requisições pendentes, e a ClickHouse usa o total de vCPUs. Em servidores muito pequenos, com dois vCPUs ou menos, metade significaria um único leitor síncrono, incapaz de manter o dispositivo ocupado, então também se usa o total. Fora desses dois casos, dobrar o número de leitores custaria CPU ao Postgres por pouco ganho de throughput no backup.
O que os números mostram
O teste rodou numa instância i8ge.12xlarge na região us-east-1 da AWS: 48 vCPUs, 384 GiB de RAM, quatro NVMe locais em RAID0 com chunk de 512 KiB, Postgres 18.6 com shared_buffers = 96GB, wal-g v3.0.9, e um banco pgbench de 467 GB, maior que a RAM da máquina. Dezesseis clientes fazem consultas pontuais contínuas, e uma tabela separada de 40 GiB é lida para a cache e deixada ociosa antes de cada rodada, simulando dados que estavam quentes minutos atrás.
| Configuração | Tempo de backup | Leitura do disco | Tabela ociosa evictada | p99 durante o backup |
|---|---|---|---|---|
| A, buffer, 24 leitores | 70 s | 6,0 GB/s | 40 GiB | 0,18 ms |
| B, direct I/O padrão (128 KiB) | 96 s | 5,8 GB/s | 0 GiB | 0,06 ms |
| C, configuração de produção (4 MiB, 48 leitores) | 71 s | 7,7 GB/s | 0 GiB | 0,06 ms |
| D, leitura larga, 24 leitores | 75 s | 7,4 GB/s | 0 GiB | 0,06 ms |
Sem backup rodando, o p99 base era de 0,04 ms. O backup em buffer levou esse número a 4,7 vezes o valor base e deixou um rastro de leituras frias por minutos depois que terminou, porque o cache precisou se reconstruir. Os três arranjos com direct I/O chegaram a 1,5 vez o valor base e devolveram a latência ao normal assim que o backup acabou. O consumo de CPU também caiu: de 65% ocupado com buffer para 60% com direct I/O dimensionado ao stripe, cerca de 14% a menos para o mesmo backup.
Três limites, não só um
Um backup disputa três recursos com o banco: CPU, memória e os discos. A ClickHouse trata os três separadamente. O backup roda em seu próprio cgroup, iniciado com systemd-run --scope em CPUWeight=25 contra o peso padrão de 100, então o escalonador dá ao Postgres quatro vezes mais CPU quando a máquina está ocupada. Os buffers de upload do wal-g são dimensionados para não passar de 5% da RAM. E o direct I/O é o limite para a cache de página e os discos: nada do que o backup lê entra na cache, então o que o Postgres já tinha lá permanece intacto.
O que fica em aberto
Todo backup descrito no post é completo: lê os 467 GB inteiros e envia cerca de 54 GB comprimidos, independentemente de uma linha ou um bilhão de linhas terem mudado desde ontem. Para um banco multi-terabyte, essa é a maior linha de custo de manter backups. A ClickHouse diz estar prototipando backups incrementais, que leriam o diretório de dados, identificariam as páginas alteradas desde o último backup e enviariam só essas, deixando o caminho de leitura (direct I/O, leitura dimensionada ao stripe, pesos de cgroup) inalterado.
Por que isso importa para quem opera Postgres
Para quem administra Postgres em produção, o ponto não é copiar os números exatos da ClickHouse, que dependem do hardware específico, mas entender o raciocínio por trás de cada variável antes de ligar O_DIRECT em qualquer ferramenta de backup (pg_basebackup, wal-g, pgBackRest ou equivalente). Três perguntas valem a pena antes de mexer nisso:
- O array de discos é um RAID0 striped, ou um disco único? Isso define se vale a pena aumentar o tamanho de leitura.
- A ferramenta de backup oferece controle sobre tamanho de leitura e concorrência, ou só um interruptor liga/desliga para direct I/O?
- O monitoramento atual captura eviction de page cache e p99 de consultas durante a janela de backup, ou só olha se o backup terminou com sucesso?
Se a resposta à terceira pergunta for não, esse é o primeiro ajuste a fazer, independente de qualquer configuração de I/O: sem visibilidade sobre o que o backup custa ao resto do banco, qualquer otimização de leitura é um tiro no escuro.
Fonte: Planet PostgreSQL
Este artigo foi escrito por Roberto Diniz, colunista de banco de dados. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.
Como uma cláusula WHERE seletiva pode travar um cluster Galera inteiro
Um incidente real mostra como o ajuste automático de chunks do pt-online-schema-change, pensado para acelerar migrações, pode sobrecarregar a replicação síncrona do Galera e travar o cluster inteiro.














