HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

A próxima conferência HighLoad++ será realizada nos dias 6 e 7 de abril de 2020 em São Petersburgo.
Detalhes e ingressos по ссылке. HighLoad++ Sibéria 2019. Salão "Krasnoyarsk". 25 de junho, 12h. Teses e apresentação.

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

Acontece que os requisitos práticos entram em conflito com a teoria, onde aspectos importantes para um produto comercial não são levados em consideração. Esta palestra apresenta um processo de seleção e combinação de diferentes abordagens para a criação de componentes de consistência causal com base em pesquisas acadêmicas baseadas nos requisitos de um produto comercial. Os ouvintes aprenderão sobre as abordagens teóricas existentes para relógios lógicos, rastreamento de dependências, segurança do sistema, sincronização de relógio e por que o MongoDB optou por determinadas soluções.

Mikhail Tyulenev (doravante denominado MT): – Vou falar sobre consistência causal - esse é um recurso que trabalhamos no MongoDB. Trabalho em um grupo de sistemas distribuídos, fizemos isso há cerca de dois anos.

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

No processo, tive que me familiarizar com muitas pesquisas acadêmicas, pois esse recurso foi muito bem estudado. Descobriu-se que nem um único artigo se enquadra no que é exigido em um banco de dados de produção devido a requisitos muito específicos que provavelmente estão presentes em qualquer aplicação de produção.

Falarei sobre como nós, como consumidores de pesquisa acadêmica, preparamos algo a partir dela que podemos apresentar aos nossos usuários como um prato pronto, conveniente e seguro de usar.

Consistência causal. Vamos definir os conceitos

Para começar, quero dizer em termos gerais o que é consistência causal. Existem dois personagens - Leonard e Penny (série de TV "The Big Bang Theory"):

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

Digamos que Penny esteja na Europa e Leonard queira dar uma festa surpresa para ela. E ele não consegue pensar em nada melhor do que tirá-la de sua lista de amigos, enviando a todos os seus amigos uma atualização no feed: “Vamos fazer Penny feliz!” (ela está na Europa, enquanto dorme, ela não vê tudo isso e não pode ver, porque não está lá). No final das contas, ela exclui esta postagem, apaga do Feed e restaura o acesso para que ela não perceba nada e não haja escândalo.
Tudo isso é muito bom, mas vamos supor que o sistema esteja distribuído e as coisas deram um pouco errado. Pode acontecer, por exemplo, que a restrição de acesso de Penny tenha ocorrido após o aparecimento desta postagem, se os eventos não estiverem relacionados por causa e efeito. Na verdade, este é um exemplo de quando a consistência causal é necessária para desempenhar uma função de negócio (neste caso).

Na verdade, essas são propriedades nada triviais do banco de dados - muito poucas pessoas as apoiam. Vamos passar para os modelos.

Modelos de consistência

O que exatamente é um modelo de consistência em bancos de dados? Estas são algumas das garantias que um sistema distribuído dá sobre quais dados o cliente pode receber e em que sequência.

Em princípio, todos os modelos de consistência se resumem à semelhança entre um sistema distribuído e um sistema executado, por exemplo, em um nó de um laptop. E é assim que um sistema que roda em milhares de “nós” distribuídos geograficamente é semelhante a um laptop, no qual todas essas propriedades são executadas automaticamente, em princípio.

Portanto, os modelos de consistência são aplicados apenas a sistemas distribuídos. Todos os sistemas que existiam anteriormente e operavam na mesma escala vertical não apresentavam tais problemas. Havia um Buffer Cache e tudo sempre era lido dele.

Modelo Forte

Na verdade, o primeiro modelo é o Strong (ou a linha de capacidade de ascensão, como é frequentemente chamada). Este é um modelo de consistência que garante que cada alteração, uma vez confirmada a sua ocorrência, seja visível para todos os usuários do sistema.

Isso cria uma ordem global de todos os eventos no banco de dados. Esta é uma propriedade de consistência muito forte e geralmente muito cara. No entanto, é muito bem suportado. É muito caro e lento - raramente é usado. Isso é chamado de capacidade de ascensão.

Há outra propriedade mais forte que é suportada no Spanner - chamada Consistência Externa. Falaremos sobre isso um pouco mais tarde.

Causal

O próximo é Causal, que é exatamente disso que eu estava falando. Existem vários outros subníveis entre Forte e Causal dos quais não vou falar, mas todos se resumem em Causal. Este é um modelo importante porque é o mais forte de todos os modelos, a consistência mais forte na presença de uma rede ou partições.

Causais são, na verdade, uma situação em que os eventos estão conectados por uma relação de causa e efeito. Muitas vezes eles são percebidos como uma leitura dos seus direitos do ponto de vista do cliente. Se o cliente observou alguns valores, ele não consegue ver valores que existiam no passado. Ele já está começando a ver leituras de prefixo. Tudo se resume à mesma coisa.
Causais como modelo de consistência são uma ordenação parcial de eventos no servidor, na qual os eventos de todos os clientes são observados na mesma sequência. Neste caso, Leonard e Penny.

Eventual

O terceiro modelo é a Consistência Eventual. Isto é o que absolutamente todos os sistemas distribuídos suportam, o modelo mínimo que faz sentido. Significa o seguinte: quando temos algumas alterações nos dados, em algum momento elas se tornam consistentes.

Nesse momento ela não fala nada, senão se transformaria em Consistência Externa - seria uma história completamente diferente. No entanto, este é um modelo muito popular, o mais comum. Por padrão, todos os usuários de sistemas distribuídos usam Consistência Eventual.

Quero dar alguns exemplos comparativos:

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

O que essas setas significam?

  • Latência. À medida que a força da consistência aumenta, ela se torna maior por motivos óbvios: é preciso fazer mais registros, obter confirmação de todos os hosts e nós que participam do cluster de que os dados já estão lá. Dessa forma, Consistência Eventual tem a resposta mais rápida, pois lá, via de regra, você pode até memorizá-la e isso será, em princípio, suficiente.
  • Disponibilidade. Se entendermos isso como a capacidade do sistema de responder na presença de quebras de rede, partições ou algum tipo de falha, a tolerância a falhas aumenta à medida que o modelo de consistência diminui, pois nos basta que um host viva e ao mesmo tempo o tempo produz alguns dados. A Consistência Eventual não garante nada sobre os dados - pode ser qualquer coisa.
  • Anomalias. Ao mesmo tempo, é claro, o número de anomalias aumenta. Na Consistência Forte eles quase nem deveriam existir, mas na Consistência Eventual eles podem ser qualquer coisa. Surge a pergunta: por que as pessoas escolhem a Consistência Eventual se ela contém anomalias? A resposta é que os modelos de Consistência Eventual são aplicáveis ​​e existem anomalias, por exemplo, num curto período de tempo; é possível usar o assistente para ler e ler mais ou menos dados consistentes; Muitas vezes é possível usar modelos de consistência forte. Na prática isto funciona e muitas vezes o número de anomalias é limitado no tempo.

Teorema CAP

Quando você vê as palavras consistência, disponibilidade – o que vem à sua mente? Isso mesmo - teorema CAP! Agora quero dissipar o mito... Não sou eu - é Martin Kleppmann, que escreveu um artigo maravilhoso, um livro maravilhoso.

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

O teorema CAP é um princípio formulado na década de 2000 de que Consistência, Disponibilidade, Partições: escolha duas e você não pode escolher três. Era um certo princípio. Foi provado como um teorema alguns anos depois por Gilbert e Lynch. Aí isso passou a ser usado como mantra - os sistemas passaram a ser divididos em CA, CP, AP e assim por diante.

Este teorema foi realmente comprovado para os seguintes casos... Em primeiro lugar, a Disponibilidade não foi considerada como um valor contínuo de zero a centenas (0 - o sistema está “morto”, 100 - responde rapidamente; estamos habituados a considerá-lo assim) , mas como uma propriedade do algoritmo, que garante que para todas as suas execuções ele retorne dados.

Não há uma palavra sobre o tempo de resposta! Existe um algoritmo que retorna dados após 100 anos - um algoritmo disponível absolutamente maravilhoso, que faz parte do teorema CAP.
Segundo: o teorema foi comprovado para alterações nos valores da mesma chave, apesar de essas alterações serem redimensionáveis. Isso significa que na realidade eles praticamente não são utilizados, pois os modelos são diferentes: Consistência Eventual, Consistência Forte (talvez).

Para que serve tudo isso? Além disso, o teorema CAP exatamente na forma em que foi provado praticamente não é aplicável e raramente é utilizado. Na forma teórica, de alguma forma limita tudo. Acontece um certo princípio que é intuitivamente correto, mas em geral não foi comprovado.

A consistência causal é o modelo mais forte

O que está acontecendo agora é que você pode obter todas as três coisas: Consistência e Disponibilidade usando Partições. Em particular, a consistência causal é o modelo de consistência mais forte, que ainda funciona na presença de partições (quebras na rede). É por isso que suscita tanto interesse e é por isso que o abordámos.

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

Em primeiro lugar, simplifica o trabalho dos desenvolvedores de aplicativos. Em particular, a presença de um grande suporte do servidor: quando todos os registros que ocorrem dentro de um cliente têm a garantia de chegar na mesma sequência em outro cliente. Em segundo lugar, suporta partições.

Cozinha interna do MongoDB

Lembrando que é almoço, vamos para a cozinha. Vou falar sobre o modelo do sistema, ou seja, o que é o MongoDB para quem está ouvindo falar desse banco de dados pela primeira vez.

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

MongoDB (doravante denominado “MongoDB”) é um sistema distribuído que suporta escalonamento horizontal, ou seja, fragmentação; e dentro de cada fragmento também suporta redundância de dados, ou seja, replicação.

O sharding no MongoDB (não um banco de dados relacional) realiza balanceamento automático, ou seja, cada coleção de documentos (ou “tabela” em termos de dados relacionais) é dividida em pedaços, e o servidor os move automaticamente entre os shards.

O Query Router, que distribui solicitações, para um cliente é algum cliente através do qual ele funciona. Ele já sabe onde e quais dados estão localizados e direciona todas as solicitações para o fragmento correto.

Outro ponto importante: o MongoDB é um mestre único. Existe um Primário - ele pode receber registros que suportam as chaves que contém. Você não pode fazer gravação multimestre.

Fizemos a versão 4.2 - novas coisas interessantes apareceram lá. Em particular, eles inseriram o Lucene - search - ou seja, o java executável diretamente no Mongo, e aí tornou-se possível realizar pesquisas através do Lucene, o mesmo que no Elastica.

E fizeram um novo produto - Charts, também está disponível no Atlas (nuvem própria do Mongo). Eles têm um nível gratuito – você pode brincar com ele. Gostei muito do Charts - visualização de dados, muito intuitivo.

Ingredientes Consistência causal

Contei cerca de 230 artigos publicados sobre este tema - de Leslie Lampert. Agora, de memória, transmitirei a vocês algumas partes desses materiais.

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

Tudo começou com um artigo de Leslie Lampert, escrito na década de 1970. Como você pode ver, algumas pesquisas sobre esse assunto ainda estão em andamento. Agora a consistência causal está despertando interesse em conexão com o desenvolvimento de sistemas distribuídos.

Restrições

Que restrições existem? Esse é aliás um dos pontos principais, pois as restrições que um sistema de produção impõe são muito diferentes das restrições que existem nos artigos acadêmicos. Muitas vezes são bastante artificiais.

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

  • Primeiramente, “MongoDB” é um mestre único, como já disse (isso simplifica bastante).
  • Acreditamos que o sistema deverá suportar cerca de 10 mil shards. Não podemos tomar nenhuma decisão arquitetônica que limite explicitamente esse valor.
  • Temos uma nuvem, mas presumimos que uma pessoa ainda deve ter a oportunidade de baixar o binário, iniciá-lo em seu laptop e tudo funcionar perfeitamente.
  • Assumimos algo que a Research raramente assume: os clientes externos podem fazer o que quiserem. MongoDB é de código aberto. Conseqüentemente, os clientes podem ser tão espertos e irritados que podem querer quebrar tudo. Especulamos que os Feilors Bizantinos possam ter se originado.
  • Para clientes externos que estão fora do perímetro, há uma limitação importante: se esse recurso estiver desabilitado, nenhuma degradação de desempenho deverá ser observada.
  • Outro ponto geralmente antiacadêmico: a compatibilidade das versões anteriores e futuras. Os drivers antigos devem oferecer suporte a novas atualizações e o banco de dados deve oferecer suporte a drivers antigos.

Em geral, tudo isso impõe restrições.

Componentes de consistência causal

Agora falarei sobre alguns dos componentes. Se considerarmos a consistência causal em geral, podemos selecionar blocos. Escolhemos entre trabalhos que pertencem a um determinado bloco: Rastreamento de Dependências, escolha de relógios, como esses relógios podem ser sincronizados entre si e como garantimos a segurança - este é um esboço do que irei falar:

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

Acompanhamento completo de dependências

Por que é necessário? Assim, quando os dados são replicados, cada registro, cada alteração de dados contém informações sobre quais alterações dependem. A primeira e ingênua mudança ocorre quando cada mensagem que contém um registro contém informações sobre mensagens anteriores:

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

Neste exemplo, o número entre colchetes são os números dos registros. Às vezes esses registros com valores são até transferidos na íntegra, às vezes algumas versões são transferidas. O resultado final é que cada alteração contém informações sobre a anterior (obviamente carrega tudo isso dentro de si).

Por que decidimos não usar esta abordagem (rastreamento completo)? Obviamente, porque esta abordagem é impraticável: qualquer alteração numa rede social depende de todas as alterações anteriores nessa rede social, transferindo, digamos, Facebook ou VKontakte em cada atualização. No entanto, há muita pesquisa sobre Full Dependency Tracking – estas são redes pré-sociais; para algumas situações realmente funciona.

Acompanhamento de dependência explícita

O próximo é mais limitado. A transferência de informação também é considerada aqui, mas apenas aquela que é claramente dependente. O que depende do que, em regra, é determinado pela Aplicação. Quando os dados são replicados, a consulta só retorna respostas quando as dependências anteriores foram satisfeitas, ou seja, mostradas. Esta é a essência de como funciona a consistência causal.

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

Ela vê que o registro 5 depende dos registros 1, 2, 3, 4 - portanto, ela espera até que o cliente tenha acesso às alterações feitas pela decisão de acesso de Penny, quando todas as alterações anteriores já passaram pelo banco de dados.

Isso também não nos convém, porque ainda há muita informação e isso vai atrasar as coisas. Existe outra abordagem...

Relógio Lamport

Eles são muito antigos. Lamport Clock significa que essas dependências são dobradas em uma função escalar, chamada Lamport Clock.

Uma função escalar é algum número abstrato. Muitas vezes é chamado de tempo lógico. A cada evento, esse contador aumenta. O contador, atualmente conhecido pelo processo, envia cada mensagem. É claro que os processos podem estar fora de sincronia, podem ter tempos completamente diferentes. No entanto, o sistema de alguma forma equilibra o relógio com essas mensagens. O que acontece nesse caso?

Dividi esse grande fragmento em dois para deixar claro: amigos podem residir em um nó, que contém uma parte da coleção, e Feed pode residir em outro nó, que contém uma parte desta coleção. Está claro como eles podem sair da linha? O primeiro Feed dirá: “Replicado” e depois Amigos. Se o sistema não fornecer algum tipo de garantia de que o Feed não será mostrado até que as dependências Friends da coleção Friends também sejam entregues, então teremos exatamente a situação que mencionei.

Você vê como o tempo do contador no Feed aumenta logicamente:

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

Portanto, a propriedade principal deste Relógio de Lamport e consistência causal (explicada através do Relógio de Lamport) é esta: se tivermos os Eventos A e B, e o Evento B depender do Evento A*, segue-se que o Tempo Lógico do Evento A é menor que LogicalTime do Evento B.

* Às vezes também dizem que A aconteceu antes de B, ou seja, A aconteceu antes de B - esta é uma certa relação que ordena parcialmente todo o conjunto de eventos que aconteceram em geral.

O oposto está incorreto. Na verdade, esta é uma das principais desvantagens do Relógio Lamport - pedido parcial. Existe um conceito sobre eventos simultâneos, ou seja, eventos em que nem (A aconteceu antes de B) nem (A aconteceu antes de B). Um exemplo seria a adição paralela de Leonard de outra pessoa como amigo (nem mesmo Leonard, mas Sheldon, por exemplo).
Esta é a propriedade frequentemente usada ao trabalhar com relógios Lamport: eles olham especificamente para a função e a partir disso concluem que talvez esses eventos sejam dependentes. Porque uma maneira é verdadeira: se LogicalTime A for menor que LogicalTime B, então B não pode acontecer antes de A; e se mais, então talvez.

Relógio vetorial

O desenvolvimento lógico do relógio Lamport é o Vector Clock. Eles diferem porque cada nó aqui contém seu próprio relógio separado e são transmitidos como um vetor.
Nesse caso, você vê que o índice zero do vetor é responsável pelo Feed, e o primeiro índice do vetor é pelos Amigos (cada um desses nós). E agora eles vão aumentar: o índice zero de “Feed” aumenta ao escrever – 1, 2, 3:

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

Por que o Vector Clock é melhor? Porque eles permitem descobrir quais eventos são simultâneos e quando ocorrem em nós diferentes. Isto é muito importante para um sistema de fragmentação como o MongoDB. No entanto, não escolhemos isto, embora seja uma coisa maravilhosa, e funcione muito bem, e provavelmente nos serviria...

Se tivermos 10 mil fragmentos, não poderemos transferir 10 mil componentes, mesmo que os comprimamos ou criemos outra coisa - a carga útil ainda será várias vezes menor que o volume de todo esse vetor. Portanto, cerrando os corações e os dentes, abandonamos esta abordagem e passamos para outra.

Chave inglesa TrueTime. Relógio atômico

Eu disse que haveria uma história sobre Spanner. Isso é uma coisa legal, vinda diretamente do século XNUMX: relógios atômicos, sincronização GPS.

Qual é a ideia? “Spanner” é um sistema do Google que recentemente ficou disponível para as pessoas (eles adicionaram SQL a ele). Cada transação tem algum carimbo de data/hora. Como o tempo é sincronizado*, cada evento pode receber um horário específico - os relógios atômicos têm um tempo de espera, após o qual é garantido que um horário diferente “aconteça”.

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

Assim, simplesmente escrevendo no banco de dados e aguardando algum tempo, a serialização do evento é automaticamente garantida. Eles têm o modelo de Consistência mais forte que pode ser imaginado em princípio - é a Consistência Externa.

* Este é o principal problema dos relógios Lampart - eles nunca são síncronos em sistemas distribuídos. Eles podem divergir; mesmo com o NTP, ainda não funcionam muito bem. "Spanner" tem um relógio atômico e a sincronização, ao que parece, é de microssegundos.

Por que não escolhemos? Não presumimos que nossos usuários tenham um relógio atômico integrado. Quando eles aparecerem, sendo embutidos em cada laptop, haverá algum tipo de sincronização GPS super legal - então sim... Mas por enquanto o melhor que é possível é Amazon, Estações Base - para fanáticos... Então usamos outros relógios .

Relógio Híbrido

Na verdade, isso é o que funciona no MongoDB ao garantir a consistência causal. Como eles são híbridos? Híbrido é um valor escalar, mas possui dois componentes:

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

  • A primeira é a época Unix (quantos segundos se passaram desde o “início do mundo da informática”).
  • O segundo é algum incremento, também um unsigned int de 32 bits.

Isso é tudo, na verdade. Existe esta abordagem: a parte responsável pelo tempo está sincronizada com o relógio o tempo todo; cada vez que ocorre uma atualização, esta parte é sincronizada com o relógio e verifica-se que a hora está sempre mais ou menos correta, e o incremento permite distinguir entre eventos que ocorreram no mesmo momento.

Por que isso é importante para o MongoDB? Porque permite fazer algum tipo de backup de restaurantes em um determinado momento, ou seja, o evento é indexado por tempo. Isto é importante quando determinados eventos são necessários; Para um banco de dados, eventos são alterações no banco de dados que ocorreram em determinados intervalos de tempo.

Vou lhe contar o motivo mais importante só para você (por favor, não conte a ninguém)! Fizemos isso porque é assim que os dados organizados e indexados se parecem no MongoDB OpLog. OpLog é uma estrutura de dados que contém absolutamente todas as alterações no banco de dados: elas primeiro vão para o OpLog e depois são aplicadas ao próprio Storage no caso de ser uma data ou fragmento replicado.

Este foi o principal motivo. Ainda assim, também existem requisitos práticos para o desenvolvimento de um banco de dados, o que significa que ele deve ser simples – pouco código, o mínimo possível de coisas quebradas que precisam ser reescritas e testadas. O fato de nossos oplogs serem indexados por relógios híbridos ajudou muito e nos permitiu fazer a escolha certa. Realmente valeu a pena e de alguma forma funcionou magicamente no primeiro protótipo. Foi muito legal!

Sincronização de relógio

Existem vários métodos de sincronização descritos na literatura científica. Estou falando de sincronização quando temos dois fragmentos diferentes. Se houver um conjunto de réplicas, não há necessidade de sincronização: este é um “mestre único”; temos um OpLog, no qual caem todas as alterações - neste caso, tudo já está ordenado sequencialmente no próprio “Oplog”. Mas se tivermos dois fragmentos diferentes, a sincronização de tempo é importante aqui. Foi aqui que o relógio vetorial ajudou mais! Mas não os temos.

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

O segundo é adequado - este é “Heartbeats”. É possível trocar alguns sinais que ocorrem a cada unidade de tempo. Mas os Heartbeats são muito lentos, não podemos fornecer latência ao nosso cliente.

O tempo verdadeiro é, obviamente, uma coisa maravilhosa. Mas, novamente, este é provavelmente o futuro... Embora já possa ser feito no Atlas, já existem sincronizadores de tempo rápidos da “Amazon”. Mas não estará disponível para todos.

Fofoca é quando todas as mensagens incluem tempo. Isso é aproximadamente o que usamos. Cada mensagem entre nós, um driver, um roteador de nó de dados, absolutamente tudo para o MongoDB é algum tipo de elemento, um componente de banco de dados que contém um relógio que funciona. Eles têm o significado do tempo híbrido em todos os lugares, ele é transmitido. 64 bits? Isso permite, isso é possível.

Como tudo funciona junto?

Aqui estou olhando para um conjunto de réplicas para tornar tudo um pouco mais fácil. Existem Primário e Secundário. O secundário faz a replicação e nem sempre está completamente sincronizado com o primário.

Ocorre uma inserção no “Primery” com um determinado valor de tempo. Esta inserção aumenta a contagem interna em 11, se este for o máximo. Ou verificará os valores do relógio e sincronizará com o relógio se os valores do relógio forem maiores. Isso permite que você organize por tempo.

Depois que ele faz a gravação, ocorre um momento importante. O clock está no "MongoDB" e é incrementado apenas no caso de gravação em "Oplog". Este é o evento que altera o estado do sistema. Em absolutamente todos os artigos clássicos, um evento é considerado quando uma mensagem atinge um nó: a mensagem chegou, o que significa que o sistema mudou de estado.

Isso se deve ao fato de que durante a pesquisa não fica totalmente claro como essa mensagem será interpretada. Sabemos com certeza que se não estiver refletido no “Oplog”, então não será interpretado de forma alguma, e uma mudança no estado do sistema é apenas uma entrada no “Oplog”. Isso simplifica tudo para nós: o modelo simplifica e nos permite organizá-lo dentro de um conjunto de réplicas e muitas outras coisas úteis.

O valor que já está escrito no “Oplog” é retornado - sabemos que o “Oplog” já contém esse valor, e seu tempo é 12. Agora, digamos, a leitura começa em outro nó (Secundário), e ele transmite o próprio afterClusterTime mensagem. Ele diz: “Preciso de tudo o que aconteceu pelo menos depois das 12 ou durante as doze” (ver foto acima).

Isso é chamado de Causal a Consistente (CAT). Existe tal conceito na teoria de que se trata de uma fatia de tempo, que é consistente em si mesma. Neste caso, podemos dizer que este é o estado do sistema que foi observado no tempo 12.

Agora não há nada aqui ainda, porque isso simula a situação em que você precisa do Secundário para replicar dados do Primário. Ele espera... E agora que os dados chegaram - ele retorna esses valores.

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

É basicamente assim que tudo funciona. Quase.

O que significa "quase"? Vamos supor que existe alguma pessoa que leu e entendeu como tudo isso funciona. Percebi que toda vez que ClusterTime ocorre, ele atualiza o relógio lógico interno e então a próxima entrada aumenta em um. Esta função ocupa 20 linhas. Digamos que esta pessoa transmita o maior número de 64 bits, menos um.

Por que "menos um"? Como o relógio interno será substituído neste valor (obviamente, este é o maior possível e maior que o horário atual), então ocorrerá uma entrada em “Oplog”, e o relógio será incrementado em outra unidade - e já haverá seja um valor máximo (simplesmente existem todas as unidades, não há outro lugar para ir), ints não-sagrados).

É claro que depois disso o sistema fica absolutamente inacessível para qualquer coisa. Só pode ser descarregado e limpo - muito trabalho manual. Disponibilidade total:

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

Além disso, se isso for replicado em outro lugar, todo o cluster simplesmente falhará. Uma situação absolutamente inaceitável que qualquer pessoa pode organizar com muita rapidez e facilidade! Portanto, consideramos este momento como um dos mais importantes. Como prevenir isso?

Nosso jeito é assinar clusterTime

É assim que é transmitido na mensagem (antes do texto azul). Mas também começamos a gerar uma assinatura (texto azul):

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

A assinatura é gerada por uma chave que fica armazenada dentro do banco de dados, dentro de um perímetro seguro; em si é gerado e atualizado (os usuários não veem nada sobre isso). Um hash é gerado e cada mensagem é assinada quando criada e validada quando recebida.
A pergunta provavelmente surge na mente das pessoas: “Quanto isso retarda as coisas?” Eu disse que deveria funcionar rápido, principalmente na ausência desse recurso.

O que significa usar consistência causal neste caso? Isso é para mostrar o parâmetro afterClusterTime. Sem isso, simplesmente passará valores de qualquer maneira. Fofocar, a partir da versão 3.6, sempre funciona.

Se deixarmos a geração constante de assinaturas, o sistema ficará lento mesmo na ausência de um recurso, o que não atende às nossas abordagens e requisitos. Então, o que nós fizemos?

Faça isso rapidamente!

É uma coisa bastante simples, mas o truque é interessante – vou compartilhar, talvez alguém se interesse.
Temos um hash que armazena os dados assinados. Todos os dados passam pelo cache. O cache não assina o horário específico, mas sim o Range. Quando chega algum valor, geramos um Range, mascaramos os últimos 16 bits e assinamos este valor:

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

Ao receber tal assinatura, agilizamos o sistema (relativamente) 65 mil vezes. Funciona muito bem: quando realizamos experimentos, o tempo diminuiu 10 mil vezes quando fizemos uma atualização sequencial. É claro que quando estão em desacordo, isso não funciona. Mas na maioria dos casos práticos funciona. A combinação da assinatura Range com a assinatura resolveu o problema de segurança.

O que aprendemos?

Lições que aprendemos com isso:

  • Precisamos ler materiais, histórias, artigos, porque temos muita coisa interessante. Quando trabalhamos em algum recurso (especialmente agora, quando fizemos transações, etc.), precisamos ler e entender. Leva tempo, mas na verdade é muito útil porque deixa claro onde estamos. Parece que não descobrimos nada de novo – apenas pegamos os ingredientes.

    Em geral, há uma certa diferença de pensamento quando há uma conferência acadêmica (Sigmon, por exemplo) – todos focam em novas ideias. O que há de novo em nosso algoritmo? Não há nada particularmente novo aqui. A novidade reside antes na forma como combinamos as abordagens existentes. Portanto, a primeira coisa é ler os clássicos, começando por Lampart.

  • Na produção, os requisitos são completamente diferentes. Tenho certeza de que muitos de vocês não se deparam com bancos de dados “esféricos” em um vácuo abstrato, mas com coisas normais e reais que apresentam problemas de disponibilidade, latência e tolerância a falhas.
  • A última coisa é que tivemos que considerar ideias diferentes e combinar vários artigos completamente diferentes em uma abordagem, juntos. A ideia de assinar, por exemplo, geralmente veio de um artigo que considerava o protocolo Paxos, que para Failors não-Bizantinos está dentro do protocolo de autorização, para os Bizantinos - fora do protocolo de autorização... Em geral, é exatamente isso que nós acabou fazendo.

    Não há absolutamente nada de novo aqui! Mas assim que misturamos tudo... É o mesmo que dizer que a receita da salada Olivier é uma bobagem, porque os ovos, a maionese e o pepino já foram inventados... É quase a mesma história.

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

Vou terminar com isso. Obrigado!

perguntas

Pergunta do público (doravante denominada B): – Obrigado, Mikhail, pelo relatório! O tópico sobre o tempo é interessante. Você está usando fofoca. Disseram que cada um tem seu horário, cada um sabe o horário local. Pelo que entendi, temos um driver - pode haver muitos clientes com drivers, planejadores de consultas também, fragmentos também... E o que acontece com o sistema se de repente tivermos uma discrepância: alguém decide que é para um um minuto à frente, alguém um minuto atrás? Onde iremos parar?

MT: – Ótima pergunta, de fato! Eu só queria falar sobre fragmentos. Se bem entendi a pergunta, temos a seguinte situação: existe o shard 1 e o shard 2, a leitura ocorre a partir desses dois shards - eles têm uma discrepância, não interagem entre si, porque o tempo que eles conhecem é diferente, especialmente o tempo que eles existem em oplogs.
Digamos que o fragmento 1 fez um milhão de registros, o fragmento 2 não fez nada e a solicitação chegou a dois fragmentos. E o primeiro tem um afterClusterTime de mais de um milhão. Em tal situação, como expliquei, o fragmento 2 nunca responderá.

AT: – Queria saber como eles sincronizam e escolhem um tempo lógico?

MT: - Muito fácil de sincronizar. Shard, quando afterClusterTime chega até ele e ele não encontra tempo no “Oplog”, inicia não aprovado. Ou seja, ele aumenta seu tempo com as mãos para esse valor. Isso significa que não há eventos que correspondam a esta solicitação. Ele cria este evento artificialmente e assim se torna Causal Consistente.

AT: – E se depois disso vierem até ele alguns outros eventos que foram perdidos em algum lugar da rede?

MT: – O Shard é projetado de forma que eles não voltem, já que é um único master. Se ele já se inscreveu, eles não virão, mas virão mais tarde. Não pode acontecer que algo fique preso em algum lugar, então ele não escreve, e então esses eventos chegam - e a consistência Causal é quebrada. Quando ele não escreve, todos devem vir em seguida (ele irá esperar por eles).

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

AT: – Tenho várias dúvidas em relação às filas. A consistência causal pressupõe que existe uma fila específica de ações que precisam ser executadas. O que acontece se um dos nossos pacotes desaparecer? Aí vem o dia 10, 11... o 12 desapareceu, e todos os outros estão esperando que isso se torne realidade. E de repente nosso carro morreu, não podemos fazer nada. Existe um comprimento máximo da fila que se acumula antes de ser executada? Que falha fatal ocorre quando qualquer estado é perdido? Além disso, se escrevermos que existe algum estado anterior, então deveríamos de alguma forma começar a partir dele? Mas eles não o afastaram!

MT: – Também é uma ótima pergunta! O que estamos fazendo? MongoDB tem o conceito de escrita de quorum, leitura de quorum. Em que casos uma mensagem pode ser perdida? Quando uma gravação não é quorum ou quando uma leitura não é quorum (algum tipo de lixo também pode ficar preso).
Em relação à consistência causal, foi realizado um grande teste experimental, cujo resultado foi que no caso em que as escritas e leituras não são quórum, ocorrem violações da consistência causal. Exatamente o que você diz!

Nosso conselho: use pelo menos leitura de quorum ao usar consistência causal. Neste caso, nada será perdido, mesmo que o registro de quorum seja perdido... Esta é uma situação ortogonal: se o usuário não deseja que os dados sejam perdidos, ele precisa utilizar um registro de quorum. A consistência causal não garante durabilidade. A durabilidade é garantida pela replicação e pelo maquinário associado à replicação.

AT: – Quando criamos uma instância que realiza sharding para nós (não master, mas slave, respectivamente), ela depende do horário Unix da sua própria máquina ou do horário do “master”; Ele sincroniza pela primeira vez ou periodicamente?

MT: – Vou esclarecer agora. Shard (ou seja, partição horizontal) – sempre há um Primário lá. E um fragmento pode ter um “mestre” e pode haver réplicas. Mas o shard sempre suporta gravação, pois deve suportar algum domínio (o shard tem Primário).

AT: – Então tudo depende puramente do “mestre”? O tempo mestre é sempre usado?

MT: - Sim. Você pode dizer figurativamente: o relógio está correndo quando ocorre uma entrada no “master”, no “Oplog”.

AT: – Temos um cliente que se conecta e não precisa saber nada de horário?

MT: – Você não precisa saber de nada! Se falarmos sobre como funciona no cliente: quando o cliente quiser usar consistência causal, ele precisa abrir uma sessão. Agora está tudo aí: transações na sessão e recuperação de direitos... Uma sessão é a ordenação de eventos lógicos que ocorrem com o cliente.

Se ele abrir esta sessão e disser que deseja consistência causal (se a sessão suportar consistência causal por padrão), tudo funcionará automaticamente. O motorista lembra desse tempo e aumenta quando recebe uma nova mensagem. Ele lembra qual resposta o anterior retornou do servidor que retornou os dados. A próxima solicitação conterá afterCluster("tempo maior que este").

O cliente não precisa saber absolutamente nada! Isso é completamente opaco para ele. Se as pessoas usarem esses recursos, o que poderão fazer? Primeiro, você pode ler secundários com segurança: você pode gravar no Primário e ler secundários replicados geograficamente e ter certeza de que funciona. Ao mesmo tempo, as sessões que foram gravadas na Primária podem até ser transferidas para a Secundária, ou seja, você pode usar não uma sessão, mas várias.

AT: – Uma nova camada da ciência da computação – tipos de dados CRDT (Tipos de dados replicados livres de conflitos) – está fortemente relacionada ao tópico de consistência eventual. Você já pensou em integrar esses tipos de dados ao banco de dados e o que pode dizer sobre isso?

MT: - Boa pergunta! CRDT faz sentido para conflitos de gravação: no MongoDB, mestre único.

AT: – Tenho uma pergunta do devops. No mundo real, existem situações jesuíticas em que ocorre o fracasso bizantino e pessoas más dentro do perímetro protegido começam a intrometer-se no protocolo, enviando pacotes artesanais de uma forma especial?

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

MT: – Pessoas más dentro do perímetro são como um cavalo de Tróia! Pessoas más dentro do perímetro podem fazer muitas coisas ruins.

AT: – É claro que deixar, grosso modo, um buraco no servidor através do qual você pode colocar um zoológico de elefantes e colapsar todo o cluster para sempre... Levará algum tempo para recuperação manual... Isso, para dizer o mínimo, é errado. Por outro lado, isso é interessante: na vida real, na prática, existem situações em que ocorrem ataques internos naturalmente semelhantes?

MT: – Como raramente encontro falhas de segurança na vida real, não posso dizer se elas acontecem. Mas se falamos da filosofia de desenvolvimento, pensamos assim: temos um perímetro que dá segurança aos caras que fazem segurança - isso é um castelo, um muro; e dentro do perímetro você pode fazer o que quiser. É claro que existem usuários com capacidade apenas de visualizar e existem usuários com capacidade de apagar o diretório.

Dependendo dos direitos, o dano que os usuários podem causar pode ser um rato ou um elefante. É claro que um usuário com todos os direitos pode fazer qualquer coisa. Um usuário com direitos limitados pode causar danos significativamente menores. Em particular, não pode quebrar o sistema.

AT: – No perímetro protegido, alguém tentou criar protocolos inesperados para o servidor, a fim de destruir completamente o servidor e, se tiver sorte, todo o cluster... Será que alguma vez fica tão “bom”?

MT: “Nunca ouvi falar dessas coisas.” O fato de você poder travar um servidor dessa forma não é segredo. Falha por dentro, sendo do protocolo, sendo um usuário autorizado que pode escrever algo assim na mensagem... Na verdade é impossível, porque ainda será verificado. É possível desabilitar essa autenticação para usuários que não a desejam - então isso é problema deles; eles, grosso modo, destruíram as próprias paredes e você pode empurrar um elefante lá, que vai pisotear... Mas em geral, você pode se vestir de reparador, venha e tire!

AT: – Obrigado pelo relatório. Sergei (Yandex). Existe uma constante no Mong que limita o número de membros votantes no Conjunto de Réplicas, e essa constante é 7 (sete). Por que isso é uma constante? Por que isso não é algum tipo de parâmetro?

MT: – Temos conjuntos de réplicas com 40 nós. Sempre há uma maioria. não sei qual versão...

AT: – No Replica Set você pode administrar membros sem direito a voto, mas há no máximo 7 membros votantes. Como podemos sobreviver ao desligamento neste caso se nosso Replica Set estiver espalhado por 3 data centers? Um data center pode desligar facilmente e outra máquina pode cair.

MT: – (EN) Isto já está um pouco fora do âmbito do relatório. Esta é uma questão geral. Talvez eu possa te contar sobre isso mais tarde.

HighLoad++, Mikhail Tyulenev (MongoDB): Consistência causal: da teoria à prática

Alguns anúncios 🙂

Obrigado por ficar com a gente. Gostou dos nossos artigos? Quer ver mais conteúdos interessantes? Apoie-nos fazendo um pedido ou recomendando a amigos, nuvem VPS para desenvolvedores a partir de US$ 4.99, um análogo exclusivo de servidores básicos, que foi inventado por nós para você: Toda a verdade sobre VPS (KVM) E5-2697 v3 (6 núcleos) 10 GB DDR4 480 GB SSD 1 Gbps a partir de $ 19 ou como compartilhar um servidor? (disponível com RAID1 e RAID10, até 24 núcleos e até 40 GB DDR4).

Dell R730xd 2x mais barato no data center Equinix Tier IV em Amsterdã? Só aqui 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV a partir de US$ 199 na Holanda! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - a partir de US$ 99! Ler sobre Como construir uma empresa de infraestrutura. classe com o uso de servidores Dell R730xd E5-2650 v4 no valor de 9000 euros por um centavo?

Fonte: habr.com

Adicionar um comentário