Esta é a primeira de duas postagens do blog que abordam alguns dos detalhes essenciais da tecnologia de rebalanceamento do Couchbase. Este primeiro post detalha a funcionalidade de rebalanceamento em si, e o segundo discute como monitorar e trabalhar com ele.
Guia prático de rebalanceamento com o Couchbase
Em meados de 2010, uma pequena empresa chamada NorthScale lançou um produto chamado Membase. Entre os muitos recursos revolucionários estava a capacidade de aumentar (ou diminuir) dinamicamente um cluster de "nós" (instâncias do software). Por definição, essa é uma operação on-line, o que significa que não afeta a disponibilidade dos dados armazenados nesse cluster e não afeta materialmente o desempenho de um aplicativo que acessa esses dados.
Avançamos rapidamente até agora (final de 2011... ok, foi um avanço rápido). Desde então, a empresa mudou de nome (duas vezes) para Couchbase e aprendeu muito sobre como o produto funciona "no mundo real". Ele está sendo implantado em ambientes drasticamente diferentes (pense em seu próprio data center versus a nuvem da Amazon) sob aplicativos drasticamente diferentes (pense em registro versus jogos sociais versus segmentação de anúncios). A mesma tecnologia de "rebalanceamento" está sendo empregada não apenas no Membase, mas também no futuro Couchbase Server 2.0. Achei que agora seria um bom momento para escrever este artigo bastante extenso para responder a muitas das perguntas que cercam esse processo, além de abordar alguns dos problemas conhecidos. A versão atual é a 1.7.1.1, e ela percorreu um longo caminho desde a primeira versão beta da 1.6.0, naquele memorável verão de 2010.
Chega de introdução, vamos ver até onde podemos ir na toca do coelho...
O que é um rebalanceamento?
Vamos começar em um nível mais alto. O rebalanceamento é o processo de redistribuição de dados para nós adicionados ou removidos em um cluster do Membase/Couchbase Server.
Parece bem simples, certo? Para o observador casual, é. E nossa intenção é que seja. Assim como a maioria das outras tecnologias, é preciso um pouco de complexidade para alcançar essa simplicidade (distração total: https://plus.google.com/112218872649456413744/posts/dfydM2Cnepe).
Um nível abaixo agora.
Para começar a entender como o Membase/Couchbase Server funciona, o principal conceito subjacente é o de vbuckets (http://dustin.github.com/2010/06/29/memcached-vbuckets.html). Um vbucket é uma "fatia" lógica de um conjunto de dados geral (no nosso caso, um bucket... desculpe a nomenclatura, mas ainda não tínhamos uma equipe de marketing de produto quando isso surgiu). Há um número estático desses vbuckets, e as chaves são criptografadas (usando CRC32) nessa lista estática. Dessa forma, você tem a garantia de que uma determinada chave sempre fará hash para o mesmo vbucket. Cada vbucket é então atribuído a um determinado servidor dentro do cluster. Com 256 vbuckets e um servidor, todos eles estariam no mesmo servidor. Com dois servidores, eles seriam compartilhados igualmente entre os dois com 128 buckets cada, quatro servidores receberiam 64 cada e assim por diante.
Um "mapa de vbucket" é simplesmente a enumeração dessa lista de vbuckets e dos servidores que os possuem.
Estou ignorando propositalmente a replicação no momento... Voltarei a ela, mas não é importante neste momento.
Um rebalanceamento é simplesmente (essa palavra novamente...) o processo de mover um determinado número de vbuckets de alguns servidores para outros servidores. O objetivo final é que cada servidor dentro do cluster termine com o mesmo número de vbuckets. Isso garante que os dados sejam distribuídos uniformemente em todo o cluster e, portanto, o acesso do aplicativo a esses dados também seja balanceado de forma uniforme em todos os nós do cluster.
Está me acompanhando até agora? (se não estiver, envie um e-mail para perry@couchbase.com).
E outro nível.
Quando um rebalanceamento é iniciado, um processo chamado "orquestrador" examina o mapa atual de vbuckets e o combina com qualquer adição/remoção de nós para calcular como deve ser o novo mapa de vbuckets. Em seguida, o orquestrador começa a iniciar a movimentação de vbuckets de um nó para outro. É importante observar que o orquestrador simplesmente "inicia o processo" entre dois nós, mas não transfere de fato os dados em si... isso é propositalmente deixado para os nós de origem e destino coordenarem para evitar gargalos ou pontos de falha. Do ponto de vista do orquestrador, não importa realmente se um nó está sendo adicionado ou removido do cluster. De fato, vários nós podem ser adicionados e/ou removidos do cluster no mesmo rebalanceamento. Tudo o que está acontecendo é que um novo mapa de vbucket está sendo calculado e os movimentos de vbucket iniciados para tornar esse mapa uma realidade.
Cada vbucket é movido individualmente e independentemente dos outros (vários podem ser e são movidos em paralelo, mas o ponto é que não há relação entre vbuckets exclusivos). O nó de destino inicia um processo chamado 'ebucketmigrator' que abre uma conexão TAP (http://www.couchbase.org/wiki/display/membase/TAP+Protocol) com um vbucket no nó de origem. Essa conexão tem sinalizadores específicos que indicam que ela a) deseja todos os dados contidos nela e b) planeja "assumir" esse vbucket quando tudo estiver concluído.
Essa última parte informa ao nó de origem para iniciar o processo de transição assim que todos os dados forem enviados. (lição de história: o ebucketmigrator costumava se chamar vbucketmigrator, mas nós o transferimos para a VM Erlang... daí o 'e')
Enquanto cada vbucket está sendo movido, o acesso do cliente (leituras e gravações) ainda está indo para o local original. Depois que todos os dados são copiados, ocorre uma troca atômica em que o local original diz "não sou mais o mestre deste vbucket" e envia um "token" para o vbucket recém-criado dizendo "você é". O vbucket original passa de ativo para morto, e o novo passa de pendente para ativo. Os clientes inteligentes e o Moxi são atualizados com um novo mapa de vbucket para saber que isso ocorreu e as solicitações de dados subsequentes são direcionadas para o novo local. (veja algumas seções abaixo para uma discussão ainda mais aprofundada sobre o comportamento do Moxi e do cliente inteligente)
E agora o nível inferior
Pelo menos até onde eu vou. Isenção de responsabilidade: este texto vai se tornar bastante técnico. Pule uma seção se estiver com pouco tempo.
Conforme mencionado acima, o orquestrador direcionará o processo ebucketmigrator no nó de destino para "puxar" um vbucket do nó de origem por meio do TAP.
Conexões TAP no nó de origem
Quando essa conexão TAP é iniciada com o nó de origem, um "cursor" começa a percorrer a tabela de hash dentro do vbucket específico. Paralelamente, um processo de "backfiller" é iniciado e decide se os dados devem ser carregados em massa a partir do disco. Como você provavelmente sabe, o Membase/Couchbase suporta perfeitamente a existência de mais dados no sistema do que a RAM disponível. Nesse caso, pode haver uma quantidade significativa de dados que precisam ser lidos do disco para serem transferidos para outro nó. O processo de backfiller analisa a "proporção de itens residentes" (quantidade de dados armazenados em cache na RAM e não armazenados). Se essa proporção for menor que 90%, ele carrega em massa todo o vbucket do disco em um buffer temporário de RAM (há limites e retrocessos incorporados para garantir que a capacidade de RAM do nó não seja excedida). Se a proporção de itens residentes for maior que 90%, esse processo não ocorrerá.
À medida que o cursor percorre a tabela de hash, ele começa a transmitir as chaves e os documentos pela conexão TAP. Qualquer coisa que já esteja armazenada em cache na RAM é enviada muito rapidamente; qualquer outra coisa é retirada do espaço temporário do buffer (se disponível) ou lida de uma única vez no disco.
Durante esse processo, as mutações nos dados que já foram enviados são transmitidas pelo fluxo TAP à medida que ocorrem (tecnicamente, elas são enfileiradas, mas, de qualquer forma, isso acontece muito rápido). As mutações nos dados que ainda não foram enviados são aplicadas somente ao vbucket de origem e serão coletadas quando esse documento específico for transmitido.
Depois que todos os dados tiverem sido copiados e sincronizados, a transição acontece. Tecnicamente, seria possível transmitir as alterações tão rapidamente para um vbucket que ele nunca conseguiria recuperar o atraso e fazer a troca. Na prática, isso nunca acontece. Qualquer pequeno atraso no lado do cliente entre as solicitações é suficiente para que os últimos bits sejam transmitidos, e seria extremamente raro que a comunicação entre nós fosse drasticamente mais lenta do que a comunicação entre cliente e servidor.
Conexões TAP no nó de destino
Em geral, a extremidade receptora de uma conexão TAP não é tratada de forma muito diferente de um cliente comum que coloca dados no vbucket específico desse nó. Há algumas exceções:
- O vbucket no lado do destino está no estado "pendente". Isso significa que os dados contidos nele não são acessíveis a nada além do fluxo TAP que envia tráfego para o vbucket.
- Os dados não são replicados. Tradicionalmente, colocar dados em um vbucket faria com que eles fossem replicados para a réplica desse vbucket. Isso não acontece com os vbuckets pendentes.
- Backoffs do TAP (este é importante): Para evitar que um nó de origem rápido sobrecarregue um nó de destino mais lento, há uma parte especial do protocolo TAP chamada "backoffs". Isso permite que o destino informe ao remetente "PARE! ESPERE! Preciso de mais tempo...". Quando o remetente recebe essa mensagem, ele recua e tenta novamente a solicitação após um breve período de tempo. Atualmente, esses retrocessos são acionados quando a fila de gravação em disco no destino atinge 1 milhão de itens. Isso é medido em todos os vbuckets desse nó e pode ser uma combinação do tráfego de aplicativos e do tráfego de rebalanceamento. Há mais discussões abaixo sobre como monitorar isso.
Parabéns, agora você é um rebalanceador de nível 4! Sei que foi uma viagem vertiginosa, mas obrigado por ter me acompanhado.
Replicação e rebalanceamento
Não há muito o que dizer aqui, mas não quis deixá-lo completamente de fora. Cada vbucket é replicado 1, 2 ou 3 vezes em seus vbuckets de "réplica". Durante um rebalanceamento, esses vbuckets de réplica também são movidos para garantir um cluster equilibrado e são criados se ainda não existirem. Por exemplo, um único nó não tem nenhuma réplica. Quando você adiciona o segundo nó, essas réplicas são criadas. Se sua contagem de réplicas for maior que 1, a adição de mais nós fará com que sejam criadas ainda mais réplicas. É importante estar ciente disso, pois há uma multiplicação de dados sendo movidos quando várias réplicas estão envolvidas.
Há alguns truques especiais empregados durante um rebalanceamento para que não seja necessário mover um vbucket ativo e sua réplica. Se o algoritmo determinar, o software é capaz de alternar um vbucket ativo e uma réplica "no lugar" e, em seguida, mover apenas a réplica.
Por fim (pelo menos para este tópico), todo o processo de rebalanceamento não é concluído até que as réplicas tenham sido suficientemente "alcançadas" por seus vbuckets ativos, o que também pode aumentar o tempo necessário para um rebalanceamento. Essa é uma evolução da implementação original, que se preocupava apenas com os vbuckets ativos. Isso causava dois problemas. Um deles é que o rebalanceamento era "feito" antes que o cluster estivesse realmente seguro. Em segundo lugar, havia uma carga imensa colocada em todo o cluster quando o rebalanceamento terminava para reinstanciar as réplicas. A versão atual (1.7.1) alterou esse comportamento para corresponder ao que descrevi anteriormente neste parágrafo.
Clientes Moxi e Smart durante um rebalanceamento
Embora este não seja o lugar para uma descrição completa do funcionamento interno, você deve entender as ideias básicas de todos os clientes inteligentes e do Moxi.
Quando um cliente ou Moxi é iniciado, ele se conecta ao URL de qualquer nó do Membase (via porta 8091 usando HTTP). Ele se autentica, se necessário (apenas não é necessário para o bucket padrão) e recebe um mapa de vbucket. Isso é o que se chama de "conexão de fluxo contínuo" ou "fluxo de cometa" e a conexão HTTP permanece aberta (assim como a conexão TCP, acredito). Esse é o fim da comunicação quando tudo está estável.
Se o cliente sair ou reiniciar, ele passará por esse processo novamente. Se a conexão for fechada, ele também tentará se reconectar. Se o nó com o qual estava falando pela primeira vez não responder, ele irá para o próximo da lista (supondo que você tenha fornecido uma lista... a prática recomendada aqui). Ele faz um rodízio até obter uma resposta e, em seguida, permanece conectado. Observe que ele não faz conexões com todos os nós de sua lista, apenas com um de cada vez.
Agora, durante um rebalanceamento, há algumas nuances. Por definição, cada movimento do vbucket é comunicado de volta ao cliente por meio dessa conexão. Na realidade, não é bem assim. As versões anteriores esperavam até o final do rebalanceamento para enviar uma nova lista. Agora, a lista atualizada é enviada antes de fazermos o rebalanceamento (chamado de "mapa de avanço rápido") e cabe ao cliente "descobrir" à medida que o processo avança (mais sobre isso adiante).
Quando um cliente (ou o Moxi) está enviando solicitações ao cluster, ele pega a chave e a compara com a lista de vbuckets. Em seguida, ele examina o mapa que possui para determinar qual servidor está ativo para aquele vbucket. Se o mapa estiver correto, o servidor aceitará a solicitação (seja ela qual for, todas as operações têm uma chave e um ID de vbucket). Se o ID do vbucket que o cliente/Moxi está enviando para um servidor não estiver ativo nesse servidor, ele responderá com um erro dizendo "não é meu vbucket". Durante um rebalanceamento, se um cliente/Moxi não for atualizado a tempo, tiver algumas solicitações "em voo" ou, de alguma forma, simplesmente perder o memorando, o nó antigo responderá com um erro "not my vbucket" a todas as solicitações após esse ponto no tempo.
Embora tecnicamente seja um erro, essa mensagem é, na verdade, apenas um sinal para o cliente/Moxi de que ele precisa encontrar o local correto para essa solicitação e retransmiti-la. Isso serve para garantir que nunca haja clientes acessando os mesmos documentos em mais de um local. No nível mais baixo, isso é apenas um erro dizendo que a solicitação não era adequada para esse servidor. Em um nível mais alto da pilha, cabe ao cliente/Moxi entender que isso significa que ele precisa encontrar o local correto.
É nesse ponto que as coisas começam a divergir um pouco em cada implementação. Por exemplo, o Moxi originalmente tentava por força bruta todos os servidores do cluster. Se nenhum respondesse, ele enviava esse erro para o cliente (memcached legado), que não tinha ideia do que fazer com ele. Agora, lidamos com isso muito melhor e a implementação atual é fazer com que o Moxi consulte o novo mapa de "avanço rápido" que recebeu no início do rebalanceamento. A ideia básica é fazer com que os clientes inteligentes sigam a mesma lógica, sendo que cada implementação é ligeiramente diferente.
Em geral, há muito pouco tráfego nesse "canal de gerenciamento" para clientes/Moxi. Na verdade, somente quando um cliente se conecta ou durante um rebalanceamento e, mesmo assim, o volume de tráfego é muito baixo.
A(s) seguinte(s) pergunta(s) surgiu(ram) recentemente, então pensei em abordá-la(s) aqui:
"Olá Perry, tenho um usuário que tem o que ele descreve como uma rede instável, seja por causa de conjuntos de regras de firewall ou conectividade, e ele queria saber o que acontece durante um rebalanceamento quando o mapa do vbucket é atualizado, mas o computador do cliente não está disponível? Vejo que o cluster envia coisas como essa para o cliente por meio do 8091 e do HTTP, mas ele mantém uma conexão persistente ou o cluster só se conecta ao cliente quando há uma atualização que ele precisa conhecer?
Eles estão preocupados com o fato de que, após um rebalanceamento, o cliente pode ter um mapa obsoleto e querem saber mais detalhes sobre como isso é tratado."
E minha resposta detalhou esses dois pontos:
- A conexão é mantida sempre aberta. O(s) cliente(s) sempre se conecta(m) ao cluster, e não o contrário... o cluster enviará um novo mapa sobre as conexões existentes.
- Um cliente que perder sua conexão com o cluster tentará restabelecê-la (configurável). Sempre que ele se reconectar (pela primeira vez ou não), receberá o mapa mais recente que o cluster tiver. Ironicamente, uma rede instável, em teoria, pode ajudar a manter o mapa constantemente atualizado durante um rebalanceamento, mas isso é assunto para outra discussão.
Resumindo
Se você chegou até aqui, não só sabe o que é rebalanceamento, mas também sabe como ele funciona. Isso deve ajudar imensamente quando você aumentar seu cluster para que entenda melhor o que está acontecendo nos bastidores, tanto em seus servidores quanto em seus clientes.
O segunda parte analisa como monitorar seu cluster e o progresso do rebalanceamento, como lidar com falhas e como o desempenho do cluster pode ser afetado (e atenuado) durante o processo de rebalanceamento.
Obrigado pela postagem no blog. Realmente útil para entender um pouco mais sobre como funciona a replicação do couchbase. Uma pergunta adicional: isso mudou significativamente na versão atual do couchbase 3.0 ou a ideia geral e a implementação permanecem como descritas na publicação do blog.
No momento, estou testando o couchbase para ver se ele atende às necessidades da nossa empresa. Estamos criando milhões de documentos e realizando leituras quase instantaneamente depois. Queríamos saber como o Couchbase se comportaria durante uma falha de nó. Durante um teste, eliminei um nó do servidor em meu cluster para ver como o Couchbase lida com isso usando o failover automático. Parece que ele consegue lidar com as solicitações de forma aceitável até que eu atinja o rebalanceamento, então recebo erros de conexão no cliente java 2.0.1. Há algum motivo para isso estar acontecendo?
Devo fazer o rebalanceamento somente quando o processo de inserção terminar? Se for esse o caso, isso não parece muito bom porque temos um grande número de transações e, em caso de falha, faremos o failover de um nó e, em seguida, reequilibraremos até que todos os nossos nós estejam novamente ativos e o cluster esteja novamente em um estado ideal.
Saúde,
Ivan
Obrigado por escrever para o Ivan!
Esta postagem é bastante antiga, mas os conceitos básicos permanecem os mesmos. A maior mudança na versão 3.0 foi a descontinuação do TAP e a introdução de um protocolo de alteração de banco de dados (DCP) que nos dá muito mais flexibilidade e funcionalidade para mover/replicar dados pelo cluster, para outros clusters e, eventualmente, para ferramentas de terceiros. O DCP é usado basicamente da mesma forma que o TAP para fins de replicação e rebalanceamento, conforme descrito acima.
Com relação aos problemas que você está vendo, isso certamente não é esperado. O rebalanceamento deve ser realizado com pouco ou nenhum impacto no aplicativo e é muito usado por nossos clientes e testado com frequência no controle de qualidade. Solicito que entre em contato conosco por meio das listas de discussão ou diretamente comigo (perry@couchbase.com) para que eu possa obter um recurso técnico para trabalhar com você.
Mais uma vez, obrigado e espero poder ajudá-lo.
Perry
Vocês oferecem suporte ao rebalanceamento usando SSL ou alguma outra conexão segura? Temos requisitos de segurança para SSL em todos os lugares, até mesmo de nó a nó em nossa pilha.
Olá, Kevin, obrigado por escrever. No momento, não temos criptografia entre os nós de um cluster (oferecemos suporte à criptografia entre clusters), mas isso é algo que foi discutido como parte de nosso roteiro de segurança. Se quiser me enviar um e-mail particular, posso colocá-lo em contato com nosso gerente de produtos de segurança para ajudá-lo a entender seus requisitos e como podemos atendê-los.