Banco de Dados

Banco de Dados

Herb: mecanismo de replicação multi-DC para datastore Schemaless da Uber

6 ago, 2018
Publicidade

Schemaless, o armazenamento de dados escalável e tolerante a falhas da Uber, suporta as mais de 600 cidades onde operamos e as 15 milhões de corridas por dia que ocorrem em nossa plataforma, sem mencionar Uber Eats, Uber Freight e outras linhas de negócios. Desde 2014, implementamos mais de 50 instâncias Schemaless, cada uma com vários datastores e muitos milhares de nós de armazenamento.

À medida em que nossos negócios cresciam nos mercados globais, precisávamos da capacidade de replicar dados para vários datacenters a fim de oferecer suporte para a nossa arquitetura active-active. Conjuntos de dados replicados permitem que nossos aplicativos leiam e escrevam em datacenters o mais perfeitamente possível e, no caso de uma falha do datacenter, permite que os aplicativos continuem funcionando normalmente.

Para atender a essas necessidades, criamos o Herb, nossa solução de replicação. O Herb foi construído com o Go, uma das linguagens mais populares da Uber Engineering. Sempre que os dados são gravados em um datacenter, o Herb pode replicar esses dados para outras pessoas, garantindo resiliência e disponibilidade. O Herb também é agnóstico em relação ao protocolo de transporte, permitindo flexibilidade de rede e aplicabilidade para futuras arquiteturas.

Desafios de design

Quando começamos a projetar e analisar o problema de replicação que o Herb resolveria, identificamos alguns requisitos que nos forçariam a ser criativos em relação à nossa solução:

Figura 1: O sistema de replicação que projetamos precisou lidar com cinco principais preocupações.
  • Consistência: para criar experiências contínuas de usuário, precisamos garantir que os dados sejam atualizados e consistentes entre todos os datacenters participantes da replicação. Dados inconsistentes podem levar a uma experiência inferior na nossa plataforma.
  • Entrega pelo menos uma vez: como o Schemaless é apenas anexado, os datastores que reaplicam as atualizações não são um problema.
  • Entrega em ordem de novas atualizações: todas as atualizações devem ser solicitadas com base na origem do datacenter. Por exemplo, as atualizações originadas no DC1 terão o mesmo pedido no DC2, e vice-versa. Com essa abordagem, os aplicativos podem ler de qualquer datacenter e ver as mesmas atualizações solicitadas com base na origem.
  • Diferentes velocidades de consumo: datacenters diferentes consomem dados em diferentes velocidades. À medida em que a Uber escala, devemos garantir que um datacenter mais lento não bloqueie a replicação para um datacenter mais rápido.
  • Tolerância a falhas: precisamos de uma solução que seja tolerante a falhas, o que significa que uma falha no datacenter não afetará a replicação para outros datacenters.

Design e arquitetura do sistema

Figura 2: Essa figura representa a topologia de replicação de malha com “n” datacenters, ou seja, o Herb conecta cada instância Schemaless a todas as outras instâncias nos outros datacenters.

Cada datacenter Herb gerencia uma topologia de malha. Na topologia de malha completa, cada nó é conectado diretamente a cada um dos outros nós. Nesse contexto, cada datacenter se conecta a todos os outros datacenters. A configuração de replicação consiste em vários fluxos, um em cada direção para cada datacenter.

Quando uma gravação ocorre em uma instância Schemaless em um datacenter, o Herb é responsável por transportar a gravação para todos os outros datacenters. Dessa forma, se um datacenter ficar inativo, seus dados permanecerão acessíveis pelos outros datacenters.

Figura 3: Nesse exemplo, há cinco tarefas, três das quais são atribuídas ao Herb worker no Host1, enquanto as duas restantes são manipuladas pelo Herb worker no Host2.

O Herb é implementado em vários hosts. Para a descoberta de hosts, ele aproveita o Uber Naming Scheme (UNS), a solução de registro de serviço interno da Uber. Cada processo Herb pode ter várias tarefas. Em outras palavras, uma unidade de trabalho em um processo Herb, como mostrado na Figura 3, acima. Não há dependência entre tarefas, e cada tarefa é executada em um Goroutine independente.

Nós projetamos o transporte do Herb para ser configurável. Por isso, ele não é acoplado a nenhum protocolo único. Por exemplo, usamos pela primeira vez o Transmission Control Protocol (TCP), movemos para o Hypertext Transfer Protocol (HTTP) e estamos atualmente usando o YARPC, plataforma open source de chamada de procedimento remoto (RPC) da Uber para serviços Go. Nós implementamos nossa camada de transporte de uma maneira que permite fácil extensão para qualquer outro protocolo.

Para o leitor de banco de dados, inicialmente tentamos usar um leitor de pesquisa em nosso protótipo. No entanto, as consultas ao banco de dados exigem muitos recursos, e descobrimos que a pesquisa constante de armazenamentos de dados com zero ou baixo tráfego resulta em cargas de banco de dados desnecessárias.

Essa abordagem não era eficiente nem escalável, então implementamos um leitor de streaming. Escolhemos os logs de commit como a fonte de streaming, pois esses logs contêm todas as atualizações recentes e não exigem consultas de banco de dados para capturar seus dados.

Garantir o pedido de atualizações do datacenter local foi uma parte importante do nosso design, pois essa restrição simplifica muito a lógica de aplicativos que consomem dados. Esses aplicativos não precisam se preocupar com o pedido de origem local, pois as atualizações de leitura de qualquer lugar resultarão no mesmo pedido.

Quando o Herb recebe o reconhecimento de uma gravação de um datacenter, ele atualiza o deslocamento desse datacenter. Esse rastreamento de deslocamento individual ajuda os datacenters a serem executados em velocidades diferentes durante cenários como reinicialização ou falha. Ao mesmo tempo, o Herb mantém o pedido de nossas atualizações do datacenter.

Para entender como o Herb mantém sua ordenação, suponha que tenhamos as atualizações a, b, c, d e e em ordem no DC1 (veja as figuras 7 e 8). Outro datacenter, DC2, pode ter aplicado e reconhecido a atualização enquanto o DC3 está na atualização a.

Nesse exemplo, se o Herb no DC1 for reiniciado, ele enviará a atualização e para o DC2, mas o DC3 continuará recebendo as atualizações a partir de b. Como todas essas atualizações foram originadas em um datacenter, isso significa que a ordem de atualização deve ser preservada no DC2 e no DC3.

As atualizações do Schemaless para datastore são apenas anexadas. No caso de uma reinicialização, reproduzimos as atualizações do último deslocamento persistido. Como as gravações são idempotentes, aplicar as atualizações novamente não deve ser um problema. Por exemplo, suponha que o DC2 tenha recebido as atualizações a, b e c.

Enquanto isso, o Herb no DC1 foi reiniciado e só recebeu confirmação de atualização a. Nesse exemplo, o DC1 pode reproduzir atualizações iniciadas em b e o DC2 pode receber atualizações b e c novamente, sem afetar o armazenamento de dados.

Streaming

A Uber armazena petabytes de dados divididos em vários datastores. À medida em que nossos dados aumentam, é essencial que nossa solução de replicação possa escalar de acordo. Para dar suporte ao nosso crescimento, criamos o Herb para ser eficiente e, ao mesmo tempo, reduzir a latência de replicação de ponta a ponta entre os datacenters. Durante nossos testes iniciais, descobrimos que consultar o banco de dados para leituras não escalaria.

Como alternativa, criamos um método muito mais eficiente pelo qual lemos os logs do banco de dados para identificar os dados atualizados que precisam ser replicados em nossos datacenters. Esse modelo de streaming baseado em log nos permitiu acelerar o Herb e reduzir nossa latência de ponta a ponta em milissegundos.

Figura 4: O desempenho da replicação mediana do Herb é de 550-900 células recebidas por segundo e uma latência de aproximadamente 82 a 90 milissegundos. Essa latência também inclui o tempo de comunicação de rede entre os datacenters, que chega a cerca de 40 milissegundos.

A Figura 4, acima, mostra gráficos retirados de um dos datastores em uma instância de produção do Mezzanine, nosso datastore Schemaless. Esse datastore recebe aproximadamente de 550 a 900 células por segundo. Ele leva aproximadamente de 82 a 90 milissegundos para replicar essas atualizações, com as células disponíveis para serem lidas por outros datacenters. A latência real do Herb é ainda menor do que a mostrada na Figura 4, porque o atraso médio inclui o tempo de rede entre os datacenters, o que adiciona cerca de 40 milissegundos à latência total.

Logs de commit

Os logs lidos pelo Herb são transacionais, registrando todas as alterações aplicadas aos nossos bancos de dados. Cada entrada de registro é atribuída a um número único, crescente e monotônico. Naturalmente, o Herb não pode modificar os registros existentes nos logs, mas os verifica e os interpreta para capturar as alterações feitas nos bancos de dados.

Figura 5: Nossos arquivos de log como são armazenados no disco, e cada registro é armazenado em um arquivo de dados. O deslocamento correspondente é armazenado em um arquivo de índice.

Conforme mostrado na Figura 5 acima, os logs consistem em um arquivo de dados, contendo registros individuais e um arquivo de índice. Os arquivos de índice são usados para indexar o deslocamento de disco dos registros presentes em um determinado arquivo de dados para evitar a varredura do arquivo. Ao fazer isso, ele adquire as alterações com baixa latência e não afeta o desempenho do banco de dados.

Modelo de implementação

Do ponto de vista do modelo de implementação, o conjunto completo de instâncias Schemaless é particionado em subconjuntos que chamamos de “coortes de replicação”.

Figura 6: Cada uma das coortes de replicação do Herb contém algumas instâncias Schemaless separadas de acordo com suas necessidades. Por exemplo, uma instância de alto tráfego pode compartilhar recursos com uma instância de baixo tráfego, portanto, ambos podem estar em uma coorte de replicação. Também temos coortes dedicadas de replicação para testes, preparo e outros propósitos.

Uma coorte de replicação é uma unidade de implementação no Herb que contém várias instâncias Schemaless. Instâncias dentro de uma coorte podem compartilhar recursos de replicação entre si, e uma implementação manipula uma coorte de instâncias Schemaless. Instâncias críticas podem ser isoladas de outras, colocando-as em sua própria coorte. Em uma coorte, é garantido que cada instância tenha um conjunto mínimo de recursos.

Agendamento

Como cada coorte de replicação contém várias instâncias de execução de workers do Herb, precisamos garantir a cooperação e a coordenação por meio de um processo de agendamento simplificado. Para conseguir isso, decidimos usar o Ringpop, o software open source da Uber, projetado para balanceamento de carga e coordenação entre aplicativos. Em nossa implementação, cada worker do Herb anuncia-se, torna-se um nó no anel e descobre outros. A distribuição de tarefas ocorre após a formação bem sucedida do anel.

Consistência

Figura 7: O Herb impede que os datacenters recebam atualizações fora de ordem.

O Herb garante a ordem das atualizações que recebe de seu datacenter local e preserva a ordem durante o transporte dessas atualizações para outros datacenters. Como exemplo, a Figura 7, acima, mostra que o datacenter DC1 recebeu quatro atualizações, as quais estamos rotulando de 101, 201, 301 e 401, armazenadas em ordem monotônica crescente. O Herb mantém essa ordem de atualização ao replicar os dados em nosso segundo datacenter, o DC2, primeiro escrevendo 101, depois 201, 301 e 401.

Como o sistema suporta replicação assíncrona, nossos datacenters se tornam consistentes. As gravações são consideradas concluídas assim que o datacenter remoto as reconhecer.

Validação e lançamento

Como a famosa cientista da Microsoft Leslie Lamport escreveu em 1987: “Um sistema distribuído é aquele em que a falha de um computador que você nem sabia que existia pode inutilizar seu próprio computador”. Essa citação ilustra a necessidade de validar se um sistema distribuído está funcionando corretamente, garantindo que mesmo falhas parciais não corrompam os dados. Com o Herb, criamos ferramentas para validar a ordem das atualizações e garantir que nossos dados sejam consistentes entre os datacenters.

Sombreamento de tráfego

Embora tenhamos testado componentes individuais do Herb antes do lançamento, precisávamos ver como ele se comportaria com o tráfego de produção ao vivo e se os dados replicados eram consistentes. Usando ferramentas offline, lemos a partir da instância de produção e usamos o Herb para replicar essas atualizações em uma instância de teste. Quando ficamos satisfeitos com a consistência do Herb, implementamos isso na produção.

Verificação contínua

Também construímos um framework de validação quase em tempo real para auditar dados replicados pelo Herb. Esse framework relata quaisquer discrepâncias de dados causadas pela replicação, permitindo que monitoremos o sistema continuamente na produção.

Figura 8: O framework de verificação contínua do Herb lê os nós do Schemaless e a ordem da validação. Como nessa figura, o bloco de dados representa o cluster1 do datacenter e podemos ver que as atualizações (101, 301, 401 e 501) se originaram no datacenter A e ambos datacenters os têm na mesma ordem. Da mesma forma, para atualização (201, 601), o mesmo pedido ocorre nos datacenters.

Próximos passos

Nessa versão do Herb, nosso foco principal, era a replicação de dados entre datacenters geográficos e tornar nosso sistema Schemaless active-active. Durante a criação do Herb, identificamos outra área para aprimoramento nesse domínio: o fluxo de dados Schemaless, ou seja, canalizar dados entre os datastores OLTP para nosso data warehouse e transmitir dados adicionais aos consumidores. Levando isso em consideração, como próximo passo, planejamos construir novos recursos que transportarão esses fluxos de changelog para os consumidores e os tornarão agnósticos da fonte.

Se você estiver criando sistemas escalonáveis com impacto global, considere se candidatar a um cargo em nossa equipe!

Assine nossa newsletter para acompanhar as mais recentes inovações da Uber Engineering.

***

Este artigo é do Uber Engineering. Ele foi escrito por Himank Chaudhary. A tradução foi feita pela Redação iMasters com autorização. Você pode conferir o original em: https://eng.uber.com/herb-datacenter-replication/