Por que você pode precisar de replicação semi-síncrona?

Olá a todos. Em contato com Vladislav Rodin. Atualmente, estou ministrando cursos sobre arquitetura de software e arquitetura de software de alta carga no portal OTUS. Antecipando o início de um novo fluxo do curso "Arquiteto de altas cargas", decidi escrever um pequeno material autoral, que quero compartilhar com você.




Introdução


Devido ao fato de que apenas cerca de 400-700 operações por segundo podem ser executadas no HDD (o que é incomparável com os rps típicos que caem em um sistema muito carregado), o banco de dados de disco clássico é uma parte estreita da arquitetura. Portanto, atenção especial deve ser dada aos padrões de dimensionamento deste repositório.

No momento, existem 2 padrões de escala de base: replicação e sharding. O sharding permite dimensionar a operação de gravação e, como resultado, reduzir os rps para gravação por um servidor no cluster. A replicação permite que você faça o mesmo, mas com operações de leitura. Este artigo é dedicado a esse mesmo padrão.

Replicação


Se você observar a replicação em um nível muito alto, isso é uma coisa simples: você tinha um servidor, os dados estavam nele e, em seguida, esse servidor parou de lidar com a carga de leitura desses dados. Você adiciona mais alguns servidores, sincroniza dados em todos os servidores e o usuário pode ler de qualquer servidor em seu cluster.

Apesar da aparente simplicidade, existem várias opções para classificar várias implementações desse esquema:

  • Por funções no cluster (mestre-mestre ou mestre-escravo)
  • Por objetos encaminhados (com base em linhas, com base em instruções ou mistas)
  • De acordo com o mecanismo de sincronização do nó

Hoje vamos lidar com o terceiro ponto.

Como a transação é confirmada


Este tópico não está diretamente relacionado à replicação; um artigo separado pode ser escrito sobre ele, no entanto, uma vez que, sem uma compreensão mais aprofundada do mecanismo de confirmação da transação, uma leitura mais aprofundada é inútil, deixe-me lembrá-lo das coisas mais básicas. Uma transação é confirmada em 3 etapas:

  1. Escrevendo uma transação no log do banco de dados.
  2. Aplicação de uma transação em um mecanismo de banco de dados.
  3. Devolva a confirmação ao cliente sobre a aplicação bem-sucedida da transação.

Nuances podem surgir em vários bancos de dados neste algoritmo: por exemplo, no mecanismo InnoDB do banco de dados MySQL, existem 2 logs: um para replicação (log binário) e outro para manter o ACID (desfazer / refazer log), enquanto no PostgreSQL há um log executando ambas as funções (log de gravação antecipada = WAL). Acima, porém, é precisamente o conceito geral que permite que essas nuances sejam ignoradas.

Replicação síncrona (sincronizada)


Vamos adicionar a lógica para replicar as alterações recebidas no algoritmo de confirmação da transação:

  1. Escrevendo uma transação no log do banco de dados.
  2. Aplicação de uma transação em um mecanismo de banco de dados.
  3. Enviando dados para todas as réplicas.
  4. Receba confirmação de todas as réplicas sobre a transação nelas.
  5. Devolva a confirmação ao cliente sobre a aplicação bem-sucedida da transação.

Com essa abordagem, temos várias desvantagens:

  • o cliente está aguardando que as alterações sejam aplicadas a todas as réplicas.
  • À medida que o número de nós no cluster aumenta, reduzimos a probabilidade de que a operação de gravação seja bem-sucedida.

Se tudo estiver mais ou menos claro no primeiro parágrafo, as razões do segundo parágrafo devem ser esclarecidas. Se durante a replicação síncrona não obtivermos resposta de pelo menos um nó, reverteremos a transação. Assim, aumentando o número de nós no cluster, você aumenta a probabilidade de falha na operação de gravação.

Podemos esperar pela confirmação apenas de uma certa fração de nós, por exemplo, de 51% (quorum)? Sim, podemos, mas na versão clássica, é necessária a confirmação de todos os nós, pois é assim que podemos garantir a consistência completa dos dados no cluster, o que é uma vantagem indiscutível desse tipo de replicação.

Replicação assíncrona


Vamos modificar o algoritmo anterior. Enviaremos os dados para as réplicas "algum tempo depois" e "algum tempo depois" as alterações serão aplicadas nas réplicas:

  1. Escrevendo uma transação no log do banco de dados.
  2. Aplicação de uma transação em um mecanismo de banco de dados.
  3. Devolva a confirmação ao cliente sobre a aplicação bem-sucedida da transação.
  4. Enviar dados para réplicas e aplicar alterações a elas.

Essa abordagem leva ao fato de que o cluster é rápido, porque não mantemos o cliente esperando que os dados cheguem às réplicas e nem sejam comunicados.

Mas a condição de descartar dados nas réplicas "algum tempo depois" pode levar à perda da transação e à perda da transação confirmada ao usuário, porque se os dados não tiverem tempo para replicar, uma confirmação foi enviada ao cliente sobre o sucesso da operação e o nó para o qual as alterações chegaram voaram HDD, estamos perdendo a transação, o que pode levar a consequências muito desagradáveis.

Replicação semisync


Finalmente, chegamos à replicação semi-síncrona. Esse tipo de replicação não é muito conhecido e não é muito comum, mas é de considerável interesse, pois pode combinar as vantagens da replicação síncrona e assíncrona.

Vamos tentar combinar as duas abordagens anteriores. Não manteremos o cliente por muito tempo, mas exigimos que os dados sejam replicados:

  1. Escrevendo uma transação no log do banco de dados.
  2. Aplicação de uma transação em um mecanismo de banco de dados.
  3. Enviando dados para réplicas.
  4. Recebendo confirmação da réplica sobre o recebimento de alterações (elas serão aplicadas "algum tempo depois").
  5. Devolva a confirmação ao cliente sobre a aplicação bem-sucedida da transação.

Observe que, com esse algoritmo, a perda de transação ocorre apenas no caso de uma queda do nó que aceita as alterações e os nós de réplica. A probabilidade de um mau funcionamento é considerada pequena e esses riscos são aceitos.

Mas com essa abordagem, o risco de leituras fantasmas é possível. Imagine o seguinte cenário: na etapa 4, não recebemos confirmação de nenhuma réplica. Devemos reverter esta transação e não retornar a confirmação ao cliente. Como os dados foram aplicados na etapa 2, existe um intervalo de tempo entre o final da etapa 2 e a reversão da transação, durante o qual as transações paralelas podem ver as alterações que não deveriam estar no banco de dados.

Replicação semisync de menos perda


Se você pensar um pouco, poderá alterar apenas as etapas do algoritmo em alguns locais para corrigir o problema das leituras fantasmas neste cenário:

  1. Escrevendo uma transação no log do banco de dados.
  2. Enviando dados de réplica.
  3. Recebendo confirmação da réplica sobre o recebimento de alterações (elas serão aplicadas "algum tempo depois").
  4. Aplicação de uma transação em um mecanismo de banco de dados.
  5. Devolva a confirmação ao cliente sobre a aplicação bem-sucedida da transação.

Agora, confirmamos as alterações apenas se elas forem replicadas.

Conclusão


Como sempre, não há soluções perfeitas, há um conjunto de soluções, cada uma com suas próprias vantagens e desvantagens e é adequada para resolver várias classes de problemas. Isso também se aplica à escolha de um mecanismo para sincronizar os dados de um banco de dados replicado. O conjunto de benefícios da replicação semi-síncrona é sólido e interessante o suficiente para ser considerado merecedor de atenção, apesar de sua baixa prevalência.

Isso é tudo. Vejo você no curso !

All Articles