Pular para o conteúdo

Papers

Life beyond Distributed Transactions: an Apostate's Opinion

Pat Helland · CIDR 2007, 2007

O problema: aplicações em escala enorme não usam transações distribuídas

Helland abre o position paper com uma confissão: ele passou boa parte da carreira defendendo plataformas que oferecem serializabilidade global, isto é, protocolos como two-phase commit (2PC), Paxos e abordagens de quórum que dão ao programador a ilusão de atualizar dados espalhados por várias máquinas de forma atômica. Pela experiência dele na década anterior, porém, desenvolvedores simplesmente não constroem aplicações muito escaláveis assumindo transações distribuídas; quando tentam, os projetos naufragam porque o custo de desempenho e a fragilidade tornam a abordagem impraticável. Ele compara essas plataformas à Linha Maginot. O paper não tenta resolver alta disponibilidade: o foco declarado é apenas escalabilidade e as consequências de não poder contar com transações distribuídas em larga escala. Para pensar nisso, o autor propõe um experimento mental chamado 'escala quase infinita', em que o número de clientes, pedidos, contas e outras coisas de negócio cresce sem parar, enquanto cada item individual não fica muito maior; a carga passa a ser espalhada por um número enorme de máquinas, tantas que não dá para tratá-las como uma máquina só. O objetivo do paper é nomear e formalizar abstrações que já eram usadas de modo implícito, e discutir padrões de mensagens entre pedaços de dados que podem ser reparticionados com o tempo.

Entidades: a fronteira da transação é uma chave única

A ideia central é uma abstração chamada entidade. Uma entidade é uma coleção de dados com uma chave única, que vive dentro de um único escopo de serializabilidade (uma máquina ou um pequeno cluster). Por isso, dentro de uma entidade sempre é possível fazer transações atômicas, mas não entre entidades. Cada dado pertence a exatamente uma entidade, e os dados de uma entidade nunca se sobrepõem aos de outra; o exemplo do paper é um sistema de pedidos em que cada pedido, identificado por um Order-ID, precisa ser disjunto dos demais. A representação interna é livre (registros SQL, XML, arquivos, blobs). O paper assume duas camadas: o código de negócio de cima é 'agnóstico de escala' e só conhece a chave da entidade, enquanto a camada de baixo sabe onde cada entidade está e pode movê-la quando reparticiona. Como a camada de baixo pode colocar duas entidades em máquinas diferentes, o código de cima nunca pode contar com atomicidade entre chaves distintas. Com particionamento por faixa de chaves, chaves adjacentes costumam ficar na mesma máquina, então um teste que depende de atomicidade com o vizinho provavelmente a encontra no ambiente de teste; só quando um redeploy move as entidades para escopos diferentes o bug latente aparece. Outra consequência: índices alternativos não podem ser mantidos de forma transacionalmente consistente com a entidade, pois a cópia do índice fica em outro escopo; a aplicação deve aceitar que o índice pode estar defasado e usar um lookup em dois passos (chave alternativa, depois chave da entidade).

Mensagens entre entidades: entrega at-least-once e idempotência

Se não dá para atualizar duas entidades na mesma transação, a ligação entre elas é uma mensagem. O paper pede que o envio seja assíncrono em relação à transação que o originou: a mensagem só fica visível no destino depois que a transação de envio faz commit, e o enfileiramento é transacional, para não existir mensagem enviada por uma transação que abortou. A mensagem é endereçada à chave da entidade; achar a localização é trabalho da camada de baixo, que entrega a mensagem pelo menos uma vez. Segundo o autor, a maioria das aplicações usa entrega at-least-once, porque a entrega da mensagem não está acoplada à atualização do dado durável: o dado é atualizado, a máquina falha antes do acknowledgement e a mensagem é processada de novo. A alternativa seria perder mensagens (at-most-once), que ele considera ainda mais difícil. Quando entidades se movem durante o reparticionamento, mensagens podem ser repetidas e chegar fora de ordem. A consequência é que a aplicação precisa processar tudo de forma idempotente, isto é, uma repetição não pode causar mudança substantiva na entidade (o que é 'substantivo' é definido pela aplicação). Mensagens que só leem, ou que alteram a entidade sem mudança substantiva, são naturalmente idempotentes; as que causam mudança substantiva exigem que a entidade lembre, como estado, que já processou a mensagem e, se houver resposta, devolva a mesma.

Atividades: o estado que uma entidade guarda sobre cada parceiro

Para organizar esse estado, o paper introduz o conceito de atividade: a informação local que uma entidade mantém sobre o relacionamento com cada entidade parceira, incluindo o conhecimento das mensagens já recebidas. Cada atividade vive em exatamente uma entidade, e uma entidade terá uma atividade para cada parceiro de quem recebe mensagens. O autor defende que focar no estado por parceiro facilita construir aplicações escaláveis; um uso é implementar o processamento idempotente, lembrando de forma durável a transição de 'mensagem ainda pode ser processada' para 'mensagem não terá mais efeito substantivo', usando alguma característica única da mensagem. O exemplo do paper é um pedido com vários itens: há uma entidade para o pedido e uma entidade para cada item gerenciado pelo armazém, não há transações entre elas, e os dados por item dentro da entidade do pedido são atividades. Em escala quase infinita não se pode simplesmente fazer uma consulta para descobrir o que é relacionado; tudo precisa ser ligado por uma teia de relacionamentos entre duas partes, costurada com chaves de entidade. O autor também observa que é difícil, embora possível, deduplicar mensagens entre entidades de granularidade fina na infraestrutura, porque o conhecimento de deduplicação teria de migrar junto com a entidade; por isso o padrão comum é a lógica de aplicação garantir a idempotência.

Operações tentativas, incerteza e a previsão do autor

Sem transações atômicas entre entidades, chegar a um acordo implica aceitar incerteza por um tempo. Em transações distribuídas ela aparece como locks gerenciados pelo gerenciador de transações; aqui ela passa para a semântica do negócio, e o autor resume isso como 'simplesmente workflow', só que de granularidade fina, com entidades como participantes. O mecanismo é a operação tentativa: uma entidade pede a outra, por mensagem, que assuma um compromisso que deixa aberta a possibilidade de cancelamento. Quem aceita concorda em deixar o outro decidir o resultado. Essencial é o direito de cancelar: se o solicitante desiste, ocorre um cancelamento; se libera esse direito, ocorre uma confirmação, e toda operação tentativa eventualmente confirma ou cancela. O exemplo do paper é o armazém que aloca estoque para um pedido sem saber se será usado e depois descobre se ele é necessário, com uma atividade por pedido dentro de cada item para controlar essa incerteza. Outra analogia é o escrow numa compra de casa, uma estrutura em estrela de acordos entre duas partes. O autor também indica que a incerteza vira conceitos de negócio como estoque reservado ou alocação contra linha de crédito. Nas conclusões, ele prevê que, em alguns anos, esses padrões levariam a novos middlewares ou plataformas que automatizem o gerenciamento dessas aplicações, em paralelo ao surgimento dos monitores de TP nos anos 1970.

Veja as ideias em ação

Pratique na escola de System Design os desenhos que usam este paper.