Faça atualizações de esquemas

O Spanner permite-lhe fazer atualizações de esquemas sem períodos de inatividade. Pode atualizar o esquema de uma base de dados existente de várias formas:

Atualizações de esquemas suportadas

O Spanner suporta as seguintes atualizações de esquemas de uma base de dados existente:

  • Adicionar ou remover um esquema com nome.
  • Criar uma nova tabela. As colunas nas novas tabelas podem ser NOT NULL.
  • Eliminar uma tabela, se não existirem outras tabelas intercaladas na mesma e não tiver índices secundários.
  • Crie ou elimine uma tabela com uma chave externa.
  • Adicione ou remova uma chave externa de uma tabela existente.
  • Adicione uma coluna não principal a qualquer tabela. Não é possível criar novas colunas sem chave NOT NULL.
  • Eliminar uma coluna não principal de qualquer tabela, a menos que seja usada por um índice secundário, uma chave externa, uma coluna gerada armazenada ou uma restrição de verificação.
  • Adicione NOT NULL a uma coluna não principal, excluindo as colunas ARRAY.
  • Remova NOT NULL de uma coluna não principal.
  • Altere uma coluna STRING para uma coluna BYTES ou uma coluna BYTES para uma coluna STRING.
  • Altere uma coluna PROTO para uma coluna BYTES ou uma coluna BYTES para uma coluna PROTO.
  • Altere o tipo de mensagem proto de uma coluna PROTO.
  • Adicione novos valores a uma definição de ENUM e mude o nome dos valores existentes através de ALTER PROTO BUNDLE.
  • Alterar as mensagens definidas num PROTO BUNDLE de formas arbitrárias, desde que os campos modificados dessas mensagens não sejam usados como chaves em nenhuma tabela e que os dados existentes satisfaçam as novas restrições.
  • Aumentar ou diminuir o limite de comprimento para um tipo STRING ou BYTES (incluindo para MAX), a menos que seja uma coluna de chave primária herdada por uma ou mais tabelas secundárias.
  • Aumente ou diminua o limite de comprimento de uma coluna ARRAY<STRING>, ARRAY<BYTES> ou ARRAY<PROTO> para o máximo permitido.
  • Ative ou desative datas/horas de confirmação nas colunas de valor e chave primária.
  • Adicione ou remova um índice secundário.
  • Adicione ou remova uma restrição de verificação de uma tabela existente.
  • Adicione ou remova uma coluna gerada armazenada de uma tabela existente.
  • Construa um novo pacote de estatísticas do otimizador.
  • Crie e faça a gestão de visualizações de propriedades.
  • Crie e faça a gestão de sequências.
  • Crie funções de base de dados e conceda privilégios.
  • Defina, altere ou elimine o valor predefinido de uma coluna.
  • Altere as opções da base de dados (por exemplo, default_leader ou version_retention_period).
  • Crie e faça a gestão de fluxos de alterações.
  • Crie e faça a gestão de modelos de AA.

Atualizações de esquemas não suportadas

O Spanner não suporta as seguintes atualizações de esquemas de uma base de dados existente:

  • Se existir um campo PROTO do tipo ENUM referenciado por uma chave de tabela ou de índice, não pode remover valores ENUM das enumerações proto. (A remoção de valores ENUM de enumerações usadas por colunas ENUM<> é suportada, inclusive quando essas colunas são usadas como chaves.)

Desempenho da atualização do esquema

As atualizações de esquemas no Spanner não requerem tempo de inatividade. Quando emite um lote de declarações DDL para uma base de dados do Spanner, pode continuar a escrever e ler na base de dados sem interrupções enquanto o Spanner aplica a atualização como uma operação de longa duração.

O tempo necessário para executar uma declaração DDL depende de a atualização exigir a validação dos dados existentes ou o preenchimento de quaisquer dados. Por exemplo, se adicionar a anotação NOT NULL a uma coluna existente, o Spanner tem de ler todos os valores na coluna para se certificar de que a coluna não contém valores NULL. Este passo pode demorar muito tempo se houver muitos dados a validar. Outro exemplo é se estiver a adicionar um índice a uma base de dados: o Spanner preenche o índice com dados existentes e esse processo pode demorar muito tempo, consoante a definição do índice e o tamanho da tabela base correspondente. No entanto, se adicionar uma nova coluna a uma tabela, não existem dados para validar, pelo que o Spanner pode fazer a atualização mais rapidamente.

Em resumo, as atualizações do esquema que não exigem que o Spanner valide os dados existentes podem ocorrer em minutos. As atualizações do esquema que requerem validação podem demorar mais tempo, consoante a quantidade de dados existentes que precisam de ser validados, mas a validação de dados ocorre em segundo plano com uma prioridade inferior à do tráfego de produção. As atualizações do esquema que requerem a validação de dados são abordadas mais detalhadamente na secção seguinte.

Atualizações do esquema validadas em relação às definições de visualização

Quando faz uma atualização do esquema, o Spanner valida se a atualização não invalida as consultas usadas para definir as vistas existentes. Se a validação for bem-sucedida, a atualização do esquema é bem-sucedida. Se a validação não for bem-sucedida, a atualização do esquema falha. Consulte as práticas recomendadas ao criar vistas para ver detalhes.

Atualizações do esquema que requerem validação de dados

Pode fazer atualizações ao esquema que exigem a validação de que os dados existentes cumprem as novas restrições. Quando uma atualização do esquema requer validação de dados, o Spanner não permite atualizações do esquema em conflito com as entidades do esquema afetadas e valida os dados em segundo plano. Se a validação for bem-sucedida, a atualização do esquema é bem-sucedida. Se a validação não for bem-sucedida, a atualização do esquema não é concluída. As operações de validação são executadas como operações de longa duração. Pode verificar o estado destas operações para determinar se foram bem-sucedidas ou falharam.

Por exemplo, suponha que definiu o seguinte ficheiro music.proto com uma enumeração RecordLabel e uma mensagem de protocolo Songwriter:

  enum RecordLabel {
    COOL_MUSIC_INC = 0;
    PACIFIC_ENTERTAINMENT = 1;
    XYZ_RECORDS = 2;
  }

  message Songwriter {
    required string nationality   = 1;
    optional int64  year_of_birth = 2;
  }

Para adicionar uma tabela Songwriters no seu esquema:

GoogleSQL

CREATE PROTO BUNDLE (
  googlesql.example.music.Songwriter,
  googlesql.example.music.RecordLabel,
);

CREATE TABLE Songwriters (
  Id         INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  Nickname   STRING(MAX),
  OpaqueData BYTES(MAX),
  SongWriter googlesql.example.music.Songwriter
) PRIMARY KEY (Id);

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

As seguintes atualizações de esquemas são permitidas, mas requerem validação e podem demorar mais tempo a serem concluídas, consoante a quantidade de dados existentes:

  • Adicionar a anotação NOT NULL a uma coluna não principal. Por exemplo:

    ALTER TABLE Songwriters ALTER COLUMN Nickname STRING(MAX) NOT NULL;
    
  • Reduzir o comprimento de uma coluna. Por exemplo:

    ALTER TABLE Songwriters ALTER COLUMN FirstName STRING(10);
    
  • Alterando de BYTES para STRING. Por exemplo:

    ALTER TABLE Songwriters ALTER COLUMN OpaqueData STRING(MAX);
    
  • Alterando de INT64/INT32 para ENUM. Por exemplo:

    ALTER TABLE Albums ALTER COLUMN Label googlesql.example.music.RecordLabel;
    
  • Remover valores existentes da definição de enumeração RecordLabel.

  • Ativar datas/horas de confirmação numa coluna TIMESTAMP existente. Por exemplo:

    ALTER TABLE Albums ALTER COLUMN LastUpdateTime SET OPTIONS (allow_commit_timestamp = true);
    
  • Adicionar uma restrição de verificação a uma tabela existente.

  • Adicionar uma coluna gerada armazenada a uma tabela existente.

  • Criar uma nova tabela com uma chave externa.

  • Adicionar uma chave externa a uma tabela existente.

Estas atualizações do esquema falham se os dados subjacentes não cumprirem as novas restrições. Por exemplo, a declaração ALTER TABLE Songwriters ALTER COLUMN Nickname STRING(MAX) NOT NULL falha se qualquer valor na coluna Nickname for NULL, porque os dados existentes não cumprem a restrição NOT NULL da nova definição.

A validação de dados pode demorar de vários minutos a muitas horas. O tempo necessário para concluir a validação de dados depende do seguinte:

  • O tamanho do conjunto de dados
  • A capacidade de computação da instância
  • A carga na instância

Algumas atualizações de esquemas podem alterar o comportamento dos pedidos à base de dados antes de a atualização do esquema ser concluída. Por exemplo, se adicionar NOT NULL a uma coluna, o Spanner começa quase imediatamente a rejeitar gravações para novos pedidos que usem NULL para a coluna. Se a nova atualização do esquema falhar na validação de dados, terá existido um período em que as gravações foram bloqueadas, mesmo que tivessem sido aceites pelo esquema antigo.

Pode cancelar uma operação de validação de dados de execução prolongada através do método projects.instances.databases.operations.cancel ou através de gcloud spanner operations.

Ordem de execução das declarações em lotes

Se usar a CLI Google Cloud, a API REST ou a API RPC, pode emitir um lote de uma ou mais CREATE, ALTER ou DROP declarações.

O Spanner aplica as declarações do mesmo lote por ordem, parando no primeiro erro. Se a aplicação de uma declaração resultar num erro, essa declaração é revertida. Os resultados de quaisquer declarações aplicadas anteriormente no lote não são revertidos. Esta aplicação de declaração por ordem significa que, se quiser que determinadas declarações sejam executadas em paralelo, como preenchimentos de índice que podem demorar muito tempo, deve enviar essas declarações em lotes separados.

O Spanner pode combinar e reordenar declarações de diferentes lotes, misturando potencialmente declarações de diferentes lotes numa única alteração atómica que é aplicada à base de dados. Em cada alteração atómica, as declarações de diferentes lotes ocorrem numa ordem arbitrária. Por exemplo, se um lote de declarações contiver ALTER TABLE MyTable ALTER COLUMN MyColumn STRING(50) e outro lote de declarações contiver ALTER TABLE MyTable ALTER COLUMN MyColumn STRING(20), o Spanner deixa essa coluna num desses dois estados, mas não é especificado qual.

Versões do esquema criadas durante as atualizações do esquema

O Spanner usa o controlo de versões do esquema para que não haja tempo de inatividade durante uma atualização do esquema a uma base de dados grande. O Spanner mantém a versão do esquema mais antiga para suportar as leituras enquanto a atualização do esquema é processada. O Spanner cria, em seguida, uma ou mais novas versões do esquema para processar a atualização do esquema. Cada versão contém o resultado de uma coleção de declarações numa única alteração atómica.

As versões do esquema não correspondem necessariamente individualmente a lotes de declarações DDL ou a declarações DDL individuais. Algumas declarações DDL individuais, como a criação de índices para tabelas base existentes ou declarações que requerem validação de dados, resultam em várias versões do esquema. Noutros casos, várias declarações DDL podem ser agrupadas numa única versão. As versões antigas do esquema podem consumir recursos significativos do servidor e de armazenamento, e são retidas até expirarem (já não são necessárias para servir leituras de versões anteriores dos dados).

A tabela seguinte mostra quanto tempo o Spanner demora a atualizar um esquema.

Operação de esquema Duração estimada
CREATE TABLE Minuto
CREATE INDEX

Minutos a horas, se a tabela base for criada antes do índice.

Minutos, se a declaração for executada ao mesmo tempo que a declaração CREATE TABLE para a tabela base.

DROP TABLE Minuto
DROP INDEX Minuto
ALTER TABLE ... ADD COLUMN Minuto
ALTER TABLE ... ALTER COLUMN

Minutos a horas, se for necessária a validação em segundo plano.

Minutos, se a validação em segundo plano não for necessária.

ALTER TABLE ... DROP COLUMN Minuto
ANALYZE

Minutos a horas, consoante o tamanho da base de dados.

Alterações de tipos de dados e streams de alterações

Se alterar o tipo de dados de uma coluna que um fluxo de alterações monitoriza, o campo column_types dos registos do fluxo de alterações relevantes subsequentes reflete o novo tipo, tal como os dados JSON no campo old_values dos registos.mods

O new_values do campo mods de um registo de fluxo de alterações corresponde sempre ao tipo atual de uma coluna. A alteração do tipo de dados de uma coluna monitorizada não afeta os registos da stream de alterações anteriores a essa alteração.

No caso específico de uma alteração de BYTESparaSTRING, o Spanner valida os valores antigos da coluna como parte da atualização do esquema. Como resultado, o Spanner descodificou com segurança os valores do tipoBYTES antigo em strings no momento em que escreve quaisquer registos de stream de alterações subsequentes.

Práticas recomendadas para atualizações de esquemas

As secções seguintes descrevem as práticas recomendadas para atualizar esquemas.

Procedimentos antes de emitir a atualização do esquema

Antes de emitir uma atualização do esquema:

  • Verifique se todos os dados existentes na base de dados que está a alterar cumprem as restrições que a atualização do esquema está a impor. Uma vez que o êxito de alguns tipos de atualizações de esquemas depende dos dados na base de dados e não apenas do respetivo esquema atual, uma atualização de esquema bem-sucedida de uma base de dados de teste não garante uma atualização de esquema bem-sucedida de uma base de dados de produção. Seguem-se alguns exemplos comuns:

    • Se estiver a adicionar uma anotação NOT NULL a uma coluna existente, verifique se a coluna não contém valores NULL existentes.
    • Se estiver a reduzir o comprimento permitido de uma coluna STRING ou BYTES, verifique se todos os valores existentes nessa coluna cumprem a restrição de comprimento.
  • Se estiver a escrever numa coluna, numa tabela ou num índice que esteja a ser atualizado no esquema, certifique-se de que os valores que está a escrever cumprem as novas restrições.

  • Se estiver a eliminar uma coluna, uma tabela ou um índice, certifique-se de que não está a escrever nem a ler a partir dele.

Limite a frequência das atualizações do esquema

Se fizer demasiadas atualizações do esquema num curto período, o Spanner pode throttle o processamento das atualizações do esquema em fila. Isto deve-se ao facto de o Spanner limitar a quantidade de espaço para armazenar versões do esquema. A atualização do esquema pode ser limitada se existirem demasiadas versões antigas do esquema dentro do período de retenção. A taxa máxima de alterações ao esquema depende de muitos fatores, um dos quais é o número total de colunas na base de dados. Por exemplo, uma base de dados com 2000 colunas (aproximadamente 2000 linhas em INFORMATION_SCHEMA.COLUMNS) pode fazer, no máximo, 1500 alterações ao esquema (menos se a alteração ao esquema exigir várias versões) dentro do período de retenção. Para ver o estado das atualizações de esquemas em curso, use o comando gcloud spanner operations list e filtre por operações do tipo DATABASE_UPDATE_DDL. Para cancelar uma atualização de esquema em curso, use o comando gcloud spanner operations cancel e especifique o ID da operação.

A forma como as suas declarações DDL são processadas em lote e a respetiva ordem em cada lote podem afetar o número de versões do esquema resultantes. Para maximizar o número de atualizações do esquema que pode realizar durante qualquer período, deve usar o processamento em lote que minimize o número de versões do esquema. Algumas regras gerais são descritas nas atualizações grandes.

Conforme descrito nas versões do esquema, algumas declarações DDL criam várias versões do esquema, e estas são importantes quando considera o processamento em lote e a ordem em cada lote. Existem dois tipos principais de declarações que podem criar várias versões do esquema:

  • Declarações que podem ter de preencher dados de índice, como CREATE INDEX
  • Declarações que podem ter de validar dados existentes, como adicionar NOT NULL

No entanto, estes tipos de declarações nem sempre criam várias versões do esquema. O Spanner tenta detetar quando estes tipos de declarações podem ser otimizados para evitar a utilização de várias versões do esquema, o que depende do processamento em lote. Por exemplo, uma declaração CREATE INDEX que ocorra no mesmo lote que uma declaração CREATE TABLE para a tabela base do índice, sem declarações intervenientes para outras tabelas, pode evitar a necessidade de preencher os dados do índice, porque o Spanner pode garantir que a tabela base está vazia no momento em que o índice é criado. A secção Atualizações grandes descreve como usar esta propriedade para criar muitos índices de forma eficiente.

Se não conseguir processar em lote as suas declarações DDL para evitar a criação de muitas versões de esquemas, deve limitar o número de atualizações de esquemas ao esquema de uma única base de dados no respetivo período de retenção. Aumente o período durante o qual faz atualizações do esquema para permitir que o Spanner remova versões anteriores do esquema antes de serem criadas novas versões.

  • Para alguns sistemas de gestão de bases de dados relacionais, existem pacotes de software que fazem uma longa série de atualizações de esquemas de atualização e reversão à base de dados em cada implementação de produção. Estes tipos de processos não são recomendados para o Spanner.
  • O Spanner está otimizado para usar chaves primárias para particionar dados para soluções multi-inquilino. As soluções multi-inquilino que usam tabelas separadas para cada cliente podem resultar num grande atraso de operações de atualização do esquema que demoram muito tempo a concluir.
  • As atualizações de esquemas que requerem validação ou preenchimento de índice usam mais recursos do servidor porque cada declaração cria várias versões do esquema internamente.

Opções para atualizações de esquemas grandes

A melhor forma de criar uma tabela e um grande número de índices nessa tabela é criá-los todos ao mesmo tempo, para que seja criada apenas uma versão do esquema. É uma prática recomendada criar os índices imediatamente após a tabela na lista de declarações DDL. Pode criar a tabela e os respetivos índices quando cria a base de dados ou num único lote grande de declarações. Se precisar de criar muitas tabelas, cada uma com muitos índices, pode incluir todas as declarações num único lote. Pode incluir vários milhares de declarações num único lote quando todas as declarações podem ser executadas em conjunto com uma única versão do esquema.

Quando uma declaração requer o preenchimento de dados de índice ou a realização da validação de dados, não pode ser executada numa única versão do esquema. Isto acontece para CREATE INDEXdeclarações quando a tabela base do índice já existe (porque foi criada num lote anterior de declarações DDL ou porque existia uma declaração no lote entre as declarações CREATE TABLE e CREATE INDEX que exigia várias versões do esquema). O Spanner requer que não existam mais de 10 declarações deste tipo num único lote. A criação de índices que requer o preenchimento de dados, em particular, usa várias versões de esquemas por índice e, por isso, é uma boa regra geral não criar mais de 3 novos índices que requeiram o preenchimento de dados por dia (independentemente da forma como são processados em lote, a menos que esse processamento em lote possa evitar o preenchimento de dados).

Por exemplo, este lote de declarações vai usar uma única versão do esquema:

GoogleSQL

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

CREATE INDEX SingersByFirstName ON Singers(FirstName);

CREATE INDEX SingersByLastName ON Singers(LastName);

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

CREATE INDEX AlbumsByTitle ON Albums(AlbumTitle);

Por outro lado, este lote vai usar muitas versões do esquema, porque UnrelatedIndex requer o preenchimento de dados (uma vez que a respetiva tabela base já tem de existir) e isso obriga todos os índices seguintes a também exigirem o preenchimento de dados (mesmo que estejam no mesmo lote que as respetivas tabelas base):

GoogleSQL

CREATE TABLE Singers (
SingerId   INT64 NOT NULL,
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);

CREATE INDEX UnrelatedIndex ON UnrelatedTable(UnrelatedIndexKey);

CREATE INDEX SingersByFirstName ON Singers(FirstName);

CREATE INDEX SingersByLastName ON Singers(LastName);

CREATE INDEX AlbumsByTitle ON Albums(AlbumTitle);

Seria melhor mover a criação de UnrelatedIndex para o final do lote ou para um lote diferente, para minimizar as versões do esquema.

Aguarde a conclusão dos pedidos de API

Quando fizer pedidos projects.instances.databases.updateDdl (API REST) ou UpdateDatabaseDdl (API RPC), use projects.instances.databases.operations.get (API REST) ou GetOperation (API RPC), respetivamente, para aguardar a conclusão de cada pedido antes de iniciar um novo pedido. Aguardar a conclusão de cada pedido permite que a sua aplicação acompanhe o progresso das atualizações do esquema. Também mantém a lista de atualizações de esquemas pendentes num tamanho gerível.

Carregamento em massa

Se estiver a carregar dados em massa para as tabelas depois de estas serem criadas, é normalmente mais eficiente criar índices depois de os dados serem carregados. Se estiver a adicionar vários índices, pode ser mais eficiente criar a base de dados com todas as tabelas e índices no esquema inicial, conforme descrito nas opções para atualizações grandes.