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 gerada antes de T começar a ser gerada. 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, 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
O Spanner oferece aos clientes as garantias de controlo de concorrência mais rigorosas para transações, o que se denomina consistência externa2. Na consistência externa, o sistema comporta-se como se todas as transações fossem executadas sequencialmente, mesmo que 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 exemplo, tal como as bases de dados que suportam o isolamento de instantâneos, o Spanner permite que as escritas prossigam sem serem bloqueadas por transações só de leitura, mas sem apresentar as anomalias que o isolamento de instantâneos permite.
A consistência externa simplifica bastante o desenvolvimento de aplicações. Por exemplo, suponhamos que criou uma aplicação bancária no Spanner e 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 garantem 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.
Datas/horas e controlo de simultaneidade de várias versões (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 (muitas vezes denominado 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 de instantâneo" 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. Denominamos esta propriedade "data/hora adequada". Tenha em atenção que a existência de uma data/hora adequada é 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 transação T2 pode ter uma indicação de data/hora anterior à indicação de data/hora atribuída à transação 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 transação 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). Alcançá-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 indicações de tempo 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?
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 satisfazem esta propriedade de consistência, não apenas as que se encontram numa partição. 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 indicações de tempo 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 indicação de tempo a T2 que é superior à indicação de tempo de T1.
O Spanner oferece linearizabilidade?
Sim. Na verdade, 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. Na verdade, 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 no qual 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 poderia fazer com que o débito incorresse numa penalização devido a fundos insuficientes.
O Spanner oferece consistência forte?
Sim. Na verdade, 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 da réplica que recebe a leitura.
Qual é a diferença entre a consistência forte e a consistência externa?
Um protocolo de replicação apresenta uma "consistência forte" 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 diz 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 em que nunca esteve verdadeiramente (por exemplo, uma leitura pode observar um estado em que a transação B está confirmada, mas a transação A não está, 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 indicação de tempo "antiga", que não pode bloquear escritas porque as versões anteriores dos dados são imutáveis.