Nesta página, discutimos os requisitos do esquema do Spanner, como usá-lo para criar relações hierárquicas e atributos de 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 e aplica controle de acesso refinados e evitar conflitos de nomenclatura. É preciso definir um esquema para cada no Spanner.
Você também pode segmentar e armazenar ainda mais linhas na sua tabela de 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 da 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 adequado para uso como
chave primária. Por exemplo, para uma tabela Customers
, pode haver uma
CustomerId
fornecido pelo aplicativo que serve como a chave primária. Em outras
em alguns 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 é o ideal porque o Spanner divide os dados entre servidores por intervalos de chaves, o que significa que suas 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:
- Gere a chave e armazene-a uma coluna. Use a coluna hash (ou a coluna hash e as colunas de chave única juntas) como a chave primária.
- Troque a ordem das colunas na chave primária.
- Usar um identificador universal exclusivo (UUID). Versão 4 UUID é recomendado, porque usa valores aleatórios nos bits de ordem superior. Não use um algoritmo UUID (como versão 1 do UUID) que armazena o carimbo de data/hora nos bits de ordem superior.
- Faça reversão em bits dos valores sequenciais.
Relacionamentos de tabelas pai e filho
Há duas maneiras de definir relações pai-filho em Spanner: intercalação de tabelas e chaves estrangeiras.
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
.
Você associa uma tabela filha a uma tabela pai usando uma DDL que declara a tabela filha como intercalada na mãe, e pela inclusão 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 neste 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 seja um filho intercalado de outra tabela, a tabela deve ter uma tabela composta que inclui todos os componentes da chave primária do pai, em a mesma ordem e, normalmente, uma ou mais colunas adicionais da tabela filha.
O Spanner armazena linhas em ordem classificada por valores de chave primária, com linhas filhas inseridas entre linhas pai. Veja 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. O exemplos de esquema mostram a aparência desse layout físico.
Divisões de banco de dados
É possível definir hierarquias de relacionamentos pai-filho intercalados com até sete camadas de profundidade, o que significa que é possível 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 seu tabelas 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, como 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 contém um intervalo de linhas contíguas. As chaves de início e fim desse intervalo são chamados de "limites de divisão". O Spanner adiciona e remove automaticamente limites de divisão com base no tamanho e na carga, o que muda o número de divisões em banco de dados.
Divisão baseada em carga
Um exemplo de como o Spanner executa a divisão baseada em carga para reduzir os pontos de acesso de leitura, suponha que seu banco de dados contenha uma tabela com 10 linhas que são lidos com mais frequência do que todas as outras linhas da tabela. O Spanner pode adicionar limites de divisão entre cada uma dessas 10 linhas para que que são tratados por um servidor diferente, em vez de permitir que todos as leituras dessas linhas para consumir os recursos de um único servidor.
Como regra geral, se você seguir as práticas recomendadas para a criação de esquemas, O Spanner pode mitigar pontos de acesso de modo que a capacidade de leitura deve melhorar em intervalos de poucos minutos até saturar os recursos no instância ou ocorrerão casos em que nenhum novo limite de divisão possa ser adicionado (porque você tem uma divisão que cobre apenas uma linha sem filhos intercalados).
Esquemas nomeados
Os esquemas nomeados ajudam a organizar dados semelhantes. Isso ajuda você a encontrar objetos no console do Google Cloud, aplicar privilégios e evitar nomear ou colisões.
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ê
poderia criar um esquema chamado fulfillment
para a unidade de negócios de fulfillment.
Esse esquema também pode ter tabelas chamadas product
, order
e customer
information
. No primeiro exemplo, o FQN é warehouse.product
e, no
no segundo exemplo, a FQN é fulfillment.product
. Isso evita confusão na
situações em que vários objetos compartilham o mesmo nome.
Na DDL CREATE SCHEMA
, os objetos de tabela recebem um FQN, 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 de projeto.
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 a objetos de esquema que existem no momento em que você concede acesso. É necessário conceder acesso aos objetos adicionados posteriormente.
O controle de acesso refinado 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 pai
Imagine que você esteja criando um aplicativo de música e precise de uma tabela que armazene linhas de dados de cantores:
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 um filho intercalado 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 valoresNULL
em colunas-chave. Para mais informações, consulte Chave Colunas). - 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
ouBYTES
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 paravarchar
echaracter 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
O diagrama a seguir mostra linhas da tabela Singers
armazenadas pela chave primária
("Singers(1)" e, depois, "Singers(2)", em que o número entre parênteses é
o valor da chave primária.
O diagrama anterior ilustra um exemplo de limite de divisão entre as linhas
codificados por Singers(3)
e Singers(4)
, com os dados das divisões resultantes.
atribuídas a servidores diferentes. À medida que a tabela cresce, é possível que linhas de
Singers
de dados a serem armazenados em locais diferentes.
Criar tabelas mãe e filha
Suponha que agora você queira adicionar alguns dados básicos sobre os álbuns de cada cantor para do aplicativo de música.
A chave primária de Albums
é composta por duas colunas: SingerId
e AlbumId
, para associar cada álbum à sua cantora. O esquema de exemplo a seguir
define as tabelas Albums
e Singers
na raiz do banco de dados
o que as torna tabelas 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 e, em seguida, linhas de Singers
armazenadas pela chave primária contígua:
Uma observação importante sobre o esquema é que o Spanner presume que
relações de localidade de dados entre as tabelas Singers
e Albums
, já que
elas são tabelas de nível superior. Conforme 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. Caso seu aplicativo precise recuperar informações com frequência
sobre todos os álbuns de um cantor específico, crie Albums
como
uma tabela filha intercalada de Singers
, que coloca linhas dos dois
com a dimensão da chave primária. O próximo exemplo explica isso em mais
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. Como mencionado antes, a tabela mãe a chave primária deve 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 paiSingers
.- 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 forON 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 pai, depois por linhas contíguas da tabela filha que compartilham a chave primária do pai. Para exemplo, "Singers(1)", depois "Albums(1, 1)" e, em seguida, "Albums(1, 2)".
- A relação de localidade de dados de cada cantor e seus dados de álbum é
preservados se este banco de dados for dividido, desde que o tamanho de uma linha
Singers
e todas as suasAlbums
linhas permaneçam abaixo do limite de tamanho da divisão e que haja não há ponto de acesso em nenhuma destas linhas deAlbums
. - 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.
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:
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.
Neste exemplo, à medida que o número de cantores aumenta, o Spanner adiciona limites entre cantores para preservar a localidade dos dados entre um cantor e seu dados de álbuns e músicas. No entanto, se o tamanho de uma linha de um cantor e suas linhas filhas exceder o limite de tamanho da divisão, ou um ponto de acesso for detectado nas linhas filhas O Spanner tenta adicionar limites de divisão para isolar o ponto de acesso linha 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 da hierarquia seja logicamente independente, fazer a intercalação física deles dessa forma pode melhorar desempenho, pré-mesclando as tabelas e permitindo que você acesse linhas relacionadas, minimizando os acessos ao armazenamento.
Mesclagens com tabelas intercaladas
Se possível, vincule dados em tabelas intercaladas por chave principal. Como cada
a linha intercalada geralmente é armazenada fisicamente na mesma divisão que a linha pai
linha, o Spanner pode executar junções por chave primária localmente, minimizando
acesso ao armazenamento e tráfego de rede. No exemplo a seguir, Singers
e
Albums
são unidas 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 observações sobre as principais colunas.
Mudar chaves da 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 quiser armazenar NULL em uma coluna de chave primária,
omitir 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
a 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 cada o conteúdo de cada gravadora separadamente.
Multilocação clássica
A maneira clássica de projetar para multilocação é criar um banco de dados separado para
para cada cliente. Neste exemplo, cada banco de dados tem sua própria tabela Singers
:
SingerId | FirstName | LastName |
---|---|---|
1 | Marc | Richards |
2 | Catalina | Smith |
SingerId | FirstName | LastName |
---|---|---|
1 | Alice | Trentor |
2 | Gabriel | Wright |
SingerId | FirstName | LastName |
---|---|---|
1 | Benjamin | Martinez |
2 | Hannah | Harris |
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 chave CustomerId
nas tabelas. Se você tornar CustomerId
a primeira coluna de chave,
dados de cada cliente tem uma boa região administrativa. Spanner.
podem usar divisões de banco de dados para maximizar
desempenho com base no tamanho dos dados
e nos padrões de carregamento. No exemplo a seguir,
há uma única tabela Singers
para todos os clientes:
CustomerId | SingerId | FirstName | LastName |
---|---|---|---|
1 | 1 | Marc | Richards |
1 | 2 | Catalina | Smith |
2 | 1 | Alice | Trentor |
2 | 2 | Gabriel | Wright |
3 | 1 | Benjamin | Martinez |
3 | 2 | Hannah | Harris |
Se você precisa ter bancos de dados separados para cada locatário, há restrições a serem cientes de:
- Há limites quanto ao 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 tempo de resposta. 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 colocamos 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