antonio leandro

sistemas distribuídos

Megastore: Providing Scalable, Highly Available Storage for Interactive Services

paper · Jason Baker, Chris Bond, James C. Corbett, et al. · · ~45 min de leitura do original

a tese

dá para ter acid de verdade com replicação síncrona entre datacenters, desde que você aceite quebrar o banco em milhões de bancos minúsculos e só prometer transação dentro de cada pedaço

o que fica

  1. A unidade de consistência não é a linha nem a tabela: é a entity group, uma partição desenhada pela aplicação — uma conta de email, um calendário, um quadrado do mapa — e cada uma carrega seu próprio log replicado.
  2. Cada entity group suporta poucas escritas por segundo; o controle de concorrência é otimista e a latência da replicação síncrona aumenta a chance de conflito, então throughput só cresce fatiando mais fino.
  3. Paxos aqui não replica só metadado ou eleição de master: cada escrita de usuário paga uma rodada de consenso entre datacenters, e a média de commit fica entre 100 e 400 milissegundos.
  4. A leitura local rápida vem de um serviço fora do Paxos, o Coordinator, e cobra o preço de separar o commit point do visibility point: a escrita só é reconhecida depois de invalidar o coordinator de toda réplica que não aceitou.
  5. Tolerância a falha é mascaramento de falha: a resiliência esconde problemas persistentes até que um erro transitório em cima deles vire um problema grande.
  6. A ferramenta que mais achou bugs não foram os testes unitários, e sim um simulador de rede pseudoaleatório que explora ordenações de mensagens e reproduz a mesma falha dado o mesmo seed.

o problema

Em 2010 quem ia construir um serviço interativo grande escolhia entre dois lados ruins. De um lado, o RDBMS: schema, índice secundário, transação, o idioma read-modify-write que todo desenvolvedor já tem no dedo. Só que escalar isso para centenas de milhões de usuários significa redesenhar a infraestrutura inteira, e nenhum deles oferecia replicação síncrona tolerante a falha. Do outro lado, os datastores NoSQL — Bigtable, HBase, Cassandra — que escalam, mas cujo escopo transacional costuma terminar no acesso a uma única chave, e cuja consistência frouxa empurra a complexidade para dentro da aplicação.

Havia um segundo problema empilhado no primeiro: replicar entre datacenters distantes. Master/slave assíncrono confirma rápido e arrisca perder dados no failover. Master/slave síncrono não perde dados, mas depende de alguém de fora detectar a queda a tempo, e o failover é uma sequência de estágios lentos que o usuário enxerga. Replicação otimista tem latência ótima e não sabe a ordem global das mutações no momento do commit, o que significa não ter transação. E a sabedoria da época dizia que Paxos entre datacenters era lento demais para tráfego interativo.

a ideia

O Megastore ocupa o meio: particiona o banco e replica cada partição por conta própria. ACID serializável completo dentro da partição, garantias fracas entre partições. A partição se chama entity group e quem desenha a fronteira é a aplicação — cada conta de email é um grupo, cada perfil de blog é um grupo, cada retalho do mapa é um grupo.

O ponto não é evitar o Paxos, é evitar um log só. Um único log replicado entre continentes tem throughput limitado pela latência e transforma qualquer indisponibilidade em incidente de escopo global. Milhões de logs pequenos, um por entity group, dão o oposto: falha localizada e throughput agregado que cresce com o número de grupos. O preço é que operação entre grupos precisa de fila assíncrona transacional ou de two-phase commit — e os autores desencorajam o segundo.

como funciona

Cada datacenter guarda os dados num Bigtable. Uma entidade vira uma linha, as colunas da chave primária são concatenadas no row key, e a diretiva IN TABLE coloca as filhas adjacentes à raiz para que a hierarquia seja lida em varredura contígua. O log de transação e os metadados de replicação do grupo moram na linha da entidade raiz, o que permite atualizá-los com uma transação do Bigtable. Os múltiplos timestamps por célula do Bigtable dão MVCC: leitor e escritor não se bloqueiam.

A transação é read, lógica de aplicação, commit, apply, cleanup. O read descobre a próxima posição livre do log; o commit tenta ganhar aquela posição via Paxos. Concorrência otimista: vários escritores podem mirar a mesma posição, um ganha e os outros abortam e retentam.

Duas otimizações fazem isso caber no orçamento de latência. Escrita: roda-se uma instância independente de Paxos por posição do log, e o líder da posição n+1 é escolhido junto com o valor da posição n, pela heurística de réplica mais próxima do escritor. Quem chega primeiro no líder usa proposal number zero e pula a fase de prepare — uma rodada só. Quem perde cai no Paxos de duas fases.

Leitura: um serviço chamado Coordinator, um por datacenter, mantém o conjunto de entity groups para os quais a réplica local viu todas as escritas. Se o grupo está no conjunto, a leitura current é local, sem RPC entre réplicas. Para isso valer, a escrita precisa invalidar o coordinator de toda full replica que não aceitou o valor — por isso o commit point (ganhar o Paxos) vem antes do visibility point (terminar a invalidação). Coordinators detectam partição segurando locks do Chubby: perdeu a maioria dos locks, declara tudo desatualizado. Há ainda witness replicas, que votam e guardam log sem aplicar, e read-only replicas, que guardam snapshot sem votar.

o que isso custou

O teto de escrita por entity group é de poucas escritas por segundo, e os próprios autores dizem isso sem rodeio: quem precisa de mais, fatia mais fino, agrupa réplicas na mesma região ou batcha via fila. Commit médio de 100 a 400 milissegundos, contra dezenas de milissegundos na leitura.

Fora da partição, as promessas caem. Índice global pode não refletir atualizações recentes. Join não existe: a aplicação implementa o merge join com queries paralelas, e mudar o schema obriga a mexer no código da query — atrito deliberado, para que ninguém escreva um nested loop sem perceber.

O coordinator, que compra a leitura local, cobra em disponibilidade: quando um datacenter com coordinators vivos some, os escritores esperam os locks do Chubby expirarem, e há uma janela de dezenas de segundos sem escrita. Partição assimétrica — o coordinator mantém os locks mas perde contato com os proponentes — exige um operador desabilitá-lo à mão. Aconteceu poucas vezes, mas está no paper.

O tuning do Bigtable fica exposto: block size, compressão, locality group são responsabilidade (e fardo) de quem escreve a aplicação, e query lenta se diagnostica lendo trace do Bigtable. E há o que a operação ensinou: chain gang throttling, quando tolerar o participante lento como se fosse falho faz o sistema inteiro convergir para o ritmo do mais devagar.

onde isso aparece hoje

O App Engine Datastore foi construído sobre o Megastore — o paper descreve aplicações montando um modelo entity-attribute-value dinâmico em cima do schema estático, e cita o App Engine como o caso mais notável. Mais de cem aplicações em produção no Google, mais de 3 bilhões de escritas e 20 bilhões de leituras por dia, quase um petabyte de dados primários.

O limite que o Megastore não venceu — transação e consistência forte fora da partição — é exatamente o problema que o Spanner foi atacar no ano seguinte, pagando com relógio sincronizado o que aqui se pagava com fronteira de entity group; o F1 veio depois em cima dele. E a aposta central, tratar a partição pequena e replicada por consenso como unidade de tudo, reaparece com outro nome em Millions of Tiny Databases.

lido na íntegra por pipeline de llm, revisado por antonio leandro antes de publicar ·