Visão geral dos esquemas

Esta página discute os requisitos do esquema do Spanner, como usar o esquema para criar relações hierárquicas e os recursos do esquema. Ele também apresenta tabelas intercaladas, o que pode melhorar o desempenho de consulta ao consultar tabelas em uma relação pai-filho.

Um esquema é um namespace que contém objetos de banco de dados, como tabelas, visualizações, índices e funções. Você usa esquemas para organizar objetos, aplicar privilégios de controle de acesso refinado e evitar conflitos de nomenclatura. É preciso definir um esquema para cada no Spanner.

Também é possível segmentar e armazenar linhas na tabela do banco de dados em diferentes regiões geográficas. Para mais informações, consulte a Visão geral do particionamento geográfico.

Dados bem tipados

Os dados no Spanner são fortemente tipados. Os tipos de dados incluem escalares e complexos , que são descritos em Tipos de dados no GoogleSQL e tipos de dados do PostgreSQL.

Escolher chave principal

Os bancos de dados do Spanner podem conter uma ou mais tabelas. As tabelas são estruturadas como linhas e colunas. O esquema de tabela define uma ou mais colunas como a chave primária da tabela, que identifica exclusivamente cada linha. As chaves primárias são são sempre indexados para buscas rápidas de linhas. Para atualizar ou excluir arquivos linhas em uma tabela, a tabela deve ter uma chave primária. Uma tabela sem tabelas primárias as colunas-chave só podem ter uma linha. Apenas bancos de dados de dialeto GoogleSQL podem ter tabelas sem uma chave primária.

Muitas vezes, o aplicativo já tem um campo que é adequado para uso como chave primária. Por exemplo, para uma tabela Customers, pode haver um CustomerId fornecido pelo aplicativo que serve como a chave primária. Em outros casos, pode ser necessário gerar uma chave primária ao inserir a linha. Isso normalmente seria um valor inteiro único sem significância comercial (um valor chave primária alternativa).

Em todos os casos, tenha cuidado para não criar pontos de acesso com a escolha da chave primária. Por exemplo, se você inserir registros com um número inteiro monotônico crescente como a chave, a inserção sempre ocorrerá no final do espaço da chave. Isso não é desejável, porque o Spanner divide os dados entre servidores por intervalos de chaves. Isso significa que as inserções serão direcionadas para um único servidor, criando um ponto de acesso. Há técnicas que podem espalhar a carga em vários servidores e evitar pontos de acesso:

Relacionamentos de tabelas pai e filho

Há duas maneiras de definir relações pai-filho no Spanner: intercalação de tabelas e chaves externas.

A intercalação de tabelas do Spanner é uma boa opção para muitos relações pai-filho. Com a intercalação, o Spanner fica fisicamente coloca linhas filhas com linhas mãe no armazenamento. A colocation pode para melhorar o desempenho. Por exemplo, se você tiver uma tabela Customers e um Invoices, e seu aplicativo busca frequentemente todas as faturas de um cliente, é possível definir Invoices como uma tabela filha intercalada de Customers: Assim, você declara uma relação de localidade de dados entre duas tabelas independentes. você informa ao Spanner para armazenar uma ou mais linhas de Invoices com uma linha Customers.

Para associar uma tabela filha a uma mãe, use a DDL que declara a tabela filha como intercalada na mãe e inclua a chave primária da tabela mãe como a primeira parte da chave primária composta da tabela filha. Para mais informações sobre intercalação, consulte Criar tabelas intercaladas mais adiante nesta página.

As chaves externas são uma solução pai-filho mais geral e abordam casos de uso adicionais. Elas não estão limitadas a colunas de chave primária, e as tabelas podem ter várias relações de chave externa, como pai em alguns relacionamentos e filho em outros. No entanto, uma relação de chave externa não sugere a co-localização das tabelas na camada de armazenamento.

O Google recomenda que você escolha representar relações pai-filho como tabelas intercaladas ou chaves externas, mas não ambas. Para mais informações sobre chaves estrangeiras e sua comparação com tabelas intercaladas, consulte Chaves estrangeiras geral.

Chaves primárias em tabelas intercaladas

Para intercalação, cada tabela precisa ter uma chave primária. Se você declarar uma tabela como filha intercalada de outra, ela precisará ter uma chave primária composta que inclua todos os componentes da chave primária da mãe, na mesma ordem, e, normalmente, uma ou mais colunas de tabelas filhas adicionais.

O Spanner armazena linhas classificadas em ordem por valores de chave primária. As linhas filhas são inseridas entre as linhas mães. Consulte uma ilustração de linhas intercaladas em Criar tabelas intercaladas mais adiante nesta página.

Em resumo, o Spanner pode colocar fisicamente linhas de tabelas relacionadas. Os exemplos de esquema mostram como esse layout físico é.

Divisões de banco de dados

É possível definir hierarquias de relações de mãe e filha intercaladas com até sete camadas. Isso significa que você pode colocar linhas de sete tabelas independentes. Se o tamanho dos dados nas tabelas for pequeno, um único Spanner servidor de dados pode lidar com seu banco de dados. Mas o que acontece quando as tabelas relacionadas crescem e começam a atingir os limites de recursos de um servidor individual? O Spanner é um banco de dados distribuído. Isso significa que, conforme sua o banco de dados cresce, o Spanner divide seus dados em blocos chamados “divisões”. As divisões podem ser movimentadas independentemente entre si e atribuídas a servidores diferentes, que podem estar em locais físicos distintos. Um a divisão mantém um intervalo de linhas contíguas. As chaves de início e término desse intervalo são chamadas de "limites de divisão". O Spanner adiciona e remove automaticamente limites de divisão com base no tamanho e na carga, o que altera o número de divisões no banco de dados.

Divisão baseada em carga

Como um exemplo de como o Spanner executa a divisão baseada em carga para reduzir os pontos de acesso de leitura, imagine que seu banco de dados contém uma tabela com 10 linhas que são lidas com mais frequência do que todas as outras linhas na tabela. O Spanner pode adicionar limites de divisão entre cada uma dessas 10 linhas para que cada uma seja processada por um servidor diferente, em vez de permitir que todas as leituras dessas linhas consumam os recursos de um único servidor.

Como regra geral, se você seguir as práticas recomendadas para o design de esquemas, o Spanner poderá mitigar pontos de acesso para que a capacidade de leitura melhore a cada poucos minutos até que você sature os recursos na sua instância ou encontre casos em que não é possível adicionar novos limites de divisão (porque você tem uma divisão que abrange apenas uma linha sem filhos intercalados).

Esquemas nomeados

Os esquemas nomeados ajudam a organizar dados semelhantes. Isso ajuda a encontrar objetos rapidamente no console do Google Cloud, aplicar privilégios e evitar colisões de nomes.

Esquemas nomeados, assim como outros objetos de banco de dados, são gerenciados usando DDL.

Os esquemas nomeados do Spanner permitem usar nomes totalmente qualificados (FQNs, na sigla em inglês) para consultar dados. As FQNs permitem combinar o nome do esquema e para identificar objetos de banco de dados. Por exemplo, é possível criar um esquema chamado warehouse para a unidade de negócios do armazém. As tabelas que usam esse O esquema pode incluir: product, order e customer information. Ou você pode criar um esquema chamado fulfillment para a unidade de negócios de atendimento. Esse esquema também pode ter tabelas chamadas product, order e customer information. No primeiro exemplo, o FQN é warehouse.product, e no segundo exemplo, é fulfillment.product. Isso evita confusão na situações em que vários objetos compartilham o mesmo nome.

No DDL CREATE SCHEMA, os objetos de tabela recebem um FDQN, por exemplo, sales.customers, e um nome curto, por exemplo, sales.

Os seguintes objetos de banco de dados são compatíveis com esquemas nomeados:

  • TABLE
    • CREATE
    • INTERLEAVE IN [PARENT]
    • FOREIGN KEY
    • SYNONYM
  • VIEW
  • INDEX
  • FOREIGN KEY
  • SEQUENCE

Para mais informações sobre o uso de esquemas nomeados, consulte Gerenciar esquemas nomeados.

Use o controle de acesso refinado com esquemas nomeados

Com os esquemas nomeados, é possível conceder acesso no nível do esquema a cada objeto no esquema. Isso se aplica aos objetos de esquema que existem no momento em que você concede o acesso. Você precisa conceder acesso a objetos que serão adicionados mais tarde.

O controle de acesso detalhado limita o acesso a grupos inteiros de objetos de banco de dados, como tabelas, colunas e linhas na tabela.

Para mais informações, consulte Conceder privilégios de controle de acesso refinados de projeto.

Exemplos de esquema

Os exemplos de esquema nesta seção mostram como criar tabelas pai e filho com e sem intercalação e ilustrar os layouts físicos correspondentes dos dados.

Criar uma tabela mãe

Imagine que você esteja criando um aplicativo de música e precise de uma tabela que armazene linhas de dados de cantores:

Tabela "Singers" com cinco linhas e quatro colunas

A tabela contém uma coluna de chave primária, SingerId, que aparece à esquerda da linha em negrito e que as tabelas sejam organizadas por linhas e colunas.

É possível definir a tabela com a seguinte DDL:

GoogleSQL

CREATE TABLE Singers (
SingerId   INT64 NOT NULL,
FirstName  STRING(1024),
LastName   STRING(1024),
SingerInfo BYTES(MAX),
) PRIMARY KEY (SingerId);

PostgreSQL

CREATE TABLE singers (
singer_id   BIGINT PRIMARY KEY,
first_name  VARCHAR(1024),
last_name   VARCHAR(1024),
singer_info BYTEA
);

Observe o seguinte sobre o esquema do exemplo:

  • Singers é uma tabela na raiz da hierarquia do banco de dados porque não é definida como uma filha intercalada de outra tabela.
  • Para bancos de dados de dialeto GoogleSQL, as colunas de chave primária geralmente são anotadas com NOT NULL (embora você possa omitir essa anotação se quiser permitir valores NULL em colunas-chave. Para mais informações, consulte Colunas chave.
  • As colunas que não estão incluídas na chave primária são chamadas de colunas não chave e podem ter uma anotação NOT NULL opcional.
  • As colunas que usam o tipo STRING ou BYTES no GoogleSQL precisam ser definido com um comprimento, que representa o número máximo de caracteres Unicode caracteres que podem ser armazenados no campo. A especificação de comprimento é opcional para varchar e character varying do PostgreSQL tipos Para mais informações, consulte Tipos de dados escalares para bancos de dados do dialeto GoogleSQL e dados PostgreSQL para bancos de dados de dialeto PostgreSQL.

O que parece o layout físico das linhas na tabela Singers? O diagrama a seguir mostra as linhas da tabela Singers armazenadas por chave primária ("Singers(1)" e "Singers(2)", em que o número entre parênteses é o valor da chave primária.

Exemplo de linhas de uma tabela armazenada na ordem da chave primária

O diagrama anterior ilustra um exemplo de limite de divisão entre as linhas codificadas por Singers(3) e Singers(4), com os dados das divisões resultantes atribuídos a diferentes servidores. À medida que a tabela cresce, é possível que linhas de Singers de dados a serem armazenados em locais diferentes.

Criar tabelas mães e filhas

Imagine que agora você queira adicionar alguns dados básicos sobre os álbuns de cada cantor ao aplicativo de música.

Tabela "Albums" com cinco linhas e três colunas

A chave primária de Albums é composta por duas colunas: SingerId e AlbumId, para associar cada álbum à sua cantora. O exemplo de esquema a seguir define as tabelas Albums e Singers na raiz da hierarquia do banco de dados, o que as torna irmãs.

-- Schema hierarchy:
-- + Singers (sibling table of Albums)
-- + Albums (sibling table of Singers)

GoogleSQL

CREATE TABLE Singers (
 SingerId   INT64 NOT NULL,
 FirstName  STRING(1024),
 LastName   STRING(1024),
 SingerInfo BYTES(MAX),
) PRIMARY KEY (SingerId);

CREATE TABLE Albums (
SingerId     INT64 NOT NULL,
AlbumId      INT64 NOT NULL,
AlbumTitle   STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId);

PostgreSQL

CREATE TABLE singers (
singer_id   BIGINT PRIMARY KEY,
first_name  VARCHAR(1024),
last_name   VARCHAR(1024),
singer_info BYTEA
);

CREATE TABLE albums (
singer_id     BIGINT,
album_id      BIGINT,
album_title   VARCHAR,
PRIMARY KEY (singer_id, album_id)
);

O layout físico das linhas de Singers e Albums é semelhante ao Diagrama a seguir, com linhas da tabela Albums armazenadas por instâncias primárias contíguas chave, as linhas de Singers armazenadas pela chave primária contígua:

Layout físico das linhas

Uma observação importante sobre o esquema é que o Spanner não pressupõe relações de localidade de dados entre as tabelas Singers e Albums, porque são tabelas de nível superior. À medida que o banco de dados cresce, o Spanner pode adicionar limites de divisão entre qualquer uma das linhas. Isso significa que as linhas do Albums pode acabar em uma divisão diferente das linhas da tabela Singers, e as duas divisões poderiam se mover independentemente uma da outra.

Dependendo das necessidades do seu aplicativo, convém permitir que os dados Albums sejam localizados em divisões diferentes dos dados Singers. No entanto, isso pode gerar uma queda de desempenho devido à necessidade de coordenar leituras e atualizações recursos distintos. Se o aplicativo precisar recuperar informações sobre todos os álbuns de um determinado cantor com frequência, crie Albums como uma tabela filha intercalada de Singers, que coloca linhas das duas tabelas ao longo da dimensão da chave primária. O próximo exemplo explica isso detalhes.

Criar tabelas intercaladas

Uma tabela intercalada é uma tabela que você declara ser uma filha intercalada de outra tabela porque você quer que as linhas da tabela filho estejam fisicamente armazenada com a linha pai associada. Conforme mencionado anteriormente, a chave primária da tabela mãe precisa ser a primeira parte da chave primária composta da tabela filha.

Ao projetar seu aplicativo de música, suponha que você perceba que o aplicativo precisa acessar com frequência as linhas da tabela Albums ao acessar uma Singers. Por exemplo, quando você acessa a linha Singers(1), também precisa para acessar as linhas Albums(1, 1) e Albums(1, 2). Nesse caso, Singers e Albums precisam ter uma forte relação de localidade de dados. É possível declarar essa relação de localidade de dados criando Albums como um filho intercalado tabela de Singers.

-- Schema hierarchy:
-- + Singers
--   + Albums (interleaved table, child table of Singers)

A linha em negrito no esquema a seguir mostra como criar Albums como uma tabela intercalada de Singers.

GoogleSQL

CREATE TABLE Singers (
 SingerId   INT64 NOT NULL,
 FirstName  STRING(1024),
 LastName   STRING(1024),
 SingerInfo BYTES(MAX),
 ) PRIMARY KEY (SingerId);

CREATE TABLE Albums (
 SingerId     INT64 NOT NULL,
 AlbumId      INT64 NOT NULL,
 AlbumTitle   STRING(MAX),
 ) PRIMARY KEY (SingerId, AlbumId),
INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

PostgreSQL

CREATE TABLE singers (
 singer_id   BIGINT PRIMARY KEY,
 first_name  VARCHAR(1024),
 last_name   VARCHAR(1024),
 singer_info BYTEA
 );

CREATE TABLE albums (
 singer_id     BIGINT,
 album_id      BIGINT,
 album_title   VARCHAR,
 PRIMARY KEY (singer_id, album_id)
 )
 INTERLEAVE IN PARENT singers ON DELETE CASCADE;

Observações sobre este esquema:

  • SingerId, que é a primeira parte da chave primária da tabela filha Albums, também é a chave primária da tabela mãe Singers.
  • O ON DELETE CASCADE significa que, quando uma linha da tabela pai é excluída, sua as linhas filhas também são excluídas automaticamente. Se uma tabela filho não tiver essa anotação, ou ela for ON DELETE NO ACTION, será necessário exclua as linhas filhas antes de excluir a linha pai.
  • As linhas intercaladas são ordenadas primeiro por linhas da tabela mãe e, em seguida, por linhas contíguas da tabela filha que compartilham a chave primária da mãe. Por exemplo, "Singers(1)", depois "Albums(1, 1)" e "Albums(1, 2)".
  • A relação de localidade de dados de cada cantor e dados dos álbuns é preservada se esse banco de dados se dividir, desde que o tamanho de uma linha Singers e todas as linhas Albums fiquem abaixo do limite de tamanho de divisão e que não haja nenhum ponto de acesso em nenhuma dessas linhas Albums.
  • A linha pai já precisa existir para que seja possível inserir linhas filho. A linha mãe podem já existir no banco de dados ou podem ser inseridas antes da inserção das linhas filho na mesma transação.

As linhas de "Albums" são intercaladas entre as linhas de "Singers"

Criar uma hierarquia de tabelas intercaladas

A relação mãe e filha entre Singers e Albums pode ser estendido para mais tabelas descendentes. Por exemplo, é possível criar uma tabela intercalada chamada Songs como filha de Albums para armazenar a lista de faixas de cada álbum:

Tabela "Songs" com seis linhas e quatro colunas.

Songs precisa ter uma chave primária que inclua todas as chaves primárias das tabelas que estão em um nível mais alto na hierarquia, ou seja, SingerId e AlbumId.

-- Schema hierarchy:
-- + Singers
--   + Albums (interleaved table, child table of Singers)
--     + Songs (interleaved table, child table of Albums)

GoogleSQL

CREATE TABLE Singers (
 SingerId   INT64 NOT NULL,
 FirstName  STRING(1024),
 LastName   STRING(1024),
 SingerInfo BYTES(MAX),
) PRIMARY KEY (SingerId);

CREATE TABLE Albums (
 SingerId     INT64 NOT NULL,
 AlbumId      INT64 NOT NULL,
 AlbumTitle   STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId),
 INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE TABLE Songs (
 SingerId     INT64 NOT NULL,
 AlbumId      INT64 NOT NULL,
 TrackId      INT64 NOT NULL,
 SongName     STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId, TrackId),
 INTERLEAVE IN PARENT Albums ON DELETE CASCADE;

PostgreSQL

CREATE TABLE singers (
 singer_id   BIGINT PRIMARY KEY,
 first_name  VARCHAR(1024),
 last_name   VARCHAR(1024),
 singer_info BYTEA
 );

CREATE TABLE albums (
 singer_id     BIGINT,
 album_id      BIGINT,
 album_title   VARCHAR,
 PRIMARY KEY (singer_id, album_id)
 )
 INTERLEAVE IN PARENT singers ON DELETE CASCADE;

CREATE TABLE songs (
 singer_id     BIGINT,
 album_id      BIGINT,
 track_id      BIGINT,
 song_name     VARCHAR,
 PRIMARY KEY (singer_id, album_id, track_id)
 )
 INTERLEAVE IN PARENT albums ON DELETE CASCADE;

O diagrama a seguir representa uma visualização física de linhas intercaladas.

As músicas são intercaladas em "Albums", que são intercaladas entre "Singers"

Neste exemplo, conforme o número de cantores cresce, o Spanner adiciona limites divididos entre os cantores para preservar a localidade dos dados entre um cantor e seus dados de álbum e música. No entanto, se o tamanho de uma linha de cantor e as linhas filhas ultrapassarem o limite de divisão ou se um ponto de acesso for detectado nas linhas filhas, o Spanner tentará adicionar limites de divisão para isolar essa linha de ponto de acesso com todas as linhas filhas abaixo dela.

Em resumo, uma tabela mãe e todas as tabelas filhas e descendentes formam uma hierarquia de tabelas no esquema. Embora cada tabela na hierarquia seja logicamente independente, a intercalação física delas pode melhorar o desempenho, efetivamente juntando previamente as tabelas e permitindo que você acesse linhas relacionadas e minimize os acessos de armazenamento.

Mesclagens com tabelas intercaladas

Se possível, vincule dados em tabelas intercaladas por chave principal. Como cada linha intercalada geralmente é armazenada fisicamente na mesma divisão que a linha mãe, o Spanner pode realizar mesclagens por chave primária localmente, minimizando o acesso ao armazenamento e o tráfego de rede. No exemplo a seguir, Singers e Albums são unidos na chave primária SingerId.

GoogleSQL

SELECT s.FirstName, a.AlbumTitle
FROM Singers AS s JOIN Albums AS a ON s.SingerId = a.SingerId;

PostgreSQL

SELECT s.first_name, a.album_title
FROM singers AS s JOIN albums AS a ON s.singer_id = a.singer_id;

Colunas de chave

Esta seção inclui algumas notas sobre as colunas principais.

Mudar chaves de tabela

As chaves de uma tabela não podem mudar. Não é possível adicionar ou remover uma coluna de chave a uma tabela existente.

Armazenar NULLs em uma chave primária

No GoogleSQL, se você quiser armazenar NULL em uma coluna de chave primária, omita a cláusula NOT NULL dessa coluna no esquema. Os bancos de dados de dialeto PostgreSQL oferecem suporte a NULLs em uma coluna de chave primária.

Veja um exemplo de como omitir a cláusula NOT NULL na coluna da chave principal SingerId. Observe que, como SingerId é a chave primária, só pode haver uma linha que armazena NULL nessa coluna.

CREATE TABLE Singers (
  SingerId   INT64,
  FirstName  STRING(1024),
  LastName   STRING(1024),
) PRIMARY KEY (SingerId);

A propriedade anulável da coluna da chave primária precisa coincidir entre as instruções da tabela mãe e as da tabela filha. Neste exemplo, NOT NULL para a coluna Albums.SingerId não é permitido porque Singers.SingerId o omite.

CREATE TABLE Singers (
  SingerId   INT64,
  FirstName  STRING(1024),
  LastName   STRING(1024),
) PRIMARY KEY (SingerId);

CREATE TABLE Albums (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  AlbumTitle   STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

Tipos não permitidos

As colunas a seguir não podem ser do tipo ARRAY:

  • colunas de chave de uma tabela
  • colunas de chave de um índice

Projetar para multilocação

Talvez você queira implementar a multilocação se estiver armazenando dados que pertencem a diferentes clientes. Por exemplo, um serviço de música pode querer armazenar o conteúdo de cada gravadora separadamente.

Multilocação clássica

A maneira clássica de desenvolver um design para multilocação é criar um banco de dados separado para cada cliente. Neste exemplo, cada banco de dados tem sua própria tabela Singers:

Banco de dados 1: Ackworth Records
SingerId FirstName LastName
1MarcRichards
2CatalinaSmith
Banco de dados 2: Cama Records
SingerId FirstName LastName
1AliceTrentor
2GabrielWright
Banco de dados 3: Eagan Records
SingerId FirstName LastName
1BenjaminMartinez
2HannahHarris

Multilocação gerenciada por esquema

Outra maneira de projetar para multilocação no Spanner é ter todos clientes em uma única tabela em um único banco de dados e usar um banco de dados primário diferente chave-valor para cada cliente. Por exemplo, é possível incluir uma coluna de chave CustomerId nas suas tabelas. Se você tornar CustomerId a primeira coluna de chave, os dados de cada cliente terão uma boa localidade. O Spanner pode usar divisões de banco de dados para maximizar o desempenho com base no tamanho dos dados e nos padrões de carga. No exemplo abaixo, há uma única tabela Singers para todos os clientes:

Banco de dados multilocação do Spanner
CustomerId SingerId FirstName LastName
11MarcRichards
12CatalinaSmith
21AliceTrentor
22GabrielWright
31BenjaminMartinez
32HannahHarris

Se forem necessários bancos de dados separados para cada locatário, é preciso estar ciente das seguintes restrições:

  • limites para o número de bancos de dados por instância e o número de tabelas e índices por banco de dados. Dependendo do número de clientes, talvez não seja possível ter bancos de dados ou tabelas separados.
  • Adicionar novas tabelas e índices não intercalados pode levar muito tempo. Talvez você não conseguir o desempenho desejado se o design do esquema depende a adição de novas tabelas e índices.

Se for preciso criar bancos de dados separados, pode ser mais interessante distribuir suas tabelas entre eles de maneira que cada banco tenha um número baixo de alterações de esquema por semana.

Se você criar tabelas e índices separados para cada cliente do seu aplicativo, não coloque todas as tabelas e índices no mesmo banco de dados. Em vez disso, divida em vários bancos de dados para reduzir os problemas de desempenho problemas na criação de um número grande de índices.

Para saber mais sobre outros padrões de gerenciamento de dados e design de aplicativos para multilocação, consulte Como implementar a multilocação na Spanner