A lenda da Replicação Multimaster Síncrona em bases distribuídas

A história é conhecida e vira e mexe aparece alguém perguntando sobre isso. Bom, você tem uma mesma aplicação rodando em lugares geograficamente distintos. Podem ser países diferentes, podem ser estados, cidades ou até mesmo bairros diferentes, o problema é praticamente o mesmo. Imagine uma cadeia de empresas, todas elas rodando o mesmo aplicativo.

Você pode chegar nesta situação quando era apenas uma matriz que abriu uma filial. O banco de dados ficava só na matriz e a filial acessa o banco de dados remotamente, via Internet, conexão via rádio, linha privativa, etc. O problema é que a conexão com a filial tinha a mania de cair e quando isso acontece é como se acabasse a luz. Se pensarmos numa empresa que utiliza apenas a conexão da Telefônica aqui em SP, dá para imaginar o desespero.

Então a solução ideal se chama replicação multimaster síncrona: você tem alguns bancos de dados, cada um em um local diferente. Cada atualização realizada numa das bases é automaticamente replicada para as demais e vice-versa. Qualquer base pode sofrer atualizações. Uma vez realizada, ela é visível instantaneamente em todas as bases. Se você der um rollback em uma transação, o rollback será realizado de forma idêntica em todos os nós. Ou seja, tem de garantir o ACID em todos os nós, como se estivesse em apenas um banco de dados. Existe um recurso no PostgreSQL que visa garantir este comportamento, ele se chama commit em duas fases e está disponível a partir da versão 8.1 do PostgreSQL. O commit em duas fases é muito importante por realmente garantir a consistência através de todos os nós. Ele não resolve problemas como o uso intensivo de SEQUÊNCIAS ou o a equivalência de um TIMESTAMP com precisão absoluta, mas resolve o problema de ter um commit ou rollback sincronizado em todos os nós. Deve-se entender que o Commit em duas fases é implemento como comandos SQL e não como uma aplicação. Portanto a sua aplicação tem de ser desenhada específicamente para utiliza-lo.

Há uma solução no universo PostgreSQL conhecida como PGCluster, que não utiliza o commit em duas fases mas faz a replicação multimaster assíncrona através de um serviço de balanceamento de carga e outro serviço de replicação. E vai além disso, se a comunicação com um dos nós cair, ele sincroniza o nó quando ele volta a se comunicar com os demais. Não é ótimo?

Sim, o PGCluster é uma ideia muito interessante, mas não como uma solução de banco de dados distribuído. Cada alteração no banco de dados dispara uma atualização em cada nós que precisa ser confirmada em um por um e depois da confirmação, liberada em todos eles. Isto significa que o que seria feito em X tempo em um único nó, será feito em  2XNL tempo onde N é o número de nós envolvidos na replicação e L é a latência da rede. Isto significa que se os nós não estiverem dispostos lado a lado numa rede local de alta velocidade, a perda de desempenho é absolutamente intolerável.

Mesmo que você utilize fibras ópticas de última geração para interligar seus servidores localizados em lugares distintos, você sofrerá com uma limitação: a velocidade da luz. Até que se prove ao contrário, nada viaja a uma velocidade superior a da luz, inclusive a informação. E nas idas e vindas do commit em duas fazes, a velocidade da luz começa a ser relevante. Ou seja, o PGCluster só é funcional como solução de alta disponibilidade, para servidores que ficam todos no mesmo CPD. Ainda assim há uma perda de performance considerável em cada atualização do banco de dados (nas leituras a distribuídas da carga propcia uma melhora de desempenho). Para encerrar o assunto, vale lembrar que o PGCluster é uma solução complexa de instalar (são no mínimo 3 nós + o balanceador de carga + o replicador), sua última versão foi lançada em 02/2008 e não é uma solução realmente bem aceita pelos desenvolvedores do PostgreSQL.

Sim o commit em duas fases pode ser utilizado com sucesso para bancos de dados distribuídos, mas para sincronizar apenas algumas operações e não a base inteira. Se você pesquisar bastante sobre o assunto, irá esbarrar em outras soluções:

  • Houve algum tempo, se pensou numa forma nova para implementar uma replicação multimaster síncrona num projeto batizado como Slony II que rapidamente foi abandonado por ser considerado complexo demais e inviável na prática.
  • O PGCluster II  é uma tentativa de implementar algo semelhante ao Oracle RAC, que também é uma solução de alta disponibilidade e exige que todos os nós fiquem no mesmo local (eles tem de compartilhar o mesmo storage). Pelo que consta o PGCluter II ainda não teve nenhuma versão oficial lançada e não sei se ainda tem algum desenvolvimento ativo.
  • O Bucardo é uma solução já em produção, mas não é síncrona, ou seja, ela admite um atraso entre as atualizações em cada nó o que exige regras de resolução de conflito quando um mesmo registro é atualizados em diferentes nós. Ou seja, não garante nem tem como garantir o ACID. O Bucardo é indicado para sincronizar uma base principal com outras bases  com pouco volume de atualização, como no caso de forças de vendas que tem uma base isolada no notebook de cada vendedor.

Bom, mas então como resolvemos o problema das filiais??? Duas abordagens distintas:

  • Resolva o seu problema com o link e mantenha todos os seus dados numa única base. Pode parecer besteira, mas ainda é a solução mais utilizada por grandes empresas que podem investir na redundância de links de alta velocidade e alta disponibilidade;
  • Não integrar todos os dados em uma única base. Faça com que  cada filial tenha apenas uma fatia dos dados e ponto final. Uma variação mais eficiente é fazer com que uma chamada por informações que se encontram em outro servidor sejam desviadas  para o servidor correto. Isto se chama particionamento horizontal ou cluster shared nothing e é uma técnica bastante complexa, mas muito eficiente. Uma forma de implementar isto no PostgreSQL é utlizando o PL/Proxy do Skytools.

As duas abordagens podem parecer um tanto radicais para você? Bom, eu diria que 90% dos grandes bancos utilizam exatamente estas abordagens:  possuem cerca de 3 ou 4 sites (digamos que em SP, DF e PE) cada uma respondendo por todas as transações da sua região. Há um investimento pesado para conectar todos os terminais no site da sua região. Se você precisar de informações de um site em outra região, a conexão é desviada para o site correto.

Estas são as duas formas óbvias de encarar o problema: centralizar tudo numa única base, ou dividir logo todos os dados em bases isoladas. Isto é tudo o que você pode fazer sem ter que mexer na lógica da aplicação. No entanto, se você está disposto a mexer na sua aplicação, particularmente na modelagem desta, então existe sim um meio termo. A primeira coisa a se fazer é dividir as tabelas em partes conforme as regras de negócio para a sua atualização:

  1. Tabelas que não são atualizadas ou que são atualizadas raramente: inclua aqui tabelas de parâmetros e coisas do tipo. Se você puder realizar as atualizações em apenas um local então você pode fazer com elas caiam no segundo caso:
  2. Tabelas que são atualizadas apenas em um nó como a matriz e são consultadas por todas as outras filiais;
  3. Tabelas onde cada filial é responsável pela atualização de apenas um punhado de registros, sendo que cada filial não pode alterar os registros da outra filial;
  4. Tabelas onde cada filial deve poder atualizar qualquer registro da tabela, independente de qual filial seja.
  • Tudo que estiver nos casos 1 e 2 podem sofrer uma replicação multi/master, que é um pouco menos complexa e possui algumas boas ferramentas para implementar como o Slony I e o Londiste. A ideia é simples, você atualiza as informações em uma única base e as informações são replicadas para os demais nós.
  • Tudo que estiver no 3º caso deve ser modificado para cair no 2º caso através do particionamento de tabelas. Assim você divide uma única tabela em uma tabela pai e várias outras tabelas filhas, uma para cada filial. As atualizações só serão feitos na tabela relativa a filial onde ele está e as suas atualizações são replicadas para as demais filiais. O particionamento de tabelas no PostgreSQL é um pouco chato de ser feito (melhorou um pouco no 8.4 mas no 8.5 está prometida uma revolução), mas é bastante flexível, portanto, depois de particionar as tabelas, você cai numa situação onde a replicação master/slave pode ser aplicada novamente;
  • Se você tiver o azar de cair no 4º caso , então você não terá outra alternativa senão utilizar o commit em duas fazes. Em geral, com uma boa modelagem você consegue fugir deste tipo de situação ao máximo, mas quando não for realmente possível, a ferramenta é esta. Note que nos casos 2 e 3, não estou falando de replicação síncrona. Se você realmente precisar de transações síncronas, com ACID entre todos os nós, o commit em duas fases é a sua única opção.

Como você pode perceber, não existe solução fácil. Mas, feliz são os desenvolvedores que criam suas aplicações já pensando neste tipo de solução. É claro que hoje se utiliza muito SOA, REST e outras tecnologias para trafegar informações entre aplicações. Outros se aventuram com o envio de TXT, ou XML para lá e para cá das mais diversas formas. Há ainda aqueles que criam uma teia de DBLinks para interligar as bases. Eu como DBA não sou especialista neste tipo de solução, mas acredito que elas sirvam para resolver outro tipo de problema. Um exemplo clássico seria a disponibilização de informações ou serviços de uma aplicação para outra aplicação diferente e não para a replicação de informações dentro de uma mesma aplicação. Trocar informações entre aplicações é muito diferente de replicar informações em bases distribuidas. A chave do problema sempre estará nas diferenças entre síncrono e assíncrono, e master/slave e multimaster.

Bom, eu acho que é só por enquanto. Se alguém conhecer outro tipo de solução que não seja baseada em nenhum dos casos aqui ou se tiver uma experiência que refute os argumentos que apresentei aqui, por favor deixe um comentário abaixo para trocarmos umas figurinhas, ok?

Cluster != Replicação

Hoje um e-mail na lista do PostgreSQL me levou inspirou a escrever este texto. A questão é que muitas pessoas confundem replicação com clustering. A confusão não é a tôa… a palavra Cluster possui vários significados diferentes dependendo do contexto. Temos a idéia de tabelas clusterizadas, que possuem significado diferente no Oracle e PostgreSQL por exemplo e por sua vez não tem nada a ver com o conceito de cluster de SGDB. O PostgreSQL chama de Cluster o conjunto de banco de dados gerenciados por uma única instância (conjunto de datafiles, arquivos de controle e processos no servidor que formam um SGDB). O PGCluster apesar do nome, é uma solução de replicação e não de clustering! Algumas soluções proprietárias também gostam de adotar o status de Cluster, quando na verdade se tratam de soluções de replicação. O princípio da replicação é a de ter cópias de dados em mais de um banco de dados que possam ser sincronizadas entre si. O princípio do cluster é ter vários SGDBs se comportando como se fossem uma só instância de forma transparente para a aplicação. Veja por exemplo que o conceito de Grid, que a Oracle adotou como buzz word, não existe de fato em banco de dados!

Quando falamos em Cluster de banco de dados, pensamos em 3 tipos de clusters:

  • Shared All: Onde a memória (shared buffers) e os discos (datafiles) são compartilhados por cada nó do cluster;
  • Shared Disc: Onde apenas os disco são compartilhados pelos nós do cluster;
  • Shared Nothing: Onde cada nó tem a sua própria memória e discos;

Quando falamos em Replicação de banco de dados, pensamos em 4 tipos de replicação orientados por 2 paradigmas distintos:

  • Replicação sincrona: onde todas as réplicas possuem sempre os mesmo dados;
  • Replicação assíncrona: onde as réplicas podem ser sincronizadas depois que um alteração nos dados é realizada;
  • Replicação MultiMaster: onde é possível realizar leitura e gravação em qualquer réplica;
  • Replicação Master/Slave: onde apenas a réplica master permite gravação, enquanto as demais réplicas só permitem leitura;

Vejamos algumas diferenças entre replicação e cluster:

A replicação pode ser parcial ou total (em relação aos objetos do banco de dados), enquanto o cluster é sempre total em relação a uma instância do banco de dados (no caso do PostgreSQL seria em relação a um cluster criado pelo initdb).

  • A replicação pode ocorrer em apenas uma parte dos dados de cada réplica enquanto no cluster toda a instância (datafiles + processos no servidor) do SGDB de cada nó deve fazer parte do cluster;
  • A replicação pode ser assincrona ou síncrona enquanto o cluster sempre trabalha de forma síncrona.
  • A replicação pode ser MultiMaster ou Master/Slave, e no Cluster todos os nós podem realizar operações de leitura e escrita (o equivalente a um MultiMaster).
  • Técnicas de replicação, em geral não são boas para aumentar a escalabilidade horizontal de bancos de dados com alto volume de gravação (OLTP) enquanto os clusters shared all tem esta capacidade;
  • A replicação guarda uma cópia dos dados replicados em cada instância do banco de dados enquanto o cluster mantém apenas uma cópia dos dados compartilhado para todos os nós do cluster. Note que, em algumas implementações de clusters, técnicas de replicação são combinadas para se evitar o Storage como ponto vulnerável no caso de tolerância a falha.

Em princípio, parece que uma uma replicação MultiMaster síncrona é equivalente a um Cluster Shared Nothing. No entanto, note que um Cluster Shared Nothing cada nó possui apenas uma fatia do banco de dados em seu storage enquanto na replicação MultiMaster síncrona, cada nó possui uma cópia completa dos dados. Cada técnica de cluster e replicação tem diversos tipos de implementações servindo para diferentes propósitos e com diferentes limitações. Saber distinguir cada um deles é fundamental para saber por onde começar a procurar por uma solução adequada para cada tipo de problema, seja ele de alta disponibilidade, balanceamento de carga ou de bancos de dados distribuídos.

O PostgreSQL conta hoje com várias soluções de Replicação Assíncrona Master/Slave que vai do Stand By (implementado no PostgreSQL padrão), Slony I e outros. O PgPool I e II é não são exatamente soluções de replicação e sim de pool de conexões com suporte a um tipo de replicação síncrona. A única solução de replicação MultiMaster Síncrona existente hoje é o PGCluster. O projeto do Slony II pretendia ser uma nova implementação deste tipo mas foi abandonado devida a alta complexidade do projeto. O PGCluster II pretende ser uma implementação de Cluster Shared All (no mesmo estilo do Oracle RAC), mas o projeto ainda é muito recente para se saber se ele será bem sucedido.

Para saber mais veja:

Day Triper

Amanhã cedo parto para Brasília para falar um pouco mais sobre PostgreSQL, agora abordando a parte de Cluster. Agora estou finalmente me preparando para a o que virá: revisando a apresentação, checando passagens, telefones de contato, etc.

Mas para chegar aqui tive que passar por uma verdadeira maratona no trabalho. Estou com muito sono, pois dormi pouco nos últimos dias. Atividades intensas nos sistemas daqui. Vou com uma pulga atrás da orelha. Espero que dê tudo certo por aqui. Por um momento achei que não ia poder viajar pois quase não dou conta do serviço. Mas hoje a tarde fiquei mais tranqüilo e até deu tempo de escrever alguma coisa aqui. Agora é só passar alguns procedimentos para o fike que vai segurar a barra por aqui e vou embora fazer as malas.

Muita correria. Hoje é aniversário de 3 anos do Pedro meu filho. Espero que dê tudo certo em Brasíla e espero voltar logo para poder comemorar o seu aniversário no sábado.