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.