Introdução
A Replicação entre Datacenters (XDCR) é um recurso central importante do Couchbase que ajuda os usuários na recuperação de desastres e na localidade dos dados. A resolução de conflitos é um desafio inevitável enfrentado pelo XDCR quando um documento é modificado em dois locais diferentes antes de ser sincronizado entre os locais.
Até a versão 4.6, o Couchbase suportava apenas uma estratégia baseada em ID de revisão para lidar com a resolução de conflitos. Nessa estratégia, o ID de revisão de um documento, que é atualizado toda vez que é modificado, é usado como o primeiro campo para decidir o vencedor. Se o ID de revisão de ambos os participantes for o mesmo, CAS, TTL e sinalizadores serão usados na mesma ordem para resolver o conflito. Essa estratégia funciona melhor para aplicativos projetados para funcionar com base em uma política "A maioria das atualizações é melhor". Por exemplo, um aplicativo de ticker usado por condutores em um trem que atualiza um contador armazenado pelo servidor cb para contar o número de passageiros funcionará melhor com essa política e, portanto, terá um desempenho preciso com a resolução de conflitos baseada na ID de revisão.
A partir da versão 4.6, o Couchbase oferecerá suporte a uma estratégia adicional chamada resolução de conflitos baseada em carimbo de data/hora. Aqui, o registro de data e hora de um documento armazenado no CAS é usado como o primeiro campo para decidir o vencedor. Para manter a ordem consistente das mutações, o Couchbase usa um relógio lógico híbrido (HLC), que é uma combinação de um relógio físico e um relógio lógico. Se o registro de data e hora de ambos os participantes for o mesmo, o ID de revisão, o TTL e os sinalizadores serão usados na mesma ordem para resolver conflitos. Essa estratégia é adaptada para facilitar os aplicativos que são projetados com base em uma política de "A atualização mais recente é a melhor". Por exemplo, um aplicativo de rastreamento de voos que armazena a hora estimada de chegada de um voo no Couchbase Server terá um desempenho preciso com essa resolução de conflitos. Precisamente, esse mecanismo pode ser resumido como "A última gravação vence".
É preciso entender que o documento mais atualizado não precisa ser essencialmente o documento mais recente e vice-versa. Portanto, o usuário realmente precisa entender o design, as necessidades e o padrão de dados do aplicativo antes de decidir qual mecanismo de resolução de conflitos usar. Pelo mesmo motivo, o Couchbase projetou o mecanismo de resolução de conflitos como um parâmetro de nível de bucket. Os usuários precisam decidir e selecionar a estratégia que desejam seguir ao criar os buckets. Depois que um bucket é criado com um determinado mecanismo de resolução de conflitos via UI, Rest API ou CLI, ele não pode ser alterado. O usuário terá que excluir e recriar o bucket para alterar a estratégia. Também para evitar confusões e complicações, o Couchbase restringiu a configuração do XDCR no modo misto, ou seja, os buckets de origem e destino não podem ter estratégias de resolução de conflitos diferentes selecionadas. Ambos precisam usar a resolução de conflitos baseada em ID de revisão ou em registro de data e hora. Se o usuário tentar configurar de outra forma via UI, Rest API ou CLI, será exibida uma mensagem de erro.
Casos de uso de resolução de conflitos com base em carimbo de data/hora
Alta disponibilidade com failover de cluster
Aqui, todas as operações de banco de dados vão para o Datacenter A e são replicadas via XDCR para o Datacenter B. Se o cluster localizado no Datacenter A falhar, o aplicativo enviará todo o tráfego para o Datacenter B.
Localidade do data center
Aqui, dois clusters ativos operam em conjuntos discretos de documentos. Isso garante que nenhum conflito seja gerado durante a operação normal. Uma relação XDCR bidirecional é configurada para replicar suas atualizações entre si. Quando um cluster falha, o tráfego de aplicativos pode ser transferido para o cluster ativo restante.
Como a resolução de conflitos baseada em carimbo de data/hora garante um failover seguro?
A resolução de conflitos baseada em carimbo de data/hora exige que os aplicativos só permitam o tráfego para o outro data center depois de decorrido o máximo dos dois períodos de tempo a seguir:
- A latência de replicação entre A e B. Isso permite que qualquer mutação em andamento seja recebida pelo centro de dados B.
- A variação absoluta de tempo entre o Datacenter A e o Datacenter B. Isso garante que todas as gravações no Datacenter B ocorram após a última gravação no Datacenter A, após o atraso calculado, momento em que todas as operações do banco de dados iriam para o Datacenter B.
Quando a disponibilidade for restaurada no Datacenter A, os aplicativos deverão observar o mesmo período de tempo antes de redirecionar o tráfego. Em ambos os casos de uso descritos acima, o uso da resolução de conflitos baseada em carimbo de data/hora garante que a versão mais recente de qualquer documento seja preservada.
Como configurar o NTP para resolução de conflitos com base em carimbo de data/hora?
Um pré-requisito que os usuários devem ter em mente antes de optar pela resolução de conflitos baseada em carimbo de data/hora é que eles precisam usar relógios sincronizados para garantir a precisão dessa estratégia. O Couchbase os aconselha a usar o Network Time Protocol (NTP) para sincronizar o tempo em vários servidores. Os usuários terão que configurar seus clusters para sincronizar periodicamente seus relógios de parede com um servidor NTP específico ou um pool de pares NTP para garantir a disponibilidade. A sincronização do relógio é fundamental para a precisão do Hybrid Logical Clock usado pelo Couchbase para resolver conflitos com base em registros de data e hora.
Como QE, testar a resolução de conflitos baseada em carimbo de data/hora foi uma boa experiência de aprendizado. Um dos maiores desafios foi aprender como o NTP funciona. A configuração padrão para todos os casos de teste é ativar o NTP, iniciar o serviço, sincronizar o relógio de parede com o 0.north-america.pool.ntp.org e, em seguida, prosseguir com o teste. Essas etapas foram realizadas usando os seguintes comandos na configuração:
~$ chkconfig ntpd on
~$ /etc/init.d/ntpd start
~$ ntpdate -q 0.north-america.pool.ntp.org
Depois que o teste é feito e os resultados são verificados, o serviço NTP é interrompido e desativado usando os seguintes comandos:
~$ chkconfig ntpd off
~$ /etc/init.d/ntpd stop
Essa é a configuração vanilla em que todos os nós individuais sincronizam seu relógio de parede com 0.north-america.pool.ntp.org. Foi interessante automatizar os casos de teste em que os nós sincronizam seu relógio de parede com um pool de pares NTP, a sincronização do cluster de origem e de destino com diferentes pools NTP (A (0.north-america.pool.ntp.org) -> B (3.north-america.pool.ntp.org)) e cada cluster em uma topologia de cadeia de comprimento 3 (A (EST) -> B (CST) -> C (PST)) estão em fusos horários diferentes. Tivemos que configurar manualmente esses cenários, observar o comportamento e, em seguida, automatizá-lo.
Como testamos os cenários negativos baseados em NTP?
O próximo desafio foi testar cenários em que o NTP não está em execução nos nós do Couchbase e há uma variação de tempo entre a origem e o destino. A distorção de tempo também pode ocorrer se a diferença de horário do relógio de parede entre os clusters for alta. Qualquer mecanismo de sincronização de tempo levará algum tempo para sincronizar os relógios, resultando em uma janela com distorção de tempo. Observe que o Couchbase apenas emite um aviso ao criar um bucket com resolução de conflitos baseada em carimbo de data/hora, informando que o usuário deve garantir que um mecanismo de sincronização de horário esteja em vigor em todos os nós. Ele não valida nem restringe os usuários de criarem esse bucket se não houver um mecanismo de sincronização de tempo como tal. Portanto, é bem possível que o usuário ignore esse aviso, crie um bucket com resolução de conflitos baseada em carimbo de data/hora e veja um comportamento estranho quando houver uma variação de tempo.
Vamos considerar uma dessas situações aqui:
- Criar um bucket padrão no cluster de origem e destino com resolução de conflitos baseada em carimbo de data/hora
- Configuração do XDCR da origem para o destino
- Desativar o NTP em ambos os clusters
- Tornar o relógio de parede do cluster de destino mais lento do que o do cluster de origem em 5 minutos
- Pausar a replicação
- Criar um documento D1 no momento T1 no cluster de destino
- Criar um documento D2 com a mesma chave no momento T2 no cluster de origem
- Atualizar D1 no cluster de destino no momento T3
- Replicação de currículo
- Observe que D2 sobrescreve D1, embora T1 > T2 > T3 e a última atualização para D1 no cluster de destino devesse ter vencido
Aqui, a última gravação por linha do tempo não venceu, pois os relógios estavam distorcidos e não estavam sincronizados, o que fez com que o documento incorreto fosse declarado vencedor. Isso mostra como a sincronização de tempo é importante para a estratégia de resolução de conflitos baseada em carimbo de data/hora. Descobrir todos esses cenários e automatizá-los foi de fato um desafio.
Como testamos cenários complexos com a resolução de conflitos baseada em carimbo de data/hora?
O próximo passo foi determinar uma maneira de validar a exatidão dessa resolução de conflitos baseada em carimbo de data/hora em relação à estratégia baseada em ID de revisão. Precisávamos executar as mesmas etapas em uma configuração de XDCR e verificar se os resultados eram diferentes com base na estratégia de resolução de conflitos do bucket. Para isso, criamos dois compartimentos diferentes, um configurado para usar a resolução de conflitos baseada em revID e outro para usar a baseada em carimbo de data/hora. Agora, siga estas etapas em ambos os buckets paralelamente:
- Configurar o XDCR e pausar a replicação
- Criar o documento D1 no destino no momento T1
- Criar o documento D2 com a mesma chave na origem no momento T2
- Atualizar o documento D2 na fonte no momento T3
- Atualizar o documento D2 na fonte novamente no momento T4
- Atualizar o documento D1 no destino no momento T5
- Replicação de currículo
No primeiro compartimento, que está configurado para usar a resolução de conflitos baseada em revID, o documento D1 no destino será substituído por D2, pois foi o que sofreu mais mutações. Já no segundo compartimento, que está configurado para usar a resolução de conflitos baseada em carimbo de data/hora, o documento D1 no destino será declarado vencedor e mantido, pois é o último a sofrer mutação. Descobrir esses cenários e automatizá-los tornou nossa regressão exaustiva e robusta.
Como testamos a correção do HLC?
O desafio final foi testar a monotonicidade do relógio lógico híbrido (HLC) usado pelo Couchbase na resolução de conflitos baseada em carimbo de data/hora. Além de verificar se o HLC permanecia o mesmo entre um vbucket ativo e sua réplica, tivemos alguns cenários interessantes, como segue:
- C1 (mais lento) -> C2 (mais rápido) - as mutações feitas em C1 perderão com base no registro de data e hora e C2 sempre ganhará - portanto, o HLC de C2 não deve mudar após a replicação
- C1 (mais rápido) -> C2 (mais lento) - as mutações feitas em C1 sempre vencerão com base no registro de data e hora - portanto, o HLC de C2 deve ser maior do que era antes da replicação devido à monotonicidade
- Mesmo que o HLC de C2 não tenha mudado devido à replicação, qualquer atualização em C2 deve aumentar seu HLC devido à monotonicidade.
- Da mesma forma, para o cenário descrito em 2, além de o HLC de C2 ser maior do que era antes da replicação, mais atualizações de documentos em C2 devem manter seu HLC aumentando devido à monotonicidade
Portanto, todos esses desafios tornaram o teste da resolução de conflitos com base em carimbo de data/hora um feito gratificante do QE.