Este documento descreve as práticas recomendadas para atualizar esquemas.
Procedimentos antes de iniciar a atualização do esquema
Antes de emitir uma atualização de esquema, realize as seguintes ações:
Verifique se todos os dados atuais no banco de dados obedecem às restrições introduzidas pela atualização do esquema. Como algumas atualizações de esquema dependem dos dados reais, e não apenas do esquema atual, uma atualização bem-sucedida em um banco de dados de teste não garante o sucesso em um banco de dados de produção. Veja alguns exemplos comuns:
- Se você estiver adicionando uma anotação
NOT NULLa uma coluna existente, verifique se ela não contém valoresNULL. - Se o comprimento permitido de uma coluna
STRINGouBYTESestiver sendo encurtado, confira se todos os valores existentes nessa coluna atendem à restrição de comprimento.
- Se você estiver adicionando uma anotação
Se você estiver gravando em uma coluna, tabela ou índice que estiver passando por uma atualização de esquema, verifique se os valores que estão sendo gravados atendem às novas restrições.
Se você estiver excluindo uma coluna, uma tabela ou um índice, confira se eles ainda não estão sendo usados para gravação ou leitura.
Limite a frequência das atualizações de esquema
Se você executar muitas atualizações de esquema em um curto período de tempo,
o Spanner poderá
throttle o
processamento das atualizações de esquema em fila. Isso acontece porque o Spanner limita o volume de espaço para armazenar versões de esquema. A atualização do esquema poderá ser limitada se houver muitas versões antigas do esquema no período de armazenamento. A taxa máxima de mudanças no esquema depende de muitos fatores, sendo um deles o número total de colunas no banco de dados. Por exemplo, um banco de dados com 2.000 colunas (aproximadamente 2.000 linhas em INFORMATION_SCHEMA.COLUMNS) executa no máximo 1.500 alterações de esquema (menos se a mudança de esquema exigir várias versões) dentro do período de armazenamento. Para ver o estado de
atualizações de esquema em andamento, 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 andamento,
use o comando gcloud spanner operations
cancel e especifique
o ID da operação.
A maneira como as instruções DDL são agrupadas e a ordem em cada lote pode afetar o número de versões de esquema resultantes. Para maximizar o número de atualizações de esquema que podem ser realizadas em qualquer período, use lotes que minimizem o número de versões de esquema. Algumas diretrizes são descritas em grandes atualizações.
Conforme descrito em versões de esquema, algumas instruções DDL criam várias versões de esquema, e isso é importante ao considerar a criação de lotes e a ordem em cada lote. Há dois tipos principais de instruções que podem criar várias versões de esquema:
- Instruções que podem precisar preencher dados de índice, como
CREATE INDEX. - Instruções que forçam o Spanner a validar dados atuais, como adicionar
NOT NULLou restrições de comprimento.
No entanto, esses tipos de instruções nem sempre criam várias versões de esquema. O Spanner tentará detectar quando esses tipos de instruções podem ser otimizados para evitar o uso de várias versões de esquema, o que depende do agrupamento. Por exemplo, uma instrução CREATE INDEX que ocorre no mesmo lote
que uma instrução CREATE TABLE para a tabela de base do índice, sem nenhuma
instrução intermediária para outras tabelas, pode evitar a necessidade de preencher os dados do índice
porque o Spanner pode garantir que a tabela de base esteja vazia
no momento em que o índice é criado. A seção grandes atualizações descreve como usar essa propriedade para criar muitos índices com eficiência.
Se não for possível agrupar as instruções DDL em lotes para evitar a criação de muitas versões de esquema, limite o número de atualizações de esquema a um único esquema de banco de dados no período de armazenamento. Aumente o tempo no qual você faz atualizações de esquema para permitir que o Spanner remova versões anteriores do esquema antes que novas versões sejam criadas.
- Para alguns sistemas de gerenciamento de bancos de dados relacional, há pacotes de software que fazem uma longa série de atualizações de esquema de atualização e downgrade do banco de dados em cada implantação de produção. Esses tipos de processos não são recomendados para o Spanner.
- O Spanner é otimizado para usar chaves primárias para particionar dados de soluções de multilocação. Se você usa uma solução de multilocação com tabelas separadas para cada cliente, saiba que as atualizações de esquema em vários clientes ao mesmo tempo podem resultar em um grande backlog de operações de atualização de esquema que levam muito tempo para serem concluídas.
- As atualizações de esquema que exigem validação ou preenchimento de índice usam mais recursos do servidor, porque cada instrução cria várias versões do esquema internamente.
Ordem de execução de instruções em lotes
Se você usar a Google Cloud CLI, a API REST ou a API RPC, emita um lote de uma ou mais instruções CREATE, ALTER ou DROP.
O Spanner aplica instruções do mesmo lote em ordem, parando no primeiro erro. Se a aplicação de uma instrução resultar em um erro, essa instrução será revertida. Os resultados de quaisquer instruções aplicadas anteriormente no lote não são revertidos. Isso significa que, se você quiser que instruções que exigem backfill inevitável sejam executadas em paralelo (como criar vários índices em tabelas grandes e existentes), envie essas instruções em lotes separados, porque cada backfill pode levar muito tempo. Se você estiver criando uma nova tabela com índices, a prática recomendada é colocar os comandos juntos (CREATE TABLE seguido de CREATE INDEX) em um único lote para evitar o preenchimento retroativo.
O Spanner pode combinar e reordenar instruções de diferentes lotes, misturando potencialmente instruções de diferentes lotes para a mesma alteração atômica aplicada ao banco de dados. Dentro de cada mudança atômica, as instruções de diferentes lotes ocorrem em uma ordem arbitrária. Por exemplo, se um lote de instruções contiver ALTER TABLE table_name ALTER COLUMN column_name STRING(50) e outro lote de instruções contiver ALTER TABLE table_name ALTER COLUMN
column_name STRING(20), o Spanner deixará essa coluna em um desses dois estados, mas o estado em que ela será deixada não é determinístico.
Opções para grandes atualizações de esquema
A melhor maneira de criar uma tabela e um grande número de índices nela é criar todos eles ao mesmo tempo, para que apenas uma versão de esquema seja criada. A prática recomendada é criar os índices imediatamente após a tabela na lista de instruções DDL. É possível criar a tabela e seus índices durante a criação do banco de dados ou em um único lote grande de instruções. Se você precisar criar muitas tabelas, cada uma com muitos índices, será possível incluir todas as instruções em um único lote. É possível incluir milhares de instruções em um único lote quando todas elas puderem ser executadas em conjunto usando uma única versão de esquema.
Quando uma instrução exige o preenchimento de dados de índice ou a realização de uma validação de dados,
não é possível executá-la em uma única versão de esquema. Isso acontece com instruções CREATE INDEX quando a tabela base do índice já existe, seja porque foi criada em um lote anterior de instruções DDL ou porque havia uma instrução no lote entre o CREATE TABLE e CREATE INDEX que
exigiam várias versões de esquema. O Spanner exige que não haja mais de 10 instruções em um único lote. A criação de índices que
requer preenchimento, usa várias versões de esquema por índice e
por isso é uma boa regra criar todos os três novos índices que exigem preenchimento por dia, independentemente de como elas são agrupadas, a menos que
esse lote possa evitar o preenchimento.
Por exemplo, este lote de instruções usará uma única versão de 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, esse lote usará muitas versões do esquema, porque UnrelatedIndex requer preenchimento (já que sua tabela base precisa ter sido existente) e isso força todos os índices a seguir a exigir também o preenchimento (mesmo que eles estejam no mesmo lote que as tabelas de 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 de esquema.
Aguardar a conclusão das solicitações da API
Ao fazer as solicitações projects.instances.databases.updateDdl (API REST) ou UpdateDatabaseDdl (API RPC), use projects.instances.databases.operations.get (API REST) ou GetOperation (API RPC), respectivamente, para aguardar a conclusão de cada solicitação antes de iniciar uma nova solicitação. Aguardar a conclusão de cada solicitação permite que seu aplicativo acompanhe o andamento das atualizações de esquema. Ele também mantém o backlog de atualizações de esquema pendentes em um tamanho gerenciável.
Carregamento em massa
Ao carregar dados em massa em uma nova tabela, é possível criar índices secundários antes ou depois de carregar os dados. O carregamento de dados é mais rápido se você criar índices depois do carregamento, mas isso significa que os índices precisam ser preenchidos.
Se você carregar os dados primeiro e depois criar os índices, a ingestão de dados será mais rápida porque apenas a tabela será gravada. Os preenchimentos posteriores de índice podem gravar os dados em lotes otimizados, que são mais eficientes do que gravar os dados de índice junto com os dados da tabela. No entanto, o preenchimento de índices requer várias versões de esquema e tem limites. Conforme observado em opções para atualizações grandes, crie no máximo 10 índices que exigem preenchimento em um único lote, e o ideal é criar no máximo 3 índices por dia.
Como alternativa, crie tabelas e índices no mesmo lote, conforme descrito em opções para grandes atualizações. Isso evita o backfilling de índice, mas o carregamento de dados em massa será mais lento porque cada índice precisa ser atualizado à medida que os dados são carregados.
A melhor opção em uma determinada situação depende da quantidade de dados que serão carregados, das chaves específicas de tabela e índice, de quantos índices são necessários e da frequência com que as operações de carga em massa serão necessárias no mesmo banco de dados. Uma regra prática é que é melhor criar os índices separadamente se uma grande quantidade de dados precisar ser carregada em cada tabela e apenas alguns índices forem necessários.