Write-Ahead Logging: por que um banco de dados escreve a mesma informação duas vezes?

Avatar de Arthur Guedon

Quando comecei a estudar a arquitetura interna dos bancos de dados, uma decisão de projeto sempre me pareceu estranha. Sempre que uma transação altera uma tabela, o banco registra essa alteração em um arquivo de log antes mesmo de atualizar a página onde os dados realmente estão armazenados. Em outras palavras, ele escreve a mesma informação duas vezes. À primeira vista, isso parece um desperdício. Afinal, se o objetivo é atualizar uma tabela, por que registrar a alteração em outro lugar antes de executá-la?

A resposta começa em um problema muito mais antigo do que os próprios bancos de dados. Computadores falham. Falta energia, discos apresentam defeito, sistemas operacionais travam, processos são encerrados inesperadamente e máquinas inteiras deixam de responder. Qualquer sistema que armazene informações importantes precisa assumir que, mais cedo ou mais tarde, alguma dessas situações acontecerá. A questão nunca foi impedir falhas. Sempre foi descobrir como sobreviver a elas.

Imagine uma transferência bancária. O sistema debita R$ 500 de uma conta e credita esse mesmo valor em outra. Agora imagine que o servidor desligue exatamente entre essas duas operações. Quando ele voltar a funcionar, qual deve ser o estado do banco? O dinheiro desapareceu? A transferência aconteceu apenas pela metade? O saldo voltou ao valor anterior?

Esse tipo de problema, conhecido como crash recovery, acompanhou praticamente toda a evolução dos bancos de dados relacionais.

A solução adotada pelos principais sistemas segue um princípio conhecido como Write-Ahead Logging (WAL). Embora o nome tenha se popularizado por causa do PostgreSQL, praticamente todos os grandes bancos implementam a mesma ideia. Oracle utiliza Redo Logs, SQL Server possui o Transaction Log, o InnoDB do MySQL mantém seus próprios Redo Logs, e o Db2 adota mecanismos equivalentes. Mudam os nomes e os detalhes de implementação. O princípio permanece exatamente o mesmo.

Antes de modificar qualquer página de dados, o banco registra a alteração em um log persistente. Somente depois essa alteração pode ser aplicada às páginas onde os dados realmente estão armazenados.

Essa ordem pode parecer apenas um detalhe de implementação, mas muda completamente a forma como um banco de dados lida com falhas.

Imagine a seguinte transação.

BEGIN;
UPDATE contas
SET saldo = saldo - 500
WHERE id = 1;
UPDATE contas
SET saldo = saldo + 500
WHERE id = 2;
COMMIT;

Quando executamos esse código, é comum imaginar que, no momento em que o COMMIT retorna sucesso, as páginas da tabela já foram gravadas no disco.

Na prática, isso nem sempre acontece.

Em muitos bancos relacionais, o que realmente garante a durabilidade da transação não é a gravação imediata das páginas de dados, mas o fato de seus registros já terem sido armazenados com segurança no log de recuperação. As páginas podem permanecer em memória durante algum tempo e serem gravadas posteriormente, quando o gerenciador de buffers considerar esse momento mais eficiente.

Essa é uma inversão interessante de perspectiva. O COMMIT não significa necessariamente que os dados já chegaram às tabelas. Significa que o banco agora consegue recuperá-los, mesmo que o servidor desligue no instante seguinte.

Imagine então que a energia seja interrompida logo após o COMMIT, mas antes que as páginas da tabela tenham sido gravadas definitivamente. Sem um log de recuperação, toda a operação estaria perdida.

Com o Write-Ahead Logging, o banco sabe exatamente quais alterações foram confirmadas e ainda não chegaram às páginas de dados. Durante a recuperação, ele simplesmente reaplica essas alterações até reconstruir o estado correto da base.

É justamente por isso que o banco escreve duas vezes. Não porque seja necessário duplicar os dados, mas porque cada escrita resolve um problema diferente. A primeira garante que a alteração nunca será perdida, a segunda reorganiza os dados da forma mais eficiente possível para futuras consultas.

Outro detalhe interessante é que esse log não armazena comandos SQL.

Quando comecei a estudar esse mecanismo, imaginava que o banco registrasse algo muito parecido com aquilo que escrevemos diariamente.

Algo como:

UPDATE contas
SET saldo = saldo - 500
WHERE id = 1;

Na realidade, isso quase nunca acontece.

Os registros do log descrevem alterações de baixo nível: páginas modificadas, identificadores de registros, posições dentro das páginas e outras informações suficientes para reconstruir exatamente o estado do banco após uma falha.

Cada implementação possui seu próprio formato. O PostgreSQL, por exemplo, registra entradas semelhantes a esta:

rmgr: Heap
tx: 728
lsn: 0/16B6F90
desc: INSERT off 5

Embora pareça pouco legível, esse tipo de registro contém todas as informações necessárias para que o banco refaça uma operação durante o processo de recuperação. O importante não é preservar o SQL original, mas preservar a sequência exata de alterações realizadas internamente.

Essa arquitetura também ajuda a explicar uma característica presente em praticamente todos os bancos relacionais modernos. Como a durabilidade passa a depender do log, e não da gravação imediata das páginas, o banco ganha liberdade para agrupar escritas e reduzir operações de I/O. Em vez de atualizar o disco a cada UPDATE, ele pode manter diversas páginas em memória e gravá-las posteriormente, tornando o sistema muito mais eficiente sem comprometer a consistência dos dados.

Talvez seja justamente por isso que o Write-Ahead Logging raramente apareça quando estudamos bancos de dados pela primeira vez. Ele não muda a forma como escrevemos SQL, não acelera consultas e não melhora índices. Seu trabalho acontece silenciosamente, protegendo informações que esperamos nunca perder.

Em vez de tentar construir computadores incapazes de falhar, seus projetistas aceitaram que falhas são inevitáveis e organizaram toda a arquitetura em torno dessa realidade. O WAL não impede que um servidor desligue, que um disco apresente defeito ou que um sistema operacional trave. Ele apenas garante que, quando tudo voltar a funcionar, os dados ainda estarão exatamente onde deveriam estar.


Deixe um comentário