Replicação de dados multi-node simples com o Pub/Sub do HarperDB
Foto de Steve Harvey no Unsplash
Nos artigos anteriores que escrevi sobre HarperDB, exploramos os domínios do Kubernetes e do Helm. Agora é hora de dar um passo atrás e tentar entender outra funcionalidade bem legal que ele oferece, nesse artigo curto e direto ao ponto.
Vamos imaginar que trabalhamos numa empresa que fornece business intelligence sobre processos produtivos pra fábricas grandes ao redor do mundo, e você está lançando um novo produto: fábricas inteligentes! Pra isso, as fábricas seriam equipadas com os mais variados tipos de sensores que monitoram várias propriedades chave das máquinas, como pressão, temperatura e peso aplicado sobre elas.
O problema é que todos esses dados precisam ser enviados de volta pros servidores principais da empresa, pra que a gente possa aplicar nossos modelos de machine learning e análise de big data em cima deles. E te pediram pra fornecer uma arquitetura boa pra fazer isso acontecer. O processo tem algumas regras:
- Os sensores mandam dados numa taxa bem alta, então mandar eles conforme são recebidos não seria uma boa ideia, já que isso ia sobrecarregar a rede, tornando o streaming de dados uma solução ruim.
- Os dados devem ser guardados num banco de dados na empresa, em formato bruto.
- Os sensores vão continuar mandando dados mesmo em caso de falha de rede. Você precisa sincronizar esses dados com os servidores da empresa assim que a rede voltar.
- A empresa precisa conseguir mandar configurações pros sensores instalados nos clientes a partir da matriz, sem precisar de uma visita local.
Como você pode realizar todas essas tarefas com o mínimo de esforço e usando a melhor tecnologia? Aí entram os clusters do HarperDB.
Clusters#
Quando falamos de clusters, a ideia base que vem à cabeça é algo tipo Kubernetes, né? Um monte de máquinas virtuais que se comportam como uma única e grande máquina. Só que o HarperDB leva essa ideia um passo além. Como estamos falando de um banco de dados, o conceito de “trabalhar como um só” se resume a ter os mesmos dados em todas as instâncias eventualmente, e esse é, de fato, o conceito de um cluster de bancos de dados.
Porém, em outros bancos, todos os dados são replicados por todo o cluster não importa o quê, eles vão ficar idênticos eventualmente. Então por que você não pode escolher quais dados quer sincronizar?
Quando você agrupa duas ou mais instâncias de um banco HarperDB num cluster, elas vão te perguntar quais dados você quer compartilhar, isso se chama subscription (assinatura). Então, basicamente, o que o HarperDB tem é uma implementação bem robusta e sofisticada de um modelo publish/subscribe que funciona no nível de tabela e tem um mecanismo tolerante a falhas que permite perder a conexão a qualquer momento e ressincronizar os dados automaticamente assim que ela voltar.
Como os clusters funcionam?#
Não vou me estender muito nessa parte porque o HarperDB tem uma documentação bem legal sobre isso, mas a ideia é que cada banco de dados forma um node no cluster, e o grupo inteiro de nodes de um cluster, junto com suas configurações, se chama topologia.
Pra mandar e receber dados, os nodes dependem de canais, que são nomeados como schema:tabela, cada canal representa uma conexão entre dois nodes, mandando ou recebendo dados de uma tabela específica. Os canais têm duas opções, eles podem ser publishers, subscribers, ou os dois.
Um canal publisher vai postar todas as mudanças daquela tabela específica pra qualquer outro node que estiver inscrito nele. Vamos ver um exemplo: se eu quisesse publicar todas as mudanças do schema chamado dev na tabela dog do primeiro node pro segundo, eu poderia criar um canal de publicação chamado dev:dog.

Por outro lado, eu poderia expressar isso de forma diferente, poderia dizer que quero escutar todas as mudanças na tabela dog do node 1, então eu posso me inscrever nele:

Quando você cria uma subscription em qualquer node, qualquer mudança naquela tabela específica vai ser sincronizada com as outras tabelas. Qualquer dado novo em dev:dog vai ser publicado pro outro node, assim como qualquer atualização ou remoção. Se a tabela não existir em um dos nodes, ela também vai ser criada. Porém, operações destrutivas como drop não são propagadas.
Criando um cluster#
Então, você decide criar uma prova de conceito pra ver se sua ideia de usar clusters do HarperDB vai funcionar. Como o HarperDB tem uma imagem Docker com clustering disponível, vamos começar criando um arquivo Docker Compose.
A ideia é simular duas instâncias de um node HarperDB e conectar as duas num cluster. Pra isso vamos criar dois serviços no nosso Docker Compose, o primeiro vai representar o sensor:
services:harper-edge:image: harperdb/harperdbcontainer_name: harper-edgeports:- "9900:9925"- "9901:9926"- "62000:62344" environment: - HDB_ADMIN_USERNAME=admin - HDB_ADMIN_PASSWORD=admin - CLUSTERING=true - CLUSTERING_USER=cluster - CLUSTERING_PASSWORD=cluster - CLUSTERING_PORT=62344 - NODE_NAME=harper-edgeVamos abrir as portas 9925, 9926 e 62344, mas como vamos subir outro serviço no mesmo computador, não podemos usar as mesmas portas, então vamos mapear elas pra 9900, 9901 e 6200 respectivamente. Vamos habilitar o clustering nas variáveis de ambiente, e configurar a porta do cluster como 62344, como definimos no manifesto de portas. Por último, vamos nomear o node como harper-edge.
Pra segunda instância, que vai representar o servidor da empresa, vamos copiar a primeira parte e só mudar os nomes e as portas. O arquivo final fica assim:
services: harper-edge: image: harperdb/harperdb container_name: harper-edge ports: - "9900:9925" - "9901:9926" - "62000:62344" environment: - HDB_ADMIN_USERNAME=admin - HDB_ADMIN_PASSWORD=admin - CLUSTERING=true - CLUSTERING_USER=cluster - CLUSTERING_PASSWORD=cluster - CLUSTERING_PORT=62344 - NODE_NAME=harper-edge
harper-host: image: harperdb/harperdb container_name: harper-host ports: - "9902:9925" - "9903:9926" - "62001:62344" environment: - HDB_ADMIN_USERNAME=admin - HDB_ADMIN_PASSWORD=admin - CLUSTERING=true - CLUSTERING_USER=cluster - CLUSTERING_PASSWORD=cluster - CLUSTERING_PORT=62344 - NODE_NAME=harper-hostEstamos só somando 1 nas portas pra elas não conflitarem, e também mudando o nome do node. A parte importante aqui é que o usuário e a senha do cluster precisam ser os mesmos nos dois serviços.
Vamos subir isso com docker compose up, você deve ver o resultado de duas instâncias do HarperDB rodando. Agora vamos até o HarperDB Studio e registrar elas. Só criar sua conta, organização, e clicar em registrar uma nova instância gerenciada pelo usuário. Esses devem ser os dados que temos:

Selecione a instância gratuita e aceite os termos, já devemos estar prontos:
Esta imagem não existe mais
lh3.googleusercontent.com
Vamos fazer o mesmo pra outra:

O que vai mudar é só o número da porta, que agora é 9902.
Conectando os nodes#
A gente não vai criar nenhuma tabela em nenhuma das instâncias. Em vez disso, vamos até o nosso studio e ir na aba “Cluster” de qualquer uma das instâncias:

Como você pode ver, temos o nosso outro node listado, mas quando tentamos conectar, recebemos um erro:

Como estamos usando Docker, a instância não fica disponível através do HarperDB Studio pra ser conectada ao outro node no cluster. Nesse caso, vamos precisar botar a mão na massa, literalmente.
O HarperDB tem uma API que permite criar manualmente uma conexão entre dois nodes. Em qualquer ferramenta de requisição que você tenha – tipo Postman, Insomnia, ou qualquer outra – use a URL http://localhost:9902 numa requisição POST. Você pode usar qualquer um dos hosts pra fazer o que estamos prestes a fazer, mas pra manter a ideia do projeto, vamos conectar no host que representa o banco de dados da empresa.
Na seção de autenticação, vamos usar o HTTP auth básico, o usuário e a senha são os que definimos no arquivo compose, nesse caso os dois são admin. Agora vamos criar um corpo JSON parecido com esse:
{ "operation": "add_node", "name": "harper-edge", "host": "harper-edge", "port": 62344, "subscriptions": []}Então, do banco de dados da empresa, queremos publicar configurações e assinar os dados dos sensores, vamos dizer isso pro node com dois objetos dentro da chave subscriptions:
{ "operation": "add_node", "name": "harper-edge", "host": "harper-edge", "port": 62344, "subscriptions": [ { "channel": "default:sensor_data", "subscribe": true, "publish": false }, { "channel": "default:configs", "subscribe": false, "publish": true } ]}Agora, voltamos pro nosso studio e entramos na instância harper-host. Vamos criar nosso schema chamado default e uma tabela chamada configs.

Agora, vamos adicionar um dado simples nessa tabela clicando no botão + no canto superior direito:

Depois de salvar, vamos trocar pra instância harper-edge, e olha só! Agora temos uma tabela configs e dentro dela temos o mesmo item que acabamos de adicionar:

Vamos fazer o mesmo na instância harper-edge, e adicionar uma tabela chamada sensor_data com hash de id, e depois adicionar alguns dados nela:

E se voltarmos pro host, também vamos ter os mesmos dados lá:

Isso vale mesmo se a gente apagar os dados de um dos lados, nesse caso os dados do outro lado também vão ser apagados.
Conclusão#
Esse foi o primeiro vislumbre dessa funcionalidade incrível. Fique de olho nos próximos tutoriais sobre como podemos usar o pub/sub do Harper a nosso favor em outros exemplos do mundo real.