Modelo de dados e esquema

Resumo do modelo de dados

Um banco de dados do Cloud Spanner pode conter uma ou mais tabelas. Elas parecem tabelas de bancos de dados relacionais, já que são estruturadas com linhas, colunas e valores, e contêm chaves primárias. Os dados no Cloud Spanner são muito especificados por tipo: você precisa definir um esquema para cada banco de dados, e esse esquema precisa especificar os tipos de dados de cada coluna em cada tabela. Entre os tipos de dados permitidos, estão os escalares e de matrizes. Eles são explicados com mais detalhes em Tipos de dados. Também é possível definir um ou mais índices secundários em uma tabela, conforme descrito em Índices secundários.

Relacionamentos de tabelas pai e filho

É possível definir várias tabelas em um banco de dados. Você também tem a opção de definir os relacionamentos de pai e filho entre as tabelas se quiser que o Cloud Spanner colocalize fisicamente as linhas dele para uma recuperação eficiente. Por exemplo, suponha que você tem as tabelas Customers e Invoices. Se o aplicativo busca frequentemente todas as faturas para um determinado cliente, é possível definir Invoices como uma tabela filho de Customers. Ao fazer isso, você está declarando uma relação de localidade de dados entre duas tabelas logicamente independentes. Você está dizendo que o Cloud Spanner armazena fisicamente uma ou mais linhas de Invoices com uma linha de Customers.

Chaves primárias

Como você determina para o Cloud Spanner quais linhas de Invoices precisam ser armazenadas com linhas específicas de Customers? Você faz isso usando a chave primária dessas tabelas. Toda tabela precisa ter uma chave primária, e essa chave pode ser composta por zero ou mais colunas da tabela. Se você declarar uma tabela para ser filho de outra, as colunas da chave primária do pai precisarão ser o prefixo da chave primária da secundária. Isso significa que, se a chave primária de uma tabela pai for composta por N colunas, a chave primária de cada uma das tabelas filho também precisa ser composta das mesmas N colunas, na mesma ordem e começando pela mesma coluna.

O Cloud Spanner armazena linhas classificadas em ordem por valores de chave primária. As linhas filho são inseridas entre linhas pai que compartilham o mesmo prefixo de chave primária. Essa inserção de filhos entre os pais ao longo da dimensão da chave primária é chamada de intercalação, e as tabelas filho também são chamadas de tabelas intercaladas. Consulte abaixo uma ilustração de linhas intercaladas, em Como criar tabelas intercaladas.

Em resumo, o Cloud Spanner pode localizar juntas fisicamente linhas de tabelas relacionadas. Os exemplos de esquema abaixo mostram o aspecto desse layout físico.

Como escolher uma chave primária

A chave primária identifica exclusivamente cada uma das linhas em uma tabela. Para atualizar ou excluir linhas existentes em uma tabela, é preciso que a tabela tenha uma chave primária composta de uma ou mais colunas. Uma tabela sem colunas de chave primária pode ter apenas uma linha. Muitas vezes, o aplicativo já tem um campo que é uma escolha natural para uso como chave primária. No exemplo da tabela Customers acima, pode haver um CustomerId fornecido pelo aplicativo que funciona bem como a chave primária. Em outros casos, pode ser necessário gerar uma chave primária ao inserir a linha, como um valor INT64 único que você gera.

Em todos os casos, você deve ter 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 Cloud Spanner divide os dados entre servidores por intervalos de chaves, o que 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:

Divisões de banco de dados

É possível definir hierarquias de relações de pai e filho entre tabelas com até sete camadas. Isso significa que você pode colocalizar linhas de sete tabelas logicamente independentes. Se o tamanho dos dados nas tabelas for pequeno, o banco de dados provavelmente poderá ser tratado por um único servidor do Cloud Spanner. Mas o que acontece quando as tabelas relacionadas crescem e começam a atingir os limites de recursos de um servidor individual? O Cloud Spanner é um banco de dados distribuído. Isso significa que, à medida que o banco de dados cresce, o Cloud Spanner divide os dados em blocos denominados "divisões". As divisões individuais podem se mover de maneira independente umas da outras e serem atribuídas a servidores diferentes, que podem estar em locais físicos distintos. Uma divisão é definida como um intervalo de linhas em uma tabela de nível superior (em outras palavras, não intercalada), em que as linhas são ordenadas pela chave primária. As chaves de início e término desse intervalo são chamadas de "limites de divisão". O Cloud Spanner adiciona e remove automaticamente os limites de divisão, o que altera o número de divisões no banco de dados.

O Cloud Spanner divide os dados com base na carga: ele adiciona limites de divisão automaticamente quando detecta alta carga de leitura ou gravação espalhada entre muitas chaves em uma divisão. Você tem algum controle sobre como seus dados são divididos porque o Cloud Spanner só pode definir limites de divisão entre as linhas de tabelas que estão na raiz de uma hierarquia (ou seja, tabelas que não são intercaladas em uma tabela pai). Além disso, as linhas de uma tabela intercalada não podem ser divididas da linha correspondente na tabela pai porque as linhas da tabela intercalada são armazenadas na ordem da chave primária classificada juntamente com a linha da tabela pai que compartilha o mesmo prefixo da chave primária. Consulte uma ilustração sobre linhas intercaladas em Como criar uma hierarquia de tabelas intercaladas. Assim, os relacionamentos de tabela pai e filho que você define, juntamente com os valores de chave primária definidos para linhas de tabelas relacionadas, permitem controlar a maneira como os dados são divididos em segundo plano.

Divisão baseada em carga

Como um exemplo de como o Cloud 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. Enquanto essa tabela está na raiz da hierarquia do banco de dados (em outras palavras, não é uma tabela intercalada), o Cloud Spanner pode adicionar limites de divisão entre cada uma dessas 10 linhas para que cada uma delas seja manipulada 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 projeto do esquema, o Cloud Spanner poderá reduzir os pontos de acesso em leituras que segmentam linhas de uma tabela não intercalada, de modo que a capacidade de leitura melhore a cada poucos minutos até você saturar os recursos na instância ou se deparar com casos em que nenhum novo limite de divisão pode ser adicionado porque há uma divisão que cobre apenas uma única linha e as tabelas filho intercaladas dela.

Exemplos de esquema

Os exemplos de esquema abaixo mostram como criar tabelas do Cloud Spanner com e sem relações pai e filho e ilustrar os layouts físicos correspondentes de dados.

Como criar uma tabela

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

Tabela "Singers" com cinco linhas e quatro colunas.

Vista lógica de linhas em uma tabela simples de cantores. A coluna da chave primária aparece à esquerda da linha em negrito.

Observe que a tabela contém uma coluna de chave primária, SingerId, que aparece à esquerda da linha em negrito, e que as tabelas são organizadas por linhas, colunas e valores.

Você pode definir a tabela com um esquema do Cloud Spanner como este:

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

Observe o seguinte sobre o esquema do exemplo:

  • Singers é uma tabela na raiz da hierarquia do banco de dados porque não é definida como filho de outra tabela.
  • Colunas de chave primária são geralmente anotadas com NOT NULL (mas é possível omitir esta anotação se quiser permitir valores NULL nas colunas-chave. Saiba mais em Colunas-chave).
  • As colunas que não estão incluídas na chave primária são chamadas de colunas não chave, e elas podem ter uma anotação NOT NULL opcional.
  • As colunas que usam o tipo STRING ou BYTES precisam ser definidas com um comprimento, o que representa o número máximo de caracteres Unicode que podem ser armazenados no campo. Veja mais detalhes em Tipos de dados escalares.

Qual a aparência do layout físico das linhas na tabela Singers? O diagrama abaixo mostra as linhas da tabela Singers armazenadas por uma chave primária contígua, ou ordem de classificação da chave primária (isto é, "Singers (1)", depois "Singers (2)" e assim por diante, em que "Singers (1)" representa a linha na tabela Singers com chave por 1).

Exemplo de linhas de uma tabela armazenada em ordem de chave contígua.

Layout físico de linhas na tabela "Singers", com um limite de divisão de exemplo que resulta em divisões processadas por servidores diferentes.

O diagrama acima também ilustra possíveis limites de divisão, que podem ocorrer entre qualquer linha de Singers, porque Singers está na raiz da hierarquia do banco de dados. Ele também ilustra um exemplo de limite de divisão entre as linhas de chave por Singers(3) e Singers(4), com os dados das divisões resultantes atribuídas a servidores diferentes. Isso significa que, à medida que essa tabela crescer, é possível que as linhas de dados de Singers sejam armazenadas em locais diferentes.

Como criar várias tabelas

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

Visualização lógica de linhas em uma tabela "Albums". As colunas da chave primária aparecem à esquerda da linha em negrito

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

-- Schema hierarchy:
-- + Singers (sibling table of Albums)
-- + Albums (sibling table of Singers)
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);

O layout físico das linhas de Singers e Albums é semelhante ao diagrama. As linhas da tabela de Albums são armazenadas pela chave primária contígua, e então as linhas de Singers são armazenadas pela chave primária contígua:

Layout físico das linhas: as linhas de "Albums" e "Singers" são armazenadas pelo valor da chave

Layout físico de linhas das tabelas "Singers" e "Albums", ambas na raiz da hierarquia do banco de dados.

Uma observação importante sobre o esquema acima é que não há no Cloud Spanner suposições sobre as 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 Cloud Spanner pode adicionar limites de divisão entre qualquer uma das linhas mostradas acima. Isso significa que as linhas da tabela Albums podem acabar em uma divisão diferente das linhas da tabela Singers e as duas divisões podem se mover de maneira independente uma da outra.

Dependendo das necessidades do aplicativo, pode ser útil permitir que os dados de Albums sejam localizados em diferentes divisões dos dados de Singers. No entanto, se o aplicativo precisa frequentemente recuperar informações sobre todos os álbuns de um determinado cantor, crie Albums como uma tabela filho de Singers, que colocaliza linhas das duas tabelas ao longo da dimensão da chave primária. O próximo exemplo explica isso com mais detalhes.

Como criar tabelas intercaladas

À medida que o aplicativo de música está sendo projetado, imagine que você percebe que o aplicativo precisa acessar com frequência linhas de tabelas Singers e Albums para uma determinada chave primária. Por exemplo, cada vez que você acessa a linha Singers(1), também precisa acessar as linhas Albums(1, 1) e Albums(1, 2). Em outras palavras, Singers e Albums precisam ter uma forte relação de localidade de dados.

Você pode declarar essa relação de localidade de dados por meio da criação de Albums como uma tabela filho, ou "intercalada", de Singers. Conforme mencionado em Chaves primárias, uma tabela intercalada é uma tabela que é declarada filha de outra porque é necessário que as linhas da tabela filha sejam armazenadas fisicamente junto com a linha pai associada. Conforme mencionado acima, o prefixo da chave primária de uma tabela filho precisa ser a chave primária da tabela pai.

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

-- Schema hierarchy:
-- + Singers
--   + Albums (interleaved table, child table of Singers)
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;

Observações sobre este esquema:

  • SingerId, que é o prefixo da chave primária da tabela filho Albums, também é a chave primária da tabela pai Singers correspondente. Isso não será necessário se Singers e Albums estiverem no mesmo nível de hierarquia, mas é necessário neste esquema porque Albums é declarado como uma tabela filho de Singers.
  • A anotação ON DELETE CASCADE indica que, quando uma linha da tabela pai é excluída, as linhas filho nesta tabela serão automaticamente excluídas, isto é, todas as linhas que começam com a mesma chave primária. Se uma tabela filho não tiver essa anotação ou se a anotação for ON DELETE NO ACTION, exclua as linhas filho antes de excluir a linha pai.
  • As linhas intercaladas são ordenadas primeiro por linhas da tabela pai e, em seguida, por linhas contíguas da tabela filho que compartilham a chave primária da tabela pai, ou seja, "Singers(1)", depois "Albums(1, 1)", então "Albums(1, 2)" e assim por diante.
  • A relação de localidade de dados de cada cantor e dados dos respectivos álbuns seria preservada se este banco de dados se dividisse, porque as divisões só podem ser inseridas entre linhas da tabela Singers.
  • A linha pai já precisa existir para que seja possível inserir linhas filho.

Layout físico das linhas: as linhas de "Albums" são intercaladas entre as linhas da "Singers"

Layout físico de linhas da tabela "Singers" e da tabela filho "Albums".

Como criar uma hierarquia de tabelas intercaladas

A relação pai e filho entre Singers e Albums pode ser estendida a mais tabelas descendentes. Por exemplo, você pode criar uma tabela intercalada chamada Songs como filho de Albums para armazenar a lista de faixas de cada álbum:

Tabela "Songs" com seis linhas e quatro colunas

Visualização lógica de linhas em uma tabela Songs. As colunas da chave primária aparecem à esquerda da linha em negrito

Songs precisa ter uma chave primária composta por todas as chaves primárias das tabelas acima na hierarquia, ou seja, SingerId e AlbumId.

-- Schema hierarchy:
-- + Singers
--   + Albums (interleaved table, child table of Singers)
--     + Songs (interleaved table, child table of Albums)
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;

A visualização física de linhas intercaladas mostra que a relação de localidade de dados entre um cantor e seus respectivos álbuns e dados de músicas é preservada:

Visualizações físicas de linhas: as músicas são intercaladas em "Albums", que são intercaladas entre "Singers"

Layout físico das linhas das tabelas Singers, Albums e Songs, que formam uma hierarquia de tabelas intercaladas.

Em resumo, uma tabela pai e todas as tabelas filho e descendentes formam uma hierarquia de tabelas no esquema. Ainda que 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 do disco.

Se possível, vincule dados em tabelas intercaladas por chave principal. Todas as linhas intercaladas têm a garantia de serem armazenadas fisicamente na mesma divisão da sua linha pai. Por isso, o Cloud Spanner pode realizar junções por chave primária localmente, minimizando o acesso ao disco e o tráfego de rede. No exemplo a seguir, Singers e Albums são unidos na chave primária, SingerId:

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

As tabelas intercaladas no Cloud Spanner não são obrigatórias, mas são recomendadas para tabelas com fortes relacionamentos de localidade de dados. Evite as tabelas intercaladas se houver uma possibilidade de que o tamanho de uma única linha e dos descendentes dela se torne maior do que alguns poucos GB.

Colunas de chave

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

Como armazenar NULLs

Colunas de chave primária podem ser definidas para armazenar NULLs. Se você quiser armazenar NULLs em uma coluna de chave primária, omita a cláusula NOT NULL para essa coluna no esquema.

Veja um exemplo de omissão da cláusula NOT NULL na coluna da chave primária SingerId. Observe que, como SingerId é a chave primária, pode haver no máximo apenas uma linha na tabela Singers 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 pai e as da tabela filho. Neste exemplo, Albums.SingerId INT64 NOT NULL não é permitido. A instrução de chave precisa omitir a cláusula NOT NULL 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,  -- NOT ALLOWED!
  AlbumId      INT64 NOT NULL,
  AlbumTitle   STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

Tipos não permitidos

Os itens a seguir não podem ser do tipo ARRAY:

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

Como desenvolver um design para multilocação

Caso estejam sendo armazenados dados pertencentes a diferentes clientes, talvez seja interessante fornecer multilocação. Por exemplo, um serviço de música pode querer armazenar a gravadora de cada indivíduo de forma separada.

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
3MarcRichards
4GabrielWright
Banco de dados 3: Eagan Records
SingerId FirstName LastName
1BenjaminMartinez
2HannahHarris

A maneira recomendada de desenvolver um design para multilocação no Cloud Spanner é usar um valor de chave primária diferente para cada cliente. Você inclui em suas tabelas uma coluna de chave CustomerId ou de uma chave semelhante. Se você tornar CustomerId a primeira coluna de chave, os dados de cada cliente terão boa localização. O Cloud Spanner divide automaticamente os dados por todos os nós com base no tamanho deles e nos padrões de carga. Neste exemplo, há uma única tabela Singers para todos os clientes:

Banco de dados de multilocação do Cloud Spanner
CustomerId SingerId FirstName LastName
11MarcRichards
12CatalinaSmith
23MarcRichards
24GabrielWright
31BenjaminMartinez
32HannahHarris

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

  • Existem limites para a quantidade de bancos de dados por instância e de tabelas por banco de dados. Dependendo do número de clientes, pode não ser possível ter bancos de dados ou tabelas separados.
  • Adicionar novas tabelas e índices não intercalados pode levar muito tempo. Pode não ser possível alcançar o desempenho desejado se o design do esquema depender da adição de novas tabelas e índices.

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

Se você criar tabelas e índices separados para cada cliente de seu aplicativo, não coloque todas as tabelas e índices no mesmo banco de dados. Em vez disso, divida-os entre vários bancos de dados para reduzir os problemas de desempenho com a criação de um grande número de índices. Há também limites para o número de tabelas e índices por banco de dados.

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Documentação do Cloud Spanner