Spanner: TrueTime e consistência externa

O TrueTime é um relógio distribuído de elevada disponibilidade que é fornecido às aplicações em todos os servidores Google1. O TrueTime permite que as aplicações gerem datas/horas que aumentam de forma monótona: uma aplicação pode calcular uma data/hora T que tem a garantia de ser superior a qualquer data/hora T', se T' tiver terminado de ser gerado antes de T ter começado a ser gerado. Esta garantia aplica-se a todos os servidores e a todas as datas/horas.

Esta funcionalidade do TrueTime é usada pelo Spanner para atribuir datas/horas às transações. Especificamente, a cada transação é atribuída uma indicação de tempo que reflete o instante em que o Spanner considera que ocorreu. Uma vez que o Spanner usa o controlo de concorrência de várias versões (MVCC), a garantia de ordenação nas datas/horas permite que os clientes do Spanner façam leituras consistentes numa base de dados inteira (mesmo em várias regiões da nuvem) sem bloquear as escritas.

Consistência externa

Quando o nível de isolamento não é especificado ou quando define o nível de isolamento como isolamento serializável, o Spanner oferece aos clientes as garantias de controlo de concorrência mais rigorosas para transações, o que se denomina consistência externa2. No âmbito da consistência externa, o sistema comporta-se como se todas as transações fossem executadas sequencialmente, embora o Spanner as execute em vários servidores (e, possivelmente, em vários centros de dados) para um desempenho e uma disponibilidade mais elevados. Além disso, se uma transação for concluída antes de outra transação começar a ser confirmada, o sistema garante que os clientes nunca podem ver um estado que inclua o efeito da segunda transação, mas não da primeira. Intuitivamente, o Spanner é semanticamente indistinguível de uma base de dados de uma única máquina. Apesar de oferecer garantias tão fortes, o Spanner permite que as aplicações alcancem um desempenho comparável ao das bases de dados que oferecem garantias mais fracas (em troca de um desempenho mais elevado). Por predefinição, o Spanner permite que as escritas avancem sem serem bloqueadas por transações só de leitura, mas sem apresentar as anomalias que o isolamento de leitura repetível permite.

Por outro lado, o isolamento de leitura repetível garante que todas as operações de leitura numa transação veem uma imagem consistente da base de dados tal como existia no início da transação. Esta abordagem é vantajosa em cenários de concorrência de leitura/escrita elevada, em que várias transações leem dados que outras transações podem estar a modificar. Para mais informações, consulte o artigo sobre o isolamento de leitura repetível.

A consistência externa simplifica bastante o desenvolvimento de aplicações. Por exemplo, suponhamos que criou uma aplicação bancária no Spanner e que um dos seus clientes começa com 50 € na conta corrente e 50 € na conta de poupança. A sua aplicação inicia então um fluxo de trabalho no qual primeiro confirma uma transação T1 para depositar 200 € na conta de poupança e, em seguida, emite uma segunda transação T2 para debitar 150 € da conta corrente. Além disso, suponha que, no final do dia, os saldos negativos numa conta são cobertos automaticamente por outras contas e que um cliente incorre numa penalização se o saldo total em todas as respetivas contas for negativo em qualquer altura durante esse dia. As garantias de consistência externa asseguram que, uma vez que T2 começa a confirmar após T1 terminar, todos os leitores da base de dados vão observar que o depósito T1 ocorreu antes do débito T2. Por outras palavras, a consistência externa garante que ninguém verá um estado em que T2 ocorre antes de T1; por outras palavras, o débito nunca incorre numa penalização devido a fundos insuficientes.

Uma base de dados tradicional que usa o armazenamento de versão única e o bloqueio rigoroso de duas fases oferece consistência externa. Infelizmente, num sistema deste tipo, sempre que a sua aplicação quer ler os dados mais atuais (o que chamamos de "leitura forte"), o sistema adquire um bloqueio de leitura nos dados, o que bloqueia as escritas nos dados que estão a ser lidos.

Indicações de tempo e MVCC

Para ler sem bloquear as escritas, o Spanner e muitos outros sistemas de base de dados mantêm várias versões imutáveis dos dados (frequentemente denominadas controlo de concorrência de várias versões). Uma gravação cria uma nova versão imutável cuja data/hora é a da transação de gravação. Uma "leitura instantânea" numa data/hora devolve o valor da versão mais recente anterior a essa data/hora e não precisa de bloquear as escritas. Por conseguinte, é importante que as datas/horas atribuídas às versões sejam consistentes com a ordem em que as transações podem ser observadas para confirmação. Chamamos a esta propriedade "data/hora adequada", que é equivalente à consistência externa.

Para ver por que motivo a indicação de data/hora adequada é importante, considere o exemplo bancário da secção anterior. Sem a indicação de data/hora adequada, a T2 pode ser-lhe atribuída uma indicação de data/hora anterior à indicação de data/hora atribuída à T1 (por exemplo, se um sistema hipotético usasse relógios locais em vez do TrueTime e o relógio do servidor que processa a T2 estivesse ligeiramente atrasado). Uma leitura instantânea pode refletir o débito de T2, mas não o depósito de T1, mesmo que o cliente tenha visto o depósito terminar antes de iniciar o débito.

A obtenção da data/hora correta é trivial para uma base de dados de uma única máquina (por exemplo, pode simplesmente atribuir datas/horas a partir de um contador global que aumenta monotonicamente). Consegui-lo num sistema amplamente distribuído, como o Spanner, em que os servidores de todo o mundo têm de atribuir datas/horas, é muito mais difícil de fazer de forma eficiente.

O Spanner depende do TrueTime para gerar carimbos de data/hora que aumentam monotonicamente. O Spanner usa estas datas/horas de duas formas. Primeiro, usa-os como indicações de tempo adequadas para transações de escrita sem necessidade de comunicação global. Em segundo lugar, usa-os como data/hora para leituras fortes, o que permite que as leituras fortes sejam executadas numa ronda de comunicação, mesmo leituras fortes que abrangem vários servidores.

Perguntas frequentes

Que garantias de consistência oferece o Spanner?

Por predefinição, o Spanner oferece consistência externa, que é a propriedade de consistência mais rigorosa para sistemas de processamento de transações. Todas as transações no Spanner que usam o isolamento de serialização satisfazem esta propriedade de consistência, não apenas as que estão numa partição. Para mais informações, consulte o artigo Vista geral dos níveis de isolamento.

A consistência externa afirma que o Spanner executa transações de uma forma indistinguível de um sistema no qual as transações são executadas em série e, além disso, que a ordem em série é consistente com a ordem em que as transações podem ser observadas para confirmação. Uma vez que as datas/horas geradas para as transações correspondem à ordem de série, se algum cliente vir uma transação T2 começar a ser confirmada depois de outra transação T1 terminar, o sistema atribui uma data/hora a T2 superior à data/hora de T1.

O Spanner oferece linearizabilidade?

Sim. Por predefinição, o Spanner oferece consistência externa, que é uma propriedade mais forte do que a linearizabilidade, porque a linearizabilidade não diz nada sobre o comportamento das transações. A linearizabilidade é uma propriedade de objetos concorrentes que suportam operações de leitura e escrita atómicas. Numa base de dados, um "objeto" seria normalmente uma única linha ou até uma única célula. A consistência externa é uma propriedade dos sistemas de processamento de transações, em que os clientes sintetizam dinamicamente transações que contêm várias operações de leitura e escrita em objetos arbitrários. A linearizabilidade pode ser vista como um caso especial de consistência externa, em que uma transação só pode conter uma única operação de leitura ou escrita num único objeto.

O Spanner oferece serialização?

Sim. Por predefinição, o Spanner oferece consistência externa, que é uma propriedade mais rigorosa do que a serialização. Um sistema de processamento de transações é serializável se executar transações de uma forma indistinguível de um sistema em que as transações são executadas em série. O Spanner também garante que a ordem serial é consistente com a ordem em que as transações podem ser observadas para serem confirmadas.

Considere novamente o exemplo bancário usado anteriormente. Num sistema que oferece serialização, mas não consistência externa, mesmo que o cliente tenha executado T1 e, em seguida, T2 sequencialmente, o sistema teria permissão para reordená-los, o que pode fazer com que o débito incorra numa penalização devido a fundos insuficientes.

O Spanner oferece consistência forte?

Sim. O Spanner oferece consistência externa, que é uma propriedade mais forte do que a consistência forte. O modo predefinido para leituras no Spanner é "forte", o que garante que observam os efeitos de todas as transações que foram confirmadas antes do início da operação, independentemente de qual réplica recebe a leitura.

Qual é a diferença entre a consistência forte e a consistência externa?

Um protocolo de replicação apresenta uma forte consistência se os objetos replicados forem linearizáveis. Tal como a linearizabilidade, a consistência forte é mais fraca do que a consistência externa, porque não impõe nada sobre o comportamento das transações.

O Spanner oferece consistência eventual (ou tardia)?

O Spanner oferece consistência externa, que é uma propriedade muito mais forte do que a consistência eventual. A consistência eventual troca garantias mais fracas por um desempenho mais elevado. A consistência eventual é problemática porque significa que os leitores podem observar a base de dados num estado que nunca existiu (por exemplo, uma leitura pode observar um estado em que a transação B é confirmada, mas a transação A não, apesar de A ter ocorrido antes de B).

O Spanner oferece leituras desatualizadas, que oferecem vantagens de desempenho semelhantes à consistência eventual, mas com garantias de consistência muito mais fortes. Uma leitura desatualizada devolve dados de uma data/hora anterior, que não pode bloquear escritas porque as versões anteriores dos dados são imutáveis.

O que se segue?

Notas

  • 1J. C. Corbett, J. Dean, M. Epstein, A. Fikes, C. Frost, J. Furman, S. Ghemawat, A. Gubarev, C. Heiser, P. Hochschild, W. Hsieh, S. Kanthak, E. Kogan, H. Li, A. Lloyd, S. Melnik, D. Mwaura, D. Nagle, S. Quinlan, R. Rao, L. Rolig, Y. Saito, M. Szymaniak, C. Taylor, R. Wang e D. Woodford. Spanner: base de dados distribuída globalmente da Google. In Tenth USENIX Symposium on Operating Systems Design and Implementation (OSDI 12), pp. 261–264, Hollywood, CA, Oct. 2012.
  • 2Gifford, D. K. Armazenamento de informações num sistema informático descentralizado. PhD thesis, Stanford University, 1981.