Quorum nos sistemas distribuídos. Até agora, nós vimos que a replicação é muito útil porque melhora a confiabilidade de um sistema distribuído quando esta possui tolerância à falha. Então, com tolerância à falha, a confiabilidade de um sistema replicado aumenta. de um sistema replicado aumenta. No momento em que uma réplica ficar indisponível, as demais réplicas restantes continuam com o processamento das solicitações. E vamos recapitular aqui, indisponibilidade pode ser uma falha no nó, um travamento, uma falha de hardware, pode ser o particionamento ou partição de rede, que é a incapacidade de comunicação entre os nós. E esta partição pode ser completa ou pode ser parcial, ou seja, uma lentidão, uma latência. E também, indisponibilidade pode ser uma manutenção planejada. Então, a aplicação de um patch no sistema operacional, de um patch do sistema gerenciador de banco de dados e por esta razão o nó precisou ser reiniciado. Com estes elementos em mente, nós vamos avançar um pouco mais na nossa compreensão de sistemas distribuídos para banco de dados. Vamos imaginar nesse cenário, onde nós temos o cliente 1, realizando ações contra a réplica A e a réplica B do nosso banco de dados. Então nós temos um carimbo de tempo, um timestamp, que é o tempo 1, um carimbo de tempo, um timestamp, que é o tempo 1, o comando de atualizar a chave X com o valor V1. E este comando chega com sucesso na réplica B, porém não alcança a réplica A por uma indisponibilidade, seja de partição de rede ou disponibilidade deste nó. Logo em seguida, o cliente dispara o comando de busca, buscar a chave X, qual é o valor de X, comando GET. E aqui nós temos um cenário aonde este comando chega com sucesso na réplica A, que não recebeu o comando de atualização anterior e não alcança a réplica B, que recebeu o comando de atualização com sucesso. Então, nós temos uma situação em que o cliente vai obter da réplica A o valor anterior à sua escrita no tempo 1. Então, ele recebe tempo anterior, que é tempo 0, e valor 0, tempo e valor anteriores ao último comando. Então, se nós tivéssemos nesse cenário a exigência de que escritas e leituras precisam acontecer em ambas as réplicas, nós não temos tolerância à falha. E se não tem tolerância à falha, a confiabilidade do sistema cai drasticamente em sistemas distribuídos, como nós vimos aqui no comentário inicial desta aula. Bom, vamos tentar resolver esta situação. Primeiro, vamos imaginar o situação real. Fizemos um post na nossa rede social favorita e logo em seguida vamos no nosso feed, atualizamos esse feed e não vemos o post recém postado. Então, essa é uma experiência confusa ou até mesmo frustrante para muitos sistemas. Então, nós vamos trabalhar com uma consistência que é a leitura após a escrita, ou ler somente aquilo que foi escrito. Em inglês, nós vamos encontrar isso como Read After Write Consistency. Essa consistência de ler após a escrita não necessariamente significa devolver para o cliente o original, o valor escrito por ele mesmo, pois estamos em sistemas distribuídos, com paralelismo e concorrência, então um cliente 2, outro cliente, pode ter que consistência ler após a escrita exige ler o último valor escrito pelo próprio cliente ou o valor mais recente que foi escrito por outro cliente concorrendo. E então, nós vamos conseguir resolver o problema da tolerância à falha com três réplicas. E todas as requisições de leitura e escrita são enviadas às três réplicas e nós vamos considerar a requisição com sucesso se tivermos duas ou mais respostas deste universo que nós agora temos com três réplicas. Então, nós avançamos o cenário, com duas réplicas não conseguimos resolver o problema de ter consistência e tolerância à falha, e com três réplicas é possível ter consistência de leitura pós-escrita e também ser tolerante à falha. Isso vai ser possível porque nós vamos agora começar a trabalhar com o conceito do quórum. porque nós vamos agora começar a trabalhar com o conceito do quórum. Mas vamos ver como ficou o nosso cenário. Agora temos três réplicas de banco de dados, A, B e C. O mesmo cliente 1. Então, o primeiro comando, no primeiro tempo, tempo 1, atualizar X com o valor 1, x com v1. Este comando alcança a réplica C, a réplica B, porém não alcança a réplica A. Logo em seguida, existe um comando de busca, um comando GET, recuperar o conteúdo, recuperar o valor de x, que tem que ser v1. Este comando não alcança C, alcança B e alcança A. Vamos relembrar que A não recebeu o comando de atualização para V1. Então, a réplica A devolve para o cliente tempo 0 e V0, que são os valores anteriores ao último comando de escrita que foi atualizado no tempo 1, x para v1. Porém, a réplica B responde tempo 1 e v1 para o cliente, porque ela recebeu o comando anterior de atualização. anterior de atualização. Então, o cliente tem uma escrita com sucesso na réplica B e C e tem uma leitura com sucesso da réplica A e B. E agora, este cliente vai ter que decidir entre T0, V0 e T1, V1. E essa escolha é baseada em tempo, no evento mais recente, na escrita mais recente. Então, nós vamos perceber que quórum de maioria é a solução comum em sistemas distribuídos. O conceito de quórum da nossa vida real, da nossa vida humana, é o número mínimo de membros cuja presença é imprescindível, ou seja, a presença obrigatória, para que a gente tenha validade às deliberações, às decisões e votos de um determinado órgão colegiado. Em sistemas distribuídos, e pensando especificamente aqui em banco de dados, nós vamos observar um quórum de leitura e um quórum de escrita. E estes quórums vão sustentar respectivamente as operações de escrita e de leitura. E aqui quero destacar neste desenho a réplica C, que é comum em ambos os quórums. que é comum em ambos os quórums. Então, de uma maneira formal, vamos entender o seguinte. Em um sistema que tenha N réplicas, se uma escrita é reconhecida por W réplicas, ou seja, este número representa o quórum de escrita, e subsequentemente é lida a partir de F réplicas, que é o quórum de leitura, e R mais W é maior do que N, ou seja, a minha quantidade de leitura, ou seja, o meu quórum de leitura mais o meu quórum de escrita é maior do que o número de réplicas, então, sob essas condições, a leitura vai receber o valor escrito pelo próprio cliente ou o valor mais recente naquela situação em que eu posso ter um cliente 2 concorrendo com a escrita. Ou seja, sob estas condições, nós temos que a leitura aconteceu sobre a última escrita daquela informação. E aí nós vamos entender maioria como N mais 1 sobre 2 e agora nós vamos começar a perceber números comuns, principalmente em universo de MongoDB, e também podem acontecer esses números em outros bancos de dados que trabalham com replicação, o número de nós, 3, 5 e 7. Especificamente para MongoDB, 3, 5 e 7, MongoDB Atlas, para ser mais específico ainda, são os números de nós elegíveis que nós vamos encontrar em MongoDB Atlas. Nós elegíveis, este elegível se refere à capacidade de se tornar o nó primário. Então, a maioria de 3 vai ser 2, de 5 vai ser 3 e de 7 vai ser 4. ser 4. Então, outra coisa que a gente pode também captar dessa regra, desse paradigma, é que as leituras vão tolerar N-R, réplicas indisponíveis, e as escritas toleram N-W, réplicas indisponíveis. Ou seja, se o nosso sistema distribuído de banco de dados tiver algumas réplicas indisponíveis, seja porque elas travaram ou porque estão incomunicáveis por uma incapacidade de rede, ainda assim existe progresso nas transações que os nossos clientes estão emitindo para este sistema. E aqui está o fundamento de porque a gente encontra replicações com números ímpares no número de membros, principalmente em MongoDB, e outros bancos que também aplicam esse paradigma de replicação. Muito bem, ainda assim pode acontecer que algumas atualizações Muito bem. Ainda assim, pode acontecer que algumas atualizações fiquem pendentes em algumas réplicas em algum momento. Então, o nosso quórum acontece, a transação emitida pelos clientes vai progredindo com sucesso. Nós vamos conseguindo resolver leituras e escritas com quórum de escrita e leitura, respectivamente. de escritas concoram de escrita e leitura, respectivamente, e com isso a consistência, que é a consistência de uma leitura acontecer após a última escrita do dado, acontece com sucesso. Porém, algumas réplicas podem ficar para trás em termos de consistência de estado em relação às demais réplicas. Então, neste cenário, é necessário ressincronizar as réplicas para que elas fiquem iguais novamente e uma das maneiras de resolver isso é aplicar um processo de antientropia. Neste processo de antientropia, nós vamos perceber que as réplicas se comunicam periodicamente entre si para se confirmar se estão consistentes ou não. Então, trazendo isso para um universo de bancos de dados distribuídos, o log de operações daquele banco é confrontado. As réplicas estão perguntando umas às outras. O meu log é igual ao seu? Eu estou na última unidade de tempo, ou no último termo do seu log de operações? Sim, então estou consistente. Não, não estou nesse último estado, então essa réplica precisa ser reconciliada, precisa ser atualizada e os registros com tempos mais cedo, ou seja, registros mais antigos, mais defasados, serão descartados. Então, neste exemplo, nós temos a réplica A que tem a chave X, o atributo X, com tempo 2 e conteúdo igual a false, aqui um booleano, e a réplica B está com tempo 1 igual a true. Então, a réplica B está atrasada, porque o tempo dela é tempo 1, e na réplica A nós já estamos com tempo 2. Este mecanismo, este processo de antientropia, vai verificar quem tem o maior tempo e o conteúdo do valor, o conteúdo do atributo X, cujo valor é falso e tem tempo 2, será propagado para a réplica B, que está defasada, que está atrasada. Então, este mecanismo, este processo de antientropia é algo que nós vamos ver na prática, operando com o MongoDB. que nós vamos ver na prática, operando com o MongoDB. Uma alternativa ao mecanismo de anti-entropia, que também visa ressincronizar cópias, é utilizar os clientes para apoiar no processo de propagação das atualizações dos dados. Então, isso nós já vimos quando discutimos BASE, que é o reparo de leitura. Vamos a um exemplo. O nosso cliente está tentando recuperar o conteúdo, o valor da chave X e manda este comando para a réplica A, para a réplica B e réplica C. A réplica C não consegue receber este comando, portanto não faz nenhum retorno. A réplica A retorna tempo 0 e valor 0. A réplica B retorna tempo 1 e V1. O cliente entende que tempo 1 e V1 é o estado atual dessa informação e, portanto, comanda para a réplica A o tempo 1 e o V1 e também para C, tempo 1 e V1. Este tipo de abordagem nós vamos ver na prática em sistemas baseados em Dynamo, por exemplo, DynamoDB ou Cassandra.