Sessioni

Questa pagina descrive il concetto avanzato di sessioni in Spanner, tra cui le best practice per le sessioni durante la creazione di una libreria client, l'utilizzo delle API REST o RPC o dell'utilizzo delle librerie client di Google.

Panoramica delle sessioni

Una sessione rappresenta un canale di comunicazione con il servizio di database Spanner. Una sessione viene utilizzata per eseguire transazioni che leggono, scrivono o modificano i dati in un database Spanner. Ogni sessione si applica a un singolo database.

Le sessioni possono eseguire una sola transazione alla volta. Le letture, le scritture e le query indipendenti utilizzano una transazione interna e vengono conteggiate ai fini del limite di una transazione.

Vantaggi in termini di prestazioni di un pool di sessioni

Creare una sessione è costoso. Per evitare i costi delle prestazioni ogni volta che viene eseguita un'operazione del database, i client devono mantenere un pool di sessioni, ovvero un pool di sessioni disponibili pronte all'uso. Il pool deve archiviare le sessioni esistenti e restituire il tipo appropriato di sessione quando richiesto, nonché gestire la pulizia delle sessioni inutilizzate. Per un esempio di come implementare un pool di sessioni, consulta il codice sorgente di una delle librerie client di Spanner, come la libreria client Go o la libreria client Java.

Le sessioni sono pensate per durare a lungo, quindi dopo che una sessione viene utilizzata per un'operazione di database, il client deve restituire la sessione al pool per essere riutilizzata.

Panoramica dei canali gRPC

I canali gRPC vengono utilizzati dal client Spanner per la comunicazione. Un canale gRPC equivale approssimativamente a una connessione TCP. Un canale gRPC può gestire fino a 100 richieste in parallelo. Ciò significa che un'applicazione avrà bisogno di almeno un numero di canali gRPC pari al numero di richieste in parallelo che eseguirà, diviso per 100.

Il client Spanner creerà un pool di canali gRPC al momento della creazione.

Best practice per l'utilizzo delle librerie client di Google

Di seguito vengono descritte le best practice per l'utilizzo delle librerie client di Google per Spanner.

Configurare il numero di sessioni e canali gRPC nei pool

Le librerie client hanno un numero predefinito di sessioni nel pool di sessioni e un numero predefinito di canali gRPC nel pool di canali. Entrambi i valori predefiniti sono adeguati per la maggior parte dei casi. Di seguito sono riportati il numero minimo e massimo di sessioni predefiniti e il numero predefinito di canali gRPC per ciascun linguaggio di programmazione.

C++

MinSessions: 100
MaxSessions: 400
NumChannels: 4

C#

MinSessions: 100
MaxSessions: 400
NumChannels: 4

Go

MinSessions: 100
MaxSessions: 400
NumChannels: 4

Java

MinSessions: 100
MaxSessions: 400
NumChannels: 4

Node.js

Il client Node.js non supporta più canali gRPC. Consigliamo quindi di creare più client anziché aumentare le dimensioni del pool di sessioni oltre le 100 sessioni per un singolo client.

MinSessions: 25
MaxSessions: 100

PHP

Il client PHP non supporta un numero configurabile di canali gRPC.

MinSessions: 1
MaxSessions: 500

Python

Python supporta quattro diversi tipi di pool di sessioni che puoi utilizzare per gestire le sessioni.

Ruby

Il client Ruby non supporta più canali gRPC. Consigliamo quindi di creare più client anziché aumentare le dimensioni del pool di sessioni oltre le 100 sessioni per un singolo client.

MinSessions: 10
MaxSessions: 100

Il numero di sessioni utilizzate dalla tua applicazione corrisponde al numero di transazioni simultanee eseguite dall'applicazione. Devi modificare le impostazioni del pool di sessioni predefinito solo se prevedi che una singola istanza dell'applicazione esegua più transazioni in parallelo rispetto a quelle che il pool di sessioni predefinito può gestire.

Per le applicazioni ad alta contemporaneità è consigliabile quanto segue:

  1. Imposta MinSessions sul numero previsto di transazioni simultanee che verrà eseguita da un singolo client.
  2. Imposta MaxSessions sul numero massimo di transazioni simultanee che un singolo client può eseguire.
  3. Imposta MinSessions=MaxSessions se la contemporaneità prevista non cambia molto durante il ciclo di vita dell'applicazione. Ciò impedisce al pool di sessioni di aumentare o diminuire. Anche la scalabilità verticale del pool di sessioni richiede alcune risorse.
  4. Imposta NumChannels su MaxSessions / 100. Un canale gRPC può gestire fino a 100 richieste contemporaneamente. Aumenta questo valore se noti una latenza di coda elevata (latenza p95/p99), perché potrebbe indicare la congestione del canale gRPC.

L'aumento del numero di sessioni attive utilizza risorse aggiuntive nel servizio di database Spanner e nella libreria client. Aumentando il numero di sessioni oltre il bisogno effettivo dell'applicazione, le prestazioni del sistema potrebbero ridursi.

Aumentare il pool di sessioni rispetto all'aumento del numero di clienti

La dimensione del pool di sessioni per un'applicazione determina il numero di transazioni simultanee che può essere eseguita da una singola istanza dell'applicazione. Non è consigliabile aumentare le dimensioni del pool di sessioni oltre la contemporaneità massima gestibile da una singola istanza di applicazione. Se l'applicazione riceve un burst di richieste che supera il numero di sessioni nel pool, le richieste vengono messe in coda in attesa che una sessione diventi disponibile.

Le risorse utilizzate dalla libreria client sono le seguenti:

  1. Ogni canale gRPC utilizza una connessione TCP.
  2. Ogni chiamata gRPC richiede un thread. Il numero massimo di thread utilizzati dalla libreria client è uguale al numero massimo di query in esecuzione in parallelo che l'applicazione esegue. Questi thread si trovano sopra tutti i thread utilizzati dall'applicazione per la propria logica di business.

Non è consigliabile aumentare le dimensioni del pool di sessioni oltre il numero massimo di thread che una singola istanza dell'applicazione può gestire. Aumenta invece il numero di istanze dell'applicazione.

Gestisci la frazione di sessioni di scrittura

Per alcune librerie client, Spanner prenota una parte delle sessioni per le transazioni di lettura-scrittura, chiamata frazione delle sessioni di scrittura. Se la tua app utilizza tutte le sessioni di lettura, Spanner utilizza le sessioni di lettura-scrittura, anche per le transazioni di sola lettura. Le sessioni di lettura/scrittura richiedono spanner.databases.beginOrRollbackReadWriteTransaction. Se l'utente dispone del ruolo IAM spanner.databaseReader, la chiamata non riesce e Spanner restituisce questo messaggio di errore:

generic::permission_denied: Resource %resource% is missing IAM permission:
spanner.databases.beginOrRollbackReadWriteTransaction

Per le librerie client che mantengono una frazione di sessioni di scrittura, puoi impostare la frazione di sessioni di scrittura.

C++

Tutte le sessioni C++ sono le stesse. Non esistono sessioni di sola lettura o lettura/scrittura.

C#

La frazione predefinita di sessioni di scrittura per C# è 0,2. Puoi modificare la frazione utilizzando il campo WriteSessionsFraction di SessionPoolOptions.

Go

Tutte le sessioni Go sono le stesse. Non esistono sessioni di sola lettura o lettura/scrittura.

Java

Tutte le sessioni Java sono uguali. Non esistono sessioni di sola lettura o lettura/scrittura.

Node.js

Tutte le sessioni Node.js sono le stesse. Non esistono sessioni di sola lettura o lettura/scrittura.

PHP

Tutte le sessioni PHP sono uguali. Non esistono sessioni di sola lettura o lettura-scrittura.

Python

Python supporta quattro diversi tipi di pool di sessioni, che puoi utilizzare per gestire le sessioni di lettura e lettura e scrittura.

Ruby

La frazione predefinita di sessioni di scrittura per Ruby è 0,3. Puoi modificare la frazione utilizzando il metodo di inizializzazione client.

Best practice per la creazione di una libreria client o per l'utilizzo di REST/RPC

Di seguito vengono descritte le best practice per l'implementazione delle sessioni in una libreria client per Spanner o per l'utilizzo delle sessioni con le API REST o RPC.

Queste best practice si applicano solo se stai sviluppando una libreria client o se utilizzi API REST/RPC. Se utilizzi una delle librerie client di Google per Spanner, consulta le best practice per l'utilizzo delle librerie client di Google.

Crea e dimensiona il pool di sessioni

Per determinare la dimensione ottimale del pool di sessioni per un processo client, imposta il limite inferiore relativo al numero di transazioni simultanee previste e imposta il limite superiore su un numero di test iniziale, ad esempio 100. Se il limite superiore non è adeguato, aumentalo. L'aumento del numero di sessioni attive comporta l'utilizzo di risorse aggiuntive sul servizio di database Spanner, quindi la mancata pulizia delle sessioni inutilizzate può ridurre le prestazioni. Per gli utenti che lavorano con l'API RPC, consigliamo di non registrare più di 100 sessioni per canale gRPC.

Gestire le sessioni eliminate

Esistono tre modi per eliminare una sessione:

  • Un cliente può eliminare una sessione.
  • Il servizio di database Spanner può eliminare una sessione se questa è inattiva per più di 1 ora.
  • Il servizio di database Spanner può eliminare una sessione se questa ha più di 28 giorni.

I tentativi di utilizzare un risultato di sessione eliminato in NOT_FOUND. Se si verifica questo errore, crea e utilizza una nuova sessione, aggiungi la nuova sessione al pool e rimuovi la sessione eliminata dal pool.

Mantieni attiva una sessione inattiva

Il servizio di database Spanner si riserva il diritto di eliminare una sessione inutilizzata. Se hai sicuramente bisogno di mantenere attiva una sessione inattiva, ad esempio se prevedi un aumento a breve termine significativo dell'utilizzo del database, puoi evitare che la sessione venga interrotta. Eseguire un'operazione poco costosa, ad esempio eseguire la query SQL SELECT 1 per mantenere attiva la sessione. Se disponi di una sessione inattiva che non è necessaria per un utilizzo a breve termine, lascia che Spanner abbandoni la sessione e ne crei una nuova la prossima volta che ne serve una.

Uno scenario per mantenere attive le sessioni è gestire i picchi di domanda regolari nel database. Se l'utilizzo intensivo del database avviene ogni giorno dalle 9:00 alle 18:00, dovresti mantenere attive alcune sessioni inattive durante questo periodo, poiché probabilmente sono richieste per il picco di utilizzo. Dopo le 18:00, puoi consentire a Spanner di rilasciare le sessioni di inattività. Prima delle 9:00 di ogni giorno, crea nuove sessioni in modo che siano pronte per soddisfare la domanda prevista.

Un altro scenario è rappresentato da un'applicazione che utilizza Spanner, ma che deve evitare il sovraccarico della connessione. Puoi mantenere attivo un insieme di sessioni per evitare l'overhead della connessione.

Nascondi i dettagli della sessione all'utente della libreria client

Se stai creando una libreria client, non esporre le sessioni al consumer della libreria client. Offri al cliente la possibilità di effettuare chiamate al database senza la complessità di creazione e gestione delle sessioni. Per un esempio di libreria client che nasconde i dettagli della sessione al consumer della libreria client, consulta la libreria client Spanner per Java.

Gestire gli errori per le transazioni di scrittura non idempotenti

Le transazioni di scrittura senza protezione dalle repliche potrebbero applicare mutazioni più di una volta. Se una mutazione non è idempotente, una mutazione applicata più volte potrebbe causare un errore. Ad esempio, un inserimento potrebbe non riuscire con ALREADY_EXISTS anche se la riga non esisteva prima del tentativo di scrittura. Questo potrebbe verificarsi se il server di backend ha eseguito il commit della mutazione, ma non è stato in grado di comunicare l'esito positivo al client. In questo caso, è possibile riprovare la mutazione, con l'errore ALREADY_EXISTS.

Ecco i possibili modi per risolvere questo scenario quando implementi la tua libreria client o utilizzi l'API REST:

  • Struttura le tue scritture in modo che siano idempotenti.
  • Utilizza le scritture con protezione dalle repliche.
  • Implementa un metodo che esegua una logica "upsert": inserisci se è nuovo o aggiorna, se esistente.
  • Gestire l'errore per conto del client.

Mantieni connessioni stabili

Per ottenere le migliori prestazioni, la connessione utilizzata per ospitare una sessione deve rimanere stabile. Quando la connessione che ospita una sessione cambia, Spanner potrebbe interrompere la transazione attiva sulla sessione e causare una piccola quantità di carico aggiuntivo sul database durante l'aggiornamento dei metadati della sessione. Se alcune connessioni cambiano sporadicamente, va bene, ma è consigliabile evitare situazioni che potrebbero cambiare contemporaneamente un numero elevato di connessioni. Se utilizzi un proxy tra il client e Spanner, devi mantenere la stabilità della connessione per ogni sessione.

Monitora le sessioni attive

Puoi utilizzare il comando ListSessions per monitorare le sessioni attive nel database dalla riga di comando, con l'API REST o con l'API RPC. ListSessions mostra le sessioni attive di un determinato database. È utile per trovare la causa di una fuga di sessioni. (una fuga di sessioni è un incidente in cui le sessioni vengono create ma non restituite in un pool di sessioni per il riutilizzo.)

ListSessions consente di visualizzare i metadati sulle sessioni attive, tra cui la data di creazione e l'ultimo utilizzo di una sessione. L'analisi di questi dati ti indirizzerà nella giusta direzione durante la risoluzione dei problemi delle sessioni. Se la maggior parte delle sessioni attive non ha un valore approximate_last_use_time recente, questo potrebbe indicare che la tua applicazione non riutilizza correttamente le sessioni. Per ulteriori informazioni sul campo approximate_last_use_time, consulta il riferimento API RPC.

Consulta il riferimento sull'API REST, il riferimento sull'API RPC o il riferimento dello strumento a riga di comando gcloud per ulteriori informazioni sull'uso di ListSessions.

Pulizia automatica delle perdite dalle sessioni

Quando utilizzi tutte le sessioni nel pool di sessioni, ogni nuova transazione attende fino a quando una sessione non viene restituita nel pool. Quando le sessioni vengono create ma non vengono restituite al pool di sessioni per essere riutilizzate, si parla di perdita di sessione. In caso di perdita di sessione, le transazioni in attesa di una sessione aperta si bloccano a tempo e l'applicazione viene bloccata. Le perdite di sessioni sono spesso causate da transazioni problematiche che rimangono in esecuzione per un tempo estremamente lungo e non vengono confermate.

Puoi configurare il pool di sessioni per risolvere automaticamente queste transazioni non attive. Quando abiliti la libreria client per risolvere automaticamente la transizione inattiva, questa identifica le transazioni problematiche che potrebbero causare una perdita di sessioni, le rimuove dal pool di sessioni e le sostituisce con una nuova sessione.

Il logging consente inoltre di identificare queste transazioni problematiche. Se il logging è abilitato, i log degli avvisi vengono condivisi per impostazione predefinita quando è in uso oltre il 95% del pool di sessioni. Se l'utilizzo delle sessioni è superiore al 95%, devi aumentare il numero massimo di sessioni consentite nel pool di sessioni oppure potresti riscontrare una perdita di sessioni. I log di avviso contengono analisi dello stack di transazioni eseguite per più tempo del previsto e possono aiutarti a identificare la causa dell'elevato utilizzo del pool di sessioni. Il push dei log degli avvisi viene eseguito in base alla configurazione dell'utilità di esportazione dei log.

Attiva la libreria client per risolvere automaticamente le transazioni inattive

Puoi abilitare la libreria client per inviare log di avviso e risolvere automaticamente le transazioni inattive oppure abilitare la libreria client in modo che riceva solo i log di avviso.

Java

Per ricevere i log degli avvisi e rimuovere le transazioni inattive, utilizza setWarnAndCloseIfInactiveTransactions.

 final SessionPoolOptions sessionPoolOptions = SessionPoolOptions.newBuilder().setWarnAndCloseIfInactiveTransactions().build()

 final Spanner spanner =
         SpannerOptions.newBuilder()
             .setSessionPoolOption(sessionPoolOptions)
             .build()
             .getService();
 final DatabaseClient client = spanner.getDatabaseClient(databaseId);

Per ricevere solo i log di avviso, utilizza setWarnIfInactiveTransactions.

 final SessionPoolOptions sessionPoolOptions = SessionPoolOptions.newBuilder().setWarnIfInactiveTransactions().build()

 final Spanner spanner =
         SpannerOptions.newBuilder()
             .setSessionPoolOption(sessionPoolOptions)
             .build()
             .getService();
 final DatabaseClient client = spanner.getDatabaseClient(databaseId);

Go

Per ricevere log degli avvisi e rimuovere le transazioni non attive, utilizza SessionPoolConfig con InactiveTransactionRemovalOptions.

 client, err := spanner.NewClientWithConfig(
     ctx, database, spanner.ClientConfig{SessionPoolConfig: spanner.SessionPoolConfig{
         InactiveTransactionRemovalOptions: spanner.InactiveTransactionRemovalOptions{
         ActionOnInactiveTransaction: spanner.WarnAndClose,
         }
     }},
 )
 if err != nil {
     return err
 }
 defer client.Close()

Per ricevere solo i log degli avvisi, utilizza customLogger.

 customLogger := log.New(os.Stdout, "spanner-client: ", log.Lshortfile)
 // Create a logger instance using the golang log package
 cfg := spanner.ClientConfig{
         Logger: customLogger,
     }
 client, err := spanner.NewClientWithConfig(ctx, db, cfg)