Minimizar coordenação

Armazenamento do Azure
Base de Dados SQL do Azure
Azure Cosmos DB

Minimize a coordenação para alcançar escalabilidade

A maioria das aplicações na cloud consiste em múltiplos serviços de aplicação, como front-ends web, bases de dados, processos de negócio e relatórios e análises. Para alcançar a escalabilidade e a fiabilidade, cada um desses serviços deve ser executado em várias instâncias.

Sistemas descoordenados, onde o trabalho pode ser tratado de forma independente sem a necessidade de passar mensagens entre máquinas, são geralmente mais simples de escalar. A coordenação geralmente não é um estado binário, mas um espectro. A coordenação ocorre em diferentes camadas, como dados ou computação.

O que acontece quando duas instâncias tentam realizar operações simultâneas que afetam um estado partilhado? Em alguns casos, tem de existir coordenação entre os nós, por exemplo, para preservar as garantias de ACID. Neste diagrama, o Node2 está à espera que o Node1 liberte um bloqueio de base de dados:

Diagrama de bloqueio de banco de dados

A coordenação limita as vantagens do dimensionamento horizontal e cria gargalos. Neste exemplo, à medida que escalar a aplicação e adicionar mais instâncias, verá uma maior contenção de bloqueios. Na pior das hipóteses, as instâncias de front-end irão despender a maior parte do tempo a aguardar pelos bloqueios.

A semântica do tipo "exatamente uma vez" é outro ponto de origem frequente de coordenação. Por exemplo, uma encomenda tem de ser processada exatamente uma vez. Dois trabalhadores estão à escuta de novas encomendas. Worker1 recolhe uma encomenda para processamento. A aplicação tem de garantir que o Worker2 não duplica o processo, mas também tem de assegurar que se o Worker1 falhar, a encomenda não é removida.

Diagrama de coordenação

Pode utilizar um padrão como Supervisor do Agente do Agendador para coordenar as tarefas, mas neste caso a melhor abordagem será distribuir as tarefas. A cada trabalhador será atribuído um determinado conjunto de encomendas (por região de faturação, por exemplo). Se um dos workers falhar, uma nova instância irá retomar o processo no ponto em que a instância anterior ficou, mas várias instâncias não estão a competir.

Recomendações

Use componentes dissociados que se comunicam de forma assíncrona. Idealmente, os componentes devem usar eventos para se comunicar uns com os outros.

Adote a consistência eventual. Quando os dados são distribuídos, é necessário haver coordenação para impor garantias de consistência sólidas. Por exemplo, imagine que uma operação atualiza duas bases de dados. Em vez de utilizar um único âmbito de transação, é preferível que o sistema possa acomodar a consistência eventual, talvez utilizando o padrão Transação de Compensação para reverter logicamente após uma falha.

Utilize eventos de domínio para sincronizar o estado. Um evento de domínio é um evento que regista quando algo significativo acontece no domínio. Os serviços interessados podem estar à escuta do evento, em vez de utilizarem uma transação global para coordenar os vários serviços. Se esta abordagem for utilizada, o sistema tem tolerar a consistência eventual (veja o item anterior).

Considere padrões como CQRS e sourcing de eventos. Estes dois padrões podem ajudar a reduzir a disputa entre as cargas de trabalho de leitura e as cargas de trabalho de escrita.

  • O padrão CQRS separa as operações de leitura das operações de escrita. Em algumas implementações, os dados de leitura estão fisicamente separados dos dados de escrita.

  • No padrão Aprovisionamento de Eventos, as alterações de estado são registadas como uma série de eventos num arquivo de dados onde só são feitos acréscimos. Acrescentar um evento ao fluxo é uma operação atómica, que requer um nível de bloqueio mínimo.

Estes dois padrões complementam-se. Se o arquivo só de escrita do CQRS utilizar aprovisionamento de eventos, o arquivo só de leitura pode colocar-se à escuta dos mesmos eventos para criar um instantâneo legível do estado atual, otimizado para consultas. No entanto, antes de adotar o padrão CQRS ou "event sourcing", tenha em atenção os desafios que esta abordagem envolve.

Dados de partição e estado. Evite colocar todos os dados num único esquema de dados partilhado entre vários serviços de aplicação. Este princípio é imposto por uma arquitetura de microsserviços, que faz com que cada serviço seja responsável pelo seu próprio arquivo de dados. Dentro de uma única base de dados, dividir os dados em *shards* pode melhorar a concorrência, porque um serviço que escreve num *shard* não afeta outro serviço que escreve num *shard* diferente. Embora o particionamento adicione algum grau de coordenação, você pode usar o particionamento para aumentar o paralelismo para melhor escalabilidade. Particione o estado monolítico em partes menores para que os dados possam ser gerenciados de forma independente.

Crie operações idempotentes. Sempre que possível, crie as operações de modo a serem idempotentes. Dessa forma, podem ser processadas com a semântica do tipo "pelo menos uma vez". Por exemplo, pode colocar itens de trabalho numa fila. Se um trabalhador sofrer um acidente a meio de uma operação, outro trabalhador assume o item de trabalho. Se o trabalhador precisar de atualizar dados e emitir outras mensagens como parte da sua lógica, deve ser utilizado o padrão de processamento de mensagens idempotente .

Use a concorrência otimista sempre que possível. O controlo de simultaneidade pessimista utiliza bloqueios de base de dados para evitar conflitos. Isto pode causar um desempenho fraco e reduzir a disponibilidade. Com o controlo de simultaneidade otimista, cada transação modifica uma cópia ou um instantâneo dos dados. Quando a transação é consolidada, o motor de base de dados valida a transação e rejeita todas as transações que possam afetar a consistência da base de dados.

A Base de Dados SQL do Azure e o SQL Server suportam a simultaneidade otimista através do isolamento de instantâneos. Alguns serviços de armazenamento do Azure suportam a simultaneidade otimista através da utilização de Etags, incluindo o Azure Cosmos DB e o Armazenamento do Azure.

Considere utilizar o MapReduce ou outros algoritmos distribuídos paralelos. Dependendo dos dados e do tipo de trabalho a realizar, pode ser possível dividir o trabalho em tarefas independentes que podem ser realizadas por múltiplos nós a trabalhar em paralelo. Veja Estilo de arquitetura de macrocomputação.

Utilize a eleição de coordenador para a coordenação. Nos casos em que precise de coordenar as operações, certifique-se de que o coordenador não se torna no ponto único de falha da aplicação. Ao utilizar o padrão de Eleição de Líder, uma instância será o líder em qualquer momento e atuará como coordenador. Se o coordenador falhar, procede-se à eleição de uma nova instância para o papel de coordenador.

Assegure a coordenação entre clientes ligados ocasionalmente

A consistência eventual por si só não é suficiente quando clientes desconectados consomem um recurso partilhado com um limite fixo, como lugares para um evento. Vários clientes podem operar com base na mesma capacidade aparente, e sincronizar os seus registos mais tarde não consegue desfazer a sobrealocação desse recurso, como admitir mais pessoas do que o recinto do evento suporta. Aqui estão algumas recomendações de coordenação para usar neste cenário:

  • Particione a quota, não apenas os dados. Antes de um cliente desligar, atribui-lhe uma reserva exclusiva da capacidade disponível. O cliente só pode aprovar operações offline enquanto a sua reserva tiver capacidade. Garantir que a capacidade centralizada, a capacidade disponível centralmente e o montante total de todas as reservas pendentes não excedam o limite global. Quando o cliente se voltar a ligar, faça a reconciliação do que consumiu e devolva a capacidade não utilizada.

  • Decide quem absorve o problema. Se os clientes tiverem de operar offline sem reservas exclusivas, escolha uma de três opções de negócio: manter uma margem rígida de capacidade dimensionada para a procura offline agregada máxima, colocar em modo só de leitura as operações que alteram a capacidade enquanto estiverem desconectados, ou aceitar sobrealocação ocasional.

  • Falhas de reconciliação Surface para um operador. Coloque os conflitos que a sua carga de trabalho não consegue resolver automaticamente na fila de revisão. Use o padrão de Transação Compensatória para ações que o sistema pode reverter. Encaminhar resultados irreversíveis ou ambíguos para um operador que possa aplicar a política empresarial definida.