API Datastore do App Engine para serviços agrupados antigos

Nota: os programadores que criam novas aplicações são fortemente aconselhados a usar a biblioteca de cliente NDB, que tem várias vantagens em comparação com esta biblioteca de cliente, como o armazenamento em cache automático de entidades através da API Memcache. Se estiver a usar atualmente a biblioteca cliente DB mais antiga, leia o guia de migração de DB para NDB

Este documento descreve o modelo de dados para objetos armazenados no Datastore, como as consultas são estruturadas através da API e como as transações são processadas.

Entidades

Os objetos no Datastore são conhecidos como entidades. Uma entidade tem uma ou mais propriedades com nome, cada uma das quais pode ter um ou mais valores. Os valores das propriedades podem pertencer a uma variedade de tipos de dados, incluindo números inteiros, números de vírgula flutuante, strings, datas e dados binários, entre outros. Uma consulta numa propriedade com vários valores testa se algum dos valores cumpre os critérios da consulta. Isto torna essas propriedades úteis para testes de associação.

Tipos, chaves e identificadores

Cada entidade do Datastore é de um tipo específico,que categoriza a entidade para fins de consultas. Por exemplo, uma aplicação de recursos humanos pode representar cada funcionário de uma empresa com uma entidade do tipo Employee. Além disso, cada entidade tem a sua própria chave, que a identifica de forma exclusiva. A chave é composta pelos seguintes componentes:

  • O tipo da entidade
  • Um identificador, que pode ser
    • Uma string de nome da chave
    • um ID inteiro
  • Um caminho do antepassado opcional que localiza a entidade na hierarquia do Datastore

O identificador é atribuído quando a entidade é criada. Uma vez que faz parte da chave da entidade, está associado permanentemente à entidade e não pode ser alterado. Pode ser atribuído de duas formas:

  • A sua aplicação pode especificar a sua própria string de nome da chave para a entidade.
  • Pode fazer com que o Datastore atribua automaticamente à entidade um ID numérico inteiro.

Caminhos de antecessores

As entidades no Cloud Datastore formam um espaço estruturado hierarquicamente semelhante à estrutura de diretórios de um sistema de ficheiros. Quando cria uma entidade, pode, opcionalmente, designar outra entidade como respetiva principal. A nova entidade é uma secundária da entidade principal (tenha em atenção que, ao contrário de um sistema de ficheiros, a entidade principal não tem de existir efetivamente). Uma entidade sem um elemento principal é uma entidade raiz. A associação entre uma entidade e a respetiva entidade principal é permanente e não pode ser alterada depois de a entidade ser criada. O Cloud Datastore nunca atribui o mesmo ID numérico a duas entidades com o mesmo elemento principal ou a duas entidades raiz (as que não têm um elemento principal).

O principal de uma entidade, o principal do principal e assim sucessivamente, são os seus ancestrais; os seus secundários, os secundários dos secundários e assim sucessivamente, são os seus descendentes. Uma entidade raiz e todos os respetivos descendentes pertencem ao mesmo grupo de entidades. A sequência de entidades que começa com uma entidade de raiz e prossegue do elemento principal para o elemento secundário, conduzindo a uma determinada entidade, constitui o caminho do antepassado dessa entidade. A chave completa que identifica a entidade consiste numa sequência de pares de tipo-identificador que especificam o respetivo caminho de antepassados e terminam com os da própria entidade:

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

Para uma entidade raiz, o caminho de antepassados está vazio e a chave consiste apenas no tipo e no identificador da própria entidade:

[Person:GreatGrandpa]

Este conceito é ilustrado pelo diagrama seguinte:

Mostra a relação da entidade raiz com as entidades
  secundárias no grupo de entidades

Consultas e índices

Além de obter entidades do Datastore diretamente pelas respetivas chaves, uma aplicação pode executar uma consulta para as obter pelos valores das respetivas propriedades. A consulta opera em entidades de um determinado tipo; pode especificar filtros nos valores das propriedades, chaves e antecessores das entidades, e pode devolver zero ou mais entidades como resultados. Uma consulta também pode especificar ordens de ordenação para sequenciar os resultados pelos respetivos valores das propriedades. Os resultados incluem todas as entidades que têm, pelo menos, um valor (possivelmente nulo) para cada propriedade com nome nos filtros e nas ordens de ordenação, e cujos valores das propriedades cumprem todos os critérios de filtro especificados. A consulta pode devolver entidades completas, entidades projetadas, ou apenas chaves de entidades.

Uma consulta típica inclui o seguinte:

Quando executada, a consulta obtém todas as entidades do tipo indicado que satisfazem todos os filtros indicados, ordenados pela ordem especificada. As consultas são executadas como só de leitura.

Nota: para conservar a memória e melhorar o desempenho, uma consulta deve, sempre que possível, especificar um limite para o número de resultados devolvidos.

Uma consulta também pode incluir um filtro de antepassado que limita os resultados apenas ao grupo de entidades descendente de um antepassado especificado. Uma consulta deste tipo é conhecida como uma consulta de antepassados. Por predefinição, as consultas de antecessores devolvem resultados fortemente consistentes, que têm a garantia de estar atualizados com as alterações mais recentes aos dados. Por outro lado, as consultas não antecessoras podem abranger todo o Datastore em vez de apenas um único grupo de entidades, mas são apenas eventualmente consistentes e podem devolver resultados desatualizados. Se a consistência forte for importante para a sua aplicação, pode ter de ter isto em conta ao estruturar os seus dados, colocando entidades relacionadas no mesmo grupo de entidades para que possam ser obtidas com um antepassado em vez de uma consulta não antepassada. Consulte o artigo Estruturar dados para uma consistência forte para mais informações.

O App Engine predefine um índice simples em cada propriedade de uma entidade. Uma aplicação do App Engine pode definir mais índices personalizados num ficheiro de configuração de índices denominado index.yaml. O servidor de programação adiciona automaticamente sugestões a este ficheiro à medida que encontra consultas que não podem ser executadas com os índices existentes. Pode ajustar os índices manualmente editando o ficheiro antes de carregar a aplicação.

Nota: o mecanismo de consulta baseado em índice suporta uma vasta gama de consultas e é adequado para a maioria das aplicações. No entanto, não suporta alguns tipos de consultas comuns noutras tecnologias de bases de dados. Em particular, as junções e as consultas agregadas não são suportadas no motor de consultas do Datastore. Consulte a página Consultas do Datastore para ver as limitações das consultas do Datastore.

Transações

Todas as tentativas de inserir, atualizar ou eliminar uma entidade ocorrem no contexto de uma transação. Uma única transação pode incluir qualquer número dessas operações. Para manter a consistência dos dados, a transação garante que todas as operações que contém são aplicadas ao Datastore como uma unidade ou, se alguma das operações falhar, que nenhuma delas é aplicada.

Pode realizar várias ações numa entidade numa única transação. Por exemplo, para incrementar um campo de contador num objeto, tem de ler o valor do contador, calcular o novo valor e, em seguida, armazená-lo novamente. Sem uma transação, é possível que outro processo incremente o contador entre o momento em que lê o valor e o momento em que o atualiza, o que faz com que a sua aplicação substitua o valor atualizado. A leitura, o cálculo e a escrita numa única transação garantem que nenhum outro processo pode interferir no incremento.

Transações e grupos de entidades

Só são permitidas consultas de antepassados numa transação, ou seja, cada consulta transacional tem de se limitar a um único grupo de entidades. A transação em si pode aplicar-se a várias entidades, que podem pertencer a um único grupo de entidades ou (no caso de uma transação entre grupos) a até vinte e cinco grupos de entidades diferentes.

O Datastore usa a concorrência otimista para gerir as transações. Quando duas ou mais transações tentam alterar o mesmo grupo de entidades em simultâneo (atualizando entidades existentes ou criando novas), a primeira transação a ser confirmada é bem-sucedida e todas as outras falham na confirmação. Em seguida, é possível tentar novamente estas outras transações com os dados atualizados. Tenha em atenção que isto limita o número de gravações simultâneas que pode fazer em qualquer entidade num determinado grupo de entidades.

Transações entre grupos

Uma transação em entidades pertencentes a diferentes grupos de entidades é denominada transação entre grupos (XG). A transação pode ser aplicada a um máximo de vinte e cinco grupos de entidades e tem êxito desde que nenhuma transação simultânea toque em nenhum dos grupos de entidades aos quais se aplica. Isto dá-lhe mais flexibilidade na organização dos seus dados, porque não é obrigado a colocar partes de dados díspares sob o mesmo antepassado apenas para executar escritas atómicas nos mesmos.

Tal como numa transação de grupo único, não pode executar uma consulta de não antepassado numa transação XG. No entanto, pode executar consultas de antepassados em grupos de entidades separados. As consultas não transacionais (não antecessoras) podem ver todos, alguns ou nenhum dos resultados de uma transação comprometida anteriormente. (Para obter informações gerais sobre este problema, consulte o artigo Gravações no Datastore e visibilidade dos dados.) No entanto, é mais provável que essas consultas não transacionais vejam os resultados de uma transação XG parcialmente comprometida do que os de uma transação de grupo único parcialmente comprometida.

Uma transação XG que afeta apenas um único grupo de entidades tem exatamente o mesmo desempenho e custo que uma transação não XG de um único grupo. Numa transação XG que afeta vários grupos de entidades, as operações custam o mesmo que se fossem realizadas numa transação não XG, mas podem sofrer uma latência mais elevada.

Escritas na base de dados e visibilidade dos dados

Os dados são escritos no Armazenamento de dados em duas fases:

  1. Na fase de confirmação, os dados da entidade são registados nos registos de transações de uma maioria das réplicas, e todas as réplicas nas quais não foram registados são marcadas como não tendo registos atualizados.
  2. A fase de aplicação ocorre de forma independente em cada réplica e consiste em duas ações realizadas em paralelo:
    • Os dados da entidade são escritos nessa réplica.
    • As linhas de índice da entidade são escritas nessa réplica. (Tenha em atenção que esta ação pode demorar mais tempo do que a escrita dos dados propriamente dita.)

A operação de escrita é devolvida imediatamente após a fase de confirmação e, em seguida, a fase de aplicação ocorre de forma assíncrona, possivelmente em momentos diferentes em cada réplica e possivelmente com atrasos de algumas centenas de milissegundos ou mais desde a conclusão da fase de confirmação. Se ocorrer uma falha durante a fase de confirmação, são feitas novas tentativas automáticas. No entanto, se as falhas continuarem, o Datastore devolve uma mensagem de erro que a sua aplicação recebe como uma exceção. Se a fase de confirmação for bem-sucedida, mas a fase de aplicação falhar numa réplica específica, a fase de aplicação é avançada até à conclusão nessa réplica quando ocorre uma das seguintes situações:

  • As limpezas periódicas do Datastore verificam se existem tarefas de confirmação não concluídas e aplicam-nas.
  • Determinadas operações (get, put, delete e consultas de antecessores) que usam o grupo de entidades afetado fazem com que todas as alterações que foram confirmadas, mas ainda não aplicadas, sejam concluídas na réplica em que estão a ser executadas antes de avançar com a nova operação.

Este comportamento de escrita pode ter várias implicações sobre como e quando os dados ficam visíveis para a sua aplicação em diferentes partes das fases de confirmação e aplicação:

  • Se uma operação de escrita comunicar um erro de limite de tempo, não é possível determinar (sem tentar ler os dados) se a operação foi bem-sucedida ou falhou.
  • Uma vez que as consultas de obtenção e de antepassados do Datastore aplicam quaisquer modificações pendentes à réplica na qual estão a ser executadas, estas operações veem sempre uma vista consistente de todas as transações bem-sucedidas anteriores. Isto significa que uma operação get (procurar uma entidade atualizada pela respetiva chave) tem a garantia de ver a versão mais recente dessa entidade.
  • As consultas não de antecessores podem devolver resultados desatualizados porque podem estar a ser executadas numa réplica na qual as transações mais recentes ainda não foram aplicadas. Isto pode ocorrer mesmo que tenha sido realizada uma operação que garante a aplicação de transações pendentes, porque a consulta pode ser executada numa réplica diferente da operação anterior.
  • A sincronização das alterações simultâneas pode afetar os resultados das consultas não descendentes. Se uma entidade satisfizer inicialmente uma consulta, mas for alterada posteriormente de modo a deixar de o fazer, a entidade pode continuar a ser incluída no conjunto de resultados da consulta se as alterações ainda não tiverem sido aplicadas aos índices na réplica na qual a consulta foi executada.

Estatísticas do Datastore

O Datastore mantém estatísticas sobre os dados armazenados para uma aplicação, como o número de entidades de um determinado tipo ou o espaço usado pelos valores das propriedades de um determinado tipo. Pode ver estas estatísticas na página do Google Cloud painel de controlo Datastore. Também pode usar a API Datastore para aceder a estes valores programaticamente a partir da aplicação consultando entidades com nomes especiais. Consulte as estatísticas do Datastore no Python 2 para mais informações.

Exemplo do Armazenamento de dados do Python 2

Na API Python, um modelo descreve um tipo de entidade, incluindo os tipos e a configuração das respetivas propriedades. Uma aplicação define um modelo através de uma classe Python, com atributos de classe que descrevem as propriedades. O nome da classe torna-se o nome do tipo de entidade. As entidades do tipo indicado são representadas por instâncias da classe do modelo, com atributos de instância que representam os valores das propriedades. Para criar uma nova entidade, cria um objeto da classe pretendida, define os respetivos atributos e, em seguida, guarda-o (chamando um método como put()):

import datetime
from google.appengine.ext import db
from google.appengine.api import users


class Employee(db.Model):
  name = db.StringProperty(required=True)
  role = db.StringProperty(required=True,
                           choices=set(["executive", "manager", "producer"]))
  hire_date = db.DateProperty()
  new_hire_training_completed = db.BooleanProperty(indexed=False)
  email = db.StringProperty()


e = Employee(name="John",
             role="manager",
             email=users.get_current_user().email())
e.hire_date = datetime.datetime.now().date()
e.put()

A API Datastore oferece duas interfaces para consultas: uma interface de objeto de consulta e uma linguagem de consulta semelhante a SQL denominada GQL. Uma consulta devolve entidades sob a forma de instâncias das classes de modelos:

training_registration_list = ["Alfred.Smith@example.com",
                              "jharrison@example.com",
                              "budnelson@example.com"]
employees_trained = db.GqlQuery("SELECT * FROM Employee WHERE email IN :1",
                                training_registration_list)
for e in employees_trained:
  e.new_hire_training_completed = True
  db.put(e)