Best practice per l'aggiornamento dello schema

Questo documento descrive le best practice per l'aggiornamento degli schemi.

Procedure prima di iniziare l'aggiornamento dello schema

Prima di eseguire un aggiornamento dello schema:

  • Assicurati che tutti i dati esistenti nel database siano conformi ai vincoli introdotti dall'aggiornamento dello schema. Poiché alcuni aggiornamenti dello schema dipendono dai dati effettivi, non solo dallo schema attuale, un aggiornamento riuscito in un database di test non garantisce il successo in 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 la quantità di 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 allo schema (meno se la modifica allo 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 linee guida sono descritte negli aggiornamenti di grandi dimensioni.

Come descritto in Versioni dello schema, alcune istruzioni DDL creano più versioni dello schema, che sono importanti quando si prendono in considerazione 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, ad esempio CREATE INDEX.
  • Istruzioni che forzano Spanner a convalidare i dati esistenti, ad esempio l'aggiunta di vincoli NOT NULL o di lunghezza.

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. Se utilizzi una soluzione multi-tenant che utilizza tabelle separate per ogni cliente, tieni presente che gli aggiornamenti dello schema per molti clienti contemporaneamente 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 del server perché ogni istruzione crea internamente più versioni dello schema.

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ù CREATE, ALTER o DROP istruzioni.

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 le istruzioni che richiedono un backfill inevitabile vengano eseguite in parallelo (ad esempio la creazione di più indici su tabelle esistenti di grandi dimensioni), devi inviarle in batch separati, perché ogni backfill potrebbe richiedere molto tempo. Se invece stai creando una nuova tabella con indici, la best practice è inserirli insieme (CREATE TABLE seguito da CREATE INDEX) in un unico batch per evitare completamente il backfill.

Spanner potrebbe combinare e riordinare le istruzioni di batch diversi, potenzialmente mescolando le istruzioni di batch diversi in una singola modifica atomica 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 table_name ALTER COLUMN column_name STRING(50) e un altro batch di istruzioni contiene ALTER TABLE table_name ALTER COLUMN column_name STRING(20), Spanner lascerà la colonna in uno di questi due stati, ma lo stato in cui viene lasciata non è deterministico.

Opzioni per aggiornamenti di schemi 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 riempimento, in particolare, utilizza diverse versioni dello schema per indice, quindi è una buona regola generale creare non più di tre nuovi indici che richiedono il riempimento al giorno (indipendentemente da come vengono raggruppati, a meno che questo raggruppamento non possa evitare il riempimento).

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

Quando carichi in blocco i dati in una nuova tabella, puoi creare indici secondari prima o dopo il caricamento dei dati. Il caricamento dei dati è più rapido se crei gli indici dopo il caricamento, ma ciò significa che gli indici devono essere riempiti.

Se carichi prima i dati e poi crei gli indici, l'importazione dati è più veloce perché viene scritta solo la tabella e i riempimenti successivi degli indici possono scrivere i dati degli indici in batch ottimizzati più efficienti rispetto alla scrittura dei dati degli indici insieme ai dati della tabella. Tuttavia, il riempimento degli indici richiede più versioni dello schema e presenta dei limiti. Come indicato in Opzioni per aggiornamenti di grandi dimensioni, non devi creare più di 10 indici che richiedono il riempimento in un singolo batch ed è consigliabile non creare più di 3 indici di questo tipo al giorno.

In alternativa, puoi creare tabelle e indici nello stesso batch, come descritto in Opzioni per aggiornamenti di grandi dimensioni. In questo modo si evita il backfilling dell'indice, ma il caricamento collettivo dei dati sarà più lento perché ogni indice deve essere aggiornato man mano che i dati vengono caricati.

La scelta migliore in una determinata situazione dipende dalla quantità di dati da caricare, dalle chiavi specifiche di tabelle e indici, dal numero di indici necessari e dalla frequenza con cui saranno necessarie le operazioni di caricamento collettivo nello stesso database. Una regola generale è che è meglio creare gli indici separatamente se è necessario caricare una grande quantità di dati in ogni tabella e sono necessari solo pochi indici.