antonio leandro

sistemas distribuídos

Spanner: Google's Globally-Distributed Database

paper · núcleo · James C. Corbett, Jeffrey Dean, Michael Epstein, et al. · · ~47 min de leitura do original · densa

a tese

expor a incerteza do relógio em vez de escondê-la: se o banco sabe quanto pode estar errado sobre a hora, ele espera essa janela passar e entrega transação linearizável em escala planetária

o que fica

  1. TrueTime não devolve um instante, devolve um intervalo garantido a conter o instante — e é essa honestidade sobre o erro que sustenta todo o resto do sistema.
  2. O preço da consistência externa é esperar de propósito: o coordenador segura o commit até ter certeza de que o timestamp já ficou no passado, uma espera esperada de 2ε que nos testes deu cerca de 5 ms.
  3. O ε de 1 a 7 ms não sai de algoritmo, sai de hardware: receptores GPS e relógios atômicos em cada datacenter, com daemons descartando relógios mentirosos.
  4. Transação só-leitura não pega lock nenhum: escolhe um timestamp e lê de qualquer réplica suficientemente atualizada, então leitor nunca bloqueia escritor nem vice-versa.
  5. O lease de líder Paxos de 10 segundos define o pior caso de indisponibilidade — matar a zona dos líderes sem aviso derruba a vazão a quase zero até os leases expirarem.
  6. O Google não eliminou o two-phase commit, colocou ele em cima do Paxos e assumiu que é melhor o programador lidar com transação lenta do que programar em volta da ausência de transação.

o problema

Quem precisava guardar dado replicado entre datacenters do Google em 2011 escolhia entre duas derrotas. Bigtable escalava, mas replicava entre datacenters só com consistência eventual e não tinha transação entre linhas — o paper registra que a falta de transação cross-row gerava reclamação constante e que o Percolator nasceu em parte para tapar esse buraco. Megastore tinha o que as pessoas queriam — tabelas semi-relacionais e replicação síncrona — e por isso mais de 300 aplicações internas o usavam, Gmail e Calendar entre elas, apesar da vazão de escrita ruim.

O caso que forçou a decisão foi o backend de anúncios, um MySQL shardado à mão. O último resharding tinha levado mais de dois anos de esforço intenso, com coordenação entre dezenas de times para reduzir risco. Como a operação era cara demais para repetir, o time passou a empurrar dado para Bigtables externos, o que quebrava transação e impedia consulta sobre todo o conjunto. Por baixo disso tudo havia um problema mais antigo: ordenar eventos entre continentes. Se duas transações commitam em datacenters diferentes, qual aconteceu antes? Relógios de parede discordam, e ninguém sabe por quanto.

a ideia

A resposta usual é abandonar o relógio e ordenar por causalidade, do jeito que Lamport propôs. O Spanner faz o contrário: usa relógio de verdade, mas para de fingir que ele é exato. TT.now() não devolve um número, devolve [earliest, latest], um intervalo com a garantia de conter o instante absoluto da chamada.

Com isso a incerteza vira algo que dá para gastar. Se você não sabe se agora é 10:00:00,003 ou 10:00:00,010, escolha o extremo mais tardio como timestamp do commit e depois espere até ter certeza de que aquele instante já passou. A janela de dúvida deixa de ser fonte de bug e vira latência — uma latência pequena, e que encolhe conforme o hardware de relógio melhora.

como funciona

Os dados são shardados em grupos Paxos, um por tablet, com líderes de vida longa mantidos por lease de 10 segundos por padrão. Escrita passa pelo líder; leitura pode sair de qualquer réplica atualizada. O líder mantém uma lock table com two-phase locking, e transações que tocam vários grupos rodam two-phase commit entre os líderes, com o cliente dirigindo o protocolo para não mandar os dados duas vezes pela WAN.

A consistência externa vem de duas regras. Start: o coordenador escolhe um commit timestamp s não menor que TT.now().latest, calculado depois de receber o pedido de commit. Commit wait: ninguém enxerga o resultado até TT.after(s) ser verdadeiro. Juntas, garantem que se T1 commitou antes de T2 começar, o timestamp de T1 é menor. A espera esperada é 2ε e costuma ser sobreposta à comunicação do Paxos.

Leitura é o outro lado. Cada réplica mantém um tsafe — o maior timestamp em que está atualizada — e serve snapshot read de qualquer coisa abaixo dele, sem lock. Transação só-leitura só precisa de um timestamp; num grupo único, o líder usa o timestamp da última escrita commitada em vez de TT.now().latest, o que evita esperar. Mudança de schema ganha um timestamp no futuro: operações anteriores a ele passam, posteriores bloqueiam, e milhares de servidores trocam de schema atomicamente sem parar.

TrueTime em si é hardware. Cada datacenter tem time masters, a maioria com receptor GPS e antena dedicada, o resto com relógios atômicos — os autores os chamam de Armageddon masters, e custam da mesma ordem que os de GPS. Cada máquina roda um daemon que consulta vários masters, aplica uma variante do algoritmo de Marzullo para rejeitar mentirosos e se sincroniza com o resto. Entre sincronizações, o ε cresce conforme o drift de pior caso, 200 microssegundos por segundo, com poll a cada 30 segundos: um serrote de 1 a 7 ms, média de 4 ms.

o que isso custou

Nos microbenchmarks, o commit wait custa cerca de 5 ms e o Paxos cerca de 9 ms. Medido de dentro do F1 em produção, o commit num único grupo dá 72,3 ms de média e o multi-grupo, 103,0 ms; leituras dão 8,7 ms de média com desvio padrão de 376,4 ms, uma cauda que os próprios autores atribuem a conflito de lock e a réplicas em datacenters com hardware diferente. O 2PC escala até uns 50 participantes com dignidade e começa a doer em 100.

Disponibilidade tem um número explícito: matar a zona dos líderes sem aviso zera a vazão até os leases de 10 segundos expirarem e novos líderes serem eleitos. Com handoff avisado, a queda fica em 3% a 4%.

Os autores listam o que não resolveram. As estruturas de dados locais de cada nó foram feitas para acesso chave-valor e vão mal em SQL complexo. Índice secundário automático não existe — o time do F1 teve que construir os seus com transações. Não há reconfiguração dentro do Paxos, então mover réplica é tarefa de background. Cada escrita Paxos é logada duas vezes, por conveniência que eles pretendem consertar. E num grupo ocioso, sem escritas, uma snapshot read pode ficar até 8 segundos atrás no pior caso.

onde isso aparece hoje

O primeiro cliente foi o F1, e o par Spanner+F1 virou o modelo do que hoje se chama SQL distribuído: consenso por shard, transações sobre replicação, escala horizontal sem resharding manual. O TiDB descende declaradamente dessa arquitetura, com Raft no lugar do Paxos. O Spanner virou produto público no Google Cloud.

O efeito mais duradouro é retórico. A leitura corrente de que consistência forte em escala ampla é impraticável, formalizada em Brewer’s Conjecture, passou a ter um contraexemplo funcionando em produção — ao preço de GPS e relógios atômicos em cada datacenter, que quase ninguém tem. A frase que os autores deixam na conclusão é um recado de projeto: parar de depender de relógios frouxamente sincronizados e de APIs de tempo que escondem o erro.

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