Apporta aggiornamenti allo schema

Spanner ti consente di aggiornare lo schema senza tempi di inattività. Puoi aggiornare lo schema di un database esistente in diversi modi:

Aggiornamenti dello schema supportati

Spanner supporta i seguenti aggiornamenti dello schema di un database esistente:

  • Aggiungi o elimina uno schema denominato.
  • Crea una nuova tabella. Le colonne delle nuove tabelle possono essere NOT NULL.
  • Elimina una tabella se non sono presenti altre tabelle intercalate al suo interno e non ha indici secondari.
  • Crea o elimina una tabella con una chiave esterna.
  • Aggiungi o rimuovi una chiave esterna da una tabella esistente.
  • Aggiungi una colonna non chiave a qualsiasi tabella. Le nuove colonne non chiave non possono essere NOT NULL.
  • Elimina una colonna non chiave da qualsiasi tabella, a meno che non venga utilizzata da un indice secondario, una chiave esterna, una colonna generata archiviata o un vincolo di controllo.
  • Aggiungi NOT NULL a una colonna non chiave, escluse le colonne ARRAY.
  • Rimuovi NOT NULL da una colonna non chiave.
  • Converti una colonna STRING in una colonna BYTES o una colonna BYTES in una colonna STRING.
  • Converti una colonna PROTO in una colonna BYTES o una colonna BYTES in una colonna PROTO.
  • Modifica il tipo di messaggio proto di una colonna PROTO.
  • Aggiungi nuovi valori a una definizione ENUM e rinomina i valori esistenti utilizzando ALTER PROTO BUNDLE.
  • Modifica i messaggi definiti in un PROTO BUNDLE in modo arbitrario, a condizione che i campi modificati di questi messaggi non vengano utilizzati come chiavi in nessuna tabella e che i dati esistenti soddisfino i nuovi vincoli.
  • Aumenta o diminuisci il limite di lunghezza per un tipo STRING o BYTES (incluso MAX), a meno che non si tratti di una colonna di chiave primaria ereditata da una o più tabelle figlio.
  • Aumenta o diminuisci il limite di lunghezza per una colonna ARRAY<STRING>, ARRAY<BYTES> o ARRAY<PROTO> fino al massimo consentito.
  • Attiva o disattiva i timestamp di commit nelle colonne valore e chiave primaria.
  • Aggiungi o rimuovi un indice secondario.
  • Aggiungi o rimuovi un vincolo di controllo da una tabella esistente.
  • Aggiungi o rimuovi una colonna generata archiviata da una tabella esistente.
  • Crea un nuovo pacchetto di statistiche dello strumento di ottimizzazione.
  • Crea e gestisci le visualizzazioni.
  • Crea e gestisci sequenze.
  • Crea ruoli di database e concedi privilegi.
  • Imposta, modifica o elimina il valore predefinito di una colonna.
  • Modifica le opzioni del database (default_leader o version_retention_period, ad esempio).
  • Crea e gestisci flussi di modifiche.
  • Crea e gestisci modelli ML.

Aggiornamenti dello schema non supportati

Spanner non supporta i seguenti aggiornamenti dello schema di un database esistente:

  • Se è presente un campo PROTO di tipo ENUM a cui fa riferimento una chiave di tabella o indice, non puoi rimuovere i valori ENUM dalle enumerazioni proto. È supportata la rimozione dei valori ENUM dagli enum utilizzati dalle colonne ENUM<>, anche quando queste colonne vengono utilizzate come chiavi.

Rendimento dell'aggiornamento dello schema

Gli aggiornamenti dello schema in Spanner non richiedono tempi di inattività. Quando invii un batch di istruzioni DDL a un database Spanner, puoi continuare a scrivere e leggere dal database senza interruzioni mentre Spanner applica l'aggiornamento come operazione a lunga esecuzione.

Il tempo necessario per eseguire un'istruzione DDL dipende dalla necessità o meno di convalidare i dati esistenti o di eseguire il backfill di dati durante l'aggiornamento. Ad esempio, se aggiungi l'annotazione NOT NULL a una colonna esistente, Spanner deve leggere tutti i valori della colonna per assicurarsi che non contenga valori NULL. Questo passaggio può richiedere tempi lunghi in presenza di grandi quantità di dati da convalidare. Un altro esempio è l'aggiunta di un indice a un database: Spanner esegue il backfill dell'indice utilizzando i dati esistenti e questo processo può richiedere molto tempo a seconda della definizione dell'indice e delle dimensioni della tabella di base corrispondente. Tuttavia, se aggiungi una nuova colonna a una tabella, non ci sono dati esistenti da convalidare, quindi Spanner può eseguire l'aggiornamento più rapidamente.

In sintesi, gli aggiornamenti dello schema che non richiedono la convalida dei dati esistenti da parte di Spanner possono essere eseguiti in pochi minuti. Gli aggiornamenti dello schema che richiedono la convalida possono richiedere più tempo, a seconda della quantità di dati esistenti da convalidare, ma la convalida dei dati avviene in background con una priorità inferiore rispetto al traffico di produzione. Gli aggiornamenti dello schema che richiedono la convalida dei dati sono descritti in maggiore dettaglio nella sezione successiva.

Aggiornamenti dello schema convalidati in base alle definizioni delle visualizzazioni

Quando esegui un aggiornamento dello schema, Spanner verifica che l'aggiornamento non invalidi le query utilizzate per definire le viste esistenti. Se la convalida ha esito positivo, l'aggiornamento dello schema viene eseguito correttamente. Se la convalida non va a buon fine, l'aggiornamento dello schema non viene eseguito. Consulta Best practice per la creazione di viste per i dettagli.

Aggiornamenti dello schema che richiedono la convalida dei dati

Puoi apportare aggiornamenti dello schema che richiedono la convalida dei dati esistenti per verificare che soddisfino i nuovi vincoli. Quando un aggiornamento dello schema richiede la convalida dei dati, Spanner non consente aggiornamenti dello schema in conflitto alle entità dello schema interessate e convalida i dati in background. Se la convalida viene eseguita correttamente, l'aggiornamento dello schema va a buon fine. Se la convalida non va a buon fine, l'aggiornamento dello schema non viene eseguito. Le operazioni di convalida vengono eseguite come operazioni a lunga esecuzione. Puoi controllare lo stato di queste operazioni per determinare se sono state eseguite o meno.

Ad esempio, supponiamo di aver definito il seguente file music.proto con un'enumerazione RecordLabel e un messaggio di protocollo 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;
  }

Per aggiungere una tabella Songwriters allo schema:

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);

Sono consentiti i seguenti aggiornamenti dello schema, ma richiedono la convalida e potrebbero richiedere più tempo per essere completati, a seconda della quantità di dati esistenti:

  • Aggiunta dell'annotazione NOT NULL a una colonna non chiave. Ad esempio:

    ALTER TABLE Songwriters ALTER COLUMN Nickname STRING(MAX) NOT NULL;
    
  • Riduzione della lunghezza di una colonna. Ad esempio:

    ALTER TABLE Songwriters ALTER COLUMN FirstName STRING(10);
    
  • Modifica da BYTES a STRING. Ad esempio:

    ALTER TABLE Songwriters ALTER COLUMN OpaqueData STRING(MAX);
    
  • Modifica da INT64/INT32 a ENUM. Ad esempio:

    ALTER TABLE Albums ALTER COLUMN Label googlesql.example.music.RecordLabel;
    
  • Rimozione dei valori esistenti dalla definizione dell'enumerazione RecordLabel.

  • Abilitazione dei timestamp di commit in una colonna TIMESTAMP esistente. Ad esempio:

    ALTER TABLE Albums ALTER COLUMN LastUpdateTime SET OPTIONS (allow_commit_timestamp = true);
    
  • Aggiunta di un vincolo di controllo a una tabella esistente.

  • Aggiunta di una colonna generata archiviata a una tabella esistente.

  • Creazione di una nuova tabella con una chiave esterna.

  • Aggiunta di una chiave esterna a una tabella esistente.

Questi aggiornamenti dello schema non riescono se i dati sottostanti non soddisfano i nuovi vincoli. Ad esempio, l'istruzione ALTER TABLE Songwriters ALTER COLUMN Nickname STRING(MAX) NOT NULL non riesce se un valore nella colonna Nickname è NULL, perché i dati esistenti non soddisfano il vincolo NOT NULL della nuova definizione.

La convalida dei dati può richiedere da diversi minuti a molte ore. Il tempo necessario per completare la convalida dei dati dipende da:

  • Le dimensioni del set di dati
  • La capacità di calcolo dell'istanza
  • Il carico sull'istanza

Alcuni aggiornamenti dello schema possono modificare il comportamento delle richieste al database prima del completamento dell'aggiornamento dello schema. Ad esempio, se aggiungi NOT NULL a una colonna, Spanner inizia quasi immediatamente a rifiutare le scritture per le nuove richieste che utilizzano NULL per la colonna. Se il nuovo aggiornamento dello schema alla fine non riesce a convalidare i dati, si verificherà un periodo di tempo in cui le scritture sono state bloccate, anche se sarebbero state accettate dal vecchio schema.

Puoi annullare un'operazione di convalida dei dati a lunga esecuzione utilizzando il metodo projects.instances.databases.operations.cancel o utilizzando gcloud spanner operations.

Ordine di esecuzione delle istruzioni nei batch

Se utilizzi Google Cloud CLI, l'API REST o l'API RPC, puoi emettere un batch di una o più istruzioni CREATE, ALTER o DROP .

Spanner applica le istruzioni dello stesso batch in ordine, arrestandosi al primo errore. Se l'applicazione di un'istruzione genera un errore, l'istruzione viene annullata. I risultati di eventuali istruzioni applicate in precedenza nel batch non vengono ripristinati. Questa applicazione di istruzioni in ordine significa che se vuoi che determinate istruzioni vengano eseguite in parallelo, ad esempio i backfill degli indici che potrebbero richiedere molto tempo, devi inviarle in batch separati.

Spanner potrebbe combinare e riordinare le istruzioni di batch diversi, potenzialmente mescolando le istruzioni di batch diversi in una singola modifica atomica che viene applicata al database. All'interno di ogni modifica atomica, le istruzioni di batch diversi vengono eseguite in un ordine arbitrario. Ad esempio, se un batch di istruzioni contiene ALTER TABLE MyTable ALTER COLUMN MyColumn STRING(50) e un altro batch di istruzioni contiene ALTER TABLE MyTable ALTER COLUMN MyColumn STRING(20), Spanner lascerà la colonna in uno di questi due stati, ma non è specificato quale.

Versioni dello schema create durante gli aggiornamenti dello schema

Spanner utilizza il controllo delle versioni dello schema in modo che non si verifichino tempi di inattività durante un aggiornamento dello schema di un database di grandi dimensioni. Spanner mantiene la versione precedente dello schema per supportare le letture durante l'elaborazione dell'aggiornamento dello schema. Spanner crea quindi una o più nuove versioni dello schema per elaborare l'aggiornamento dello schema. Ogni versione contiene il risultato di una raccolta di istruzioni in una singola modifica atomica.

Le versioni dello schema non corrispondono necessariamente uno a uno a batch di istruzioni DDL o a singole istruzioni DDL. Alcune singole istruzioni DDL, come la creazione di indici per tabelle di base esistenti o istruzioni che richiedono la convalida dei dati, generano più versioni dello schema. In altri casi, più istruzioni DDL possono essere raggruppate in batch in un'unica versione. Le versioni precedenti dello schema possono consumare risorse significative di server e spazio di archiviazione e vengono conservate fino alla loro scadenza (non sono più necessarie per gestire le letture delle versioni precedenti dei dati).

La tabella seguente mostra il tempo necessario a Spanner per aggiornare uno schema.

Operazione dello schema Durata stimata
CREATE TABLE Minuti
CREATE INDEX

Da minuti a ore, se la tabella di base viene creata prima dell'indice.

Minuti, se l'istruzione viene eseguita contemporaneamente all'istruzione CREATE TABLE per la tabella di base.

DROP TABLE Minuti
DROP INDEX Minuti
ALTER TABLE ... ADD COLUMN Minuti
ALTER TABLE ... ALTER COLUMN

Da minuti a ore, se è necessaria la convalida in background.

Minuti, se la convalida in background non è richiesta.

ALTER TABLE ... DROP COLUMN Minuti
ANALYZE

Da pochi minuti a diverse ore, a seconda delle dimensioni del database.

Modifiche ai tipi di dati e modifiche in tempo reale

Se modifichi il tipo di dati di una colonna monitorata da uno stream di modifiche, il campo column_types dei successivi record dello stream di modifiche pertinenti riflette il nuovo tipo, così come i dati JSON old_values all'interno del campo mods dei record.

Il new_values del campo mods di un record di flusso di modifiche corrisponde sempre al tipo corrente di una colonna. La modifica del tipo di dati di una colonna monitorata non influisce sui record del flusso di modifiche precedenti alla modifica.

Nel caso specifico di una modifica da BYTES a STRING, Spanner convalida i vecchi valori della colonna nell'ambito dell'aggiornamento dello schema. Di conseguenza, Spanner ha decodificato in modo sicuro i vecchi valori di tipo BYTES in stringhe prima di scrivere qualsiasi record di stream delle modifiche successivo.

Best practice per gli aggiornamenti dello schema

Le sezioni seguenti descrivono le best practice per l'aggiornamento degli schemi.

Procedure prima di eseguire l'aggiornamento dello schema

Prima di eseguire un aggiornamento dello schema:

  • Verifica che tutti i dati esistenti nel database che stai modificando siano conformi ai limiti imposti dall'aggiornamento dello schema. Poiché la riuscita di alcuni tipi di aggiornamenti dello schema dipende dai dati nel database e non solo dal suo schema attuale, un aggiornamento dello schema riuscito di un database di test non garantisce un aggiornamento dello schema riuscito di un database di produzione. Ecco alcuni esempi comuni:

    • Se stai aggiungendo un'annotazione NOT NULL a una colonna esistente, verifica che la colonna non contenga valori NULL esistenti.
    • Se riduci la lunghezza consentita di una colonna STRING o BYTES, verifica che tutti i valori esistenti in quella colonna rispettino il vincolo di lunghezza.
  • Se stai scrivendo in una colonna, una tabella o un indice in fase di aggiornamento dello schema, assicurati che i valori che stai scrivendo soddisfino i nuovi vincoli.

  • Se stai eliminando una colonna, una tabella o un indice, assicurati di non continuare a eseguire operazioni di lettura o scrittura su questo elemento.

Limitare la frequenza degli aggiornamenti dello schema

Se esegui troppi aggiornamenti dello schema in un breve periodo di tempo, Spanner potrebbe throttle l'elaborazione degli aggiornamenti dello schema in coda. Questo perché Spanner limita lo spazio per l'archiviazione delle versioni dello schema. L'aggiornamento dello schema potrebbe essere limitato se sono presenti troppe versioni precedenti dello schema nel periodo di conservazione. La velocità massima di modifica dello schema dipende da molti fattori, uno dei quali è il numero totale di colonne nel database. Ad esempio, un database con 2000 colonne (circa 2000 righe in INFORMATION_SCHEMA.COLUMNS) è in grado di eseguire al massimo 1500 modifiche dello schema (meno se la modifica dello schema richiede più versioni) entro il periodo di conservazione. Per visualizzare lo stato degli aggiornamenti dello schema in corso, utilizza il comando gcloud spanner operations list e filtra in base alle operazioni di tipo DATABASE_UPDATE_DDL. Per annullare un aggiornamento dello schema in corso, utilizza il comando gcloud spanner operations cancel e specifica l'ID operazione.

Il modo in cui le istruzioni DDL vengono raggruppate in batch e il loro ordine all'interno di ogni batch possono influire sul numero di versioni dello schema risultanti. Per massimizzare il numero di aggiornamenti dello schema che puoi eseguire in un determinato periodo di tempo, devi utilizzare il batch che riduce al minimo il numero di versioni dello schema. Alcune regole pratiche sono descritte in Aggiornamenti di grandi dimensioni.

Come descritto nelle versioni dello schema, alcune istruzioni DDL creano più versioni dello schema, che sono importanti quando si considerano il batching e l'ordine all'interno di ogni batch. Esistono due tipi principali di istruzioni che potrebbero creare più versioni dello schema:

  • Istruzioni che potrebbero richiedere il riempimento dei dati dell'indice, come CREATE INDEX
  • Istruzioni che potrebbero richiedere la convalida dei dati esistenti, ad esempio l'aggiunta di NOT NULL

Questi tipi di istruzioni non sempre creano più versioni dello schema. Spanner tenterà di rilevare quando questi tipi di istruzioni possono essere ottimizzati per evitare l'utilizzo di più versioni dello schema, a seconda del batch. Ad esempio, un'istruzione CREATE INDEX che si verifica nello stesso batch di un'istruzione CREATE TABLE per la tabella di base dell'indice, senza istruzioni intermedie per altre tabelle, può evitare di dover eseguire il backfill dei dati dell'indice perché Spanner può garantire che la tabella di base sia vuota al momento della creazione dell'indice. La sezione Aggiornamenti di grandi dimensioni descrive come utilizzare questa proprietà per creare molti indici in modo efficiente.

Se non puoi raggruppare le istruzioni DDL per evitare di creare molte versioni dello schema, devi limitare il numero di aggiornamenti dello schema a un singolo schema di database entro il periodo di conservazione. Aumenta la finestra temporale in cui apporti gli aggiornamenti dello schema per consentire a Spanner di rimuovere le versioni precedenti dello schema prima di creare nuove versioni.

  • Per alcuni sistemi di gestione di database relazionali, esistono pacchetti software che eseguono una lunga serie di aggiornamenti e downgrade dello schema del database a ogni deployment di produzione. Questi tipi di processi non sono consigliati per Spanner.
  • Spanner è ottimizzato per utilizzare le chiavi primarie per partizionare i dati per le soluzioni multi-tenant. Le soluzioni multi-tenant che utilizzano tabelle separate per ogni cliente possono comportare un backlog elevato di operazioni di aggiornamento dello schema che richiedono molto tempo per essere completate.
  • Gli aggiornamenti dello schema che richiedono la convalida o il riempimento dell'indice utilizzano più risorse server perché ogni istruzione crea internamente più versioni dello schema.

Opzioni per aggiornamenti dello schema di grandi dimensioni

Il modo migliore per creare una tabella e un numero elevato di indici per la tabella è crearli tutti contemporaneamente, in modo che venga creata una sola versione dello schema. La best practice consiste nel creare gli indici immediatamente dopo la tabella nell'elenco delle istruzioni DDL. Puoi creare la tabella e i relativi indici quando crei il database o in un unico batch di istruzioni di grandi dimensioni. Se devi creare molte tabelle, ognuna con molti indici, puoi includere tutte le istruzioni in un unico batch. Puoi includere diverse migliaia di istruzioni in un singolo batch quando tutte le istruzioni possono essere eseguite insieme utilizzando una singola versione dello schema.

Quando un'istruzione richiede il riempimento dei dati dell'indice o l'esecuzione della convalida dei dati, non può essere eseguita in una singola versione dello schema. Ciò si verifica per le istruzioni CREATE INDEX quando la tabella di base dell'indice esiste già (perché è stata creata in un batch precedente di istruzioni DDL o perché nel batch era presente un'istruzione tra le istruzioni CREATE TABLE e CREATE INDEX che richiedeva più versioni dello schema). Spanner richiede che non ci siano più di 10 istruzioni di questo tipo in un singolo batch. La creazione di indici che richiedono il backfilling, in particolare, utilizza diverse versioni dello schema per indice, pertanto è una buona regola generale creare non più di tre nuovi indici che richiedono il backfilling al giorno (indipendentemente dal modo in cui vengono raggruppati, a meno che questo raggruppamento non possa evitare il backfilling).

Ad esempio, questo batch di istruzioni utilizzerà una singola versione dello schema:

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);

Al contrario, questo batch utilizzerà molte versioni dello schema, perché UnrelatedIndex richiede il riempimento (poiché la tabella di base deve già esistere) e ciò impone che anche tutti gli indici seguenti richiedano il riempimento (anche se si trovano nello stesso batch delle tabelle di 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);

Sarebbe meglio spostare la creazione di UnrelatedIndex alla fine del batch o in un batch diverso per ridurre al minimo le versioni dello schema.

Attendi il completamento delle richieste API

Quando effettui richieste projects.instances.databases.updateDdl (API REST) o UpdateDatabaseDdl (API RPC), utilizza projects.instances.databases.operations.get (API REST) o GetOperation (API RPC), rispettivamente, per attendere il completamento di ogni richiesta prima di iniziare una nuova richiesta. L'attesa del completamento di ogni richiesta consente alla tua applicazione di monitorare l'avanzamento degli aggiornamenti dello schema. Inoltre, mantiene il backlog degli aggiornamenti dello schema in attesa a una dimensione gestibile.

Caricamento collettivo

Se esegui il caricamento in blocco dei dati nelle tabelle dopo la creazione, in genere è più efficiente creare gli indici dopo aver caricato i dati. Se stai aggiungendo diversi indici, potrebbe essere più efficiente creare il database con tutte le tabelle e gli indici nello schema iniziale, come descritto nelle opzioni per gli aggiornamenti di grandi dimensioni.