Livelli di isolamento e conflitti di scrittura in Azure Databricks

Il livello di isolamento di una tabella definisce il grado in cui una transazione deve essere isolata dalle modifiche apportate da operazioni simultanee. I conflitti di scrittura in Azure Databricks dipendono dal livello di isolamento.

Delta Lake offre garanzie di transazione ACID tra letture e scritture. Ciò significa che:

  • Più writer in più cluster possono modificare simultaneamente una partizione di tabella. I writer visualizzano una visualizzazione snapshot coerente della tabella e le scritture vengono eseguite in un ordine seriale.
    • I lettori continuano a visualizzare una visualizzazione snapshot coerente della tabella con cui è stato avviato il processo di Azure Databricks, anche quando una tabella viene modificata durante un processo.

Vedere Che cosa sono le garanzie ACID in Azure Databricks?.

Nota

Azure Databricks usa Delta Lake per tutte le tabelle per impostazione predefinita. Questo articolo descrive il comportamento per Delta Lake in Azure Databricks.

Importante

Le modifiche ai metadati causano l'esito negativo di tutte le operazioni di scrittura simultanee. Queste operazioni includono modifiche al protocollo di tabella, alle proprietà della tabella o allo schema dei dati.

Le letture di streaming hanno esito negativo quando rilevano un commit che modifica i metadati della tabella. Se si vuole che lo streaming continui, è necessario riavviarlo. Per i metodi consigliati, vedere Considerazioni sulla produzione per Structured Streaming.

Di seguito sono riportati esempi di query che modificano i metadati:

-- Set a table property.
ALTER TABLE table-name SET TBLPROPERTIES ('delta.isolationLevel' = 'Serializable')

-- Enable a feature using a table property and update the table protocol.
ALTER TABLE table_name SET TBLPROPERTIES ('delta.enableDeletionVectors' = true);

-- Drop a table feature.
ALTER TABLE table_name DROP FEATURE deletionVectors;

-- Upgrade to UniForm.
REORG TABLE table_name APPLY (UPGRADE UNIFORM(ICEBERG_COMPAT_VERSION=2));

-- Update the table schema.
ALTER TABLE table_name ADD COLUMNS (col_name STRING);

Conflitti di scrittura con concorrenza a livello di riga

La concorrenza a livello di riga riduce i conflitti tra le operazioni di scrittura simultanee rilevando le modifiche a livello di riga e risolvendo automaticamente i conflitti che si verificano quando si scrive simultaneamente l'aggiornamento o si eliminano righe diverse nello stesso file di dati.

La concorrenza a livello di riga è disponibile a livello generale in Databricks Runtime 14.2 e versioni successive. La concorrenza a livello di riga è supportata per impostazione predefinita per le condizioni seguenti:

  • Tabelle con vettori di eliminazione abilitati e senza partizionamento.
  • Tabelle con clustering liquido, a meno che non siano stati disabilitati i vettori di eliminazione.

Le tabelle con partizioni non supportano la concorrenza a livello di riga, ma possono comunque evitare conflitti tra OPTIMIZE e tutte le altre operazioni di scrittura quando i vettori di eliminazione sono abilitati. Vedere Limitazioni per la concorrenza a livello di riga.

Per altre versioni di Databricks Runtime, vedere Comportamento di anteprima della concorrenza a livello di riga (legacy).

Nella tabella seguente vengono descritte le coppie di operazioni di scrittura in conflitto in ogni livello di isolamento con la concorrenza a livello di riga abilitata.

Nota

Le tabelle con colonne Identity non supportano transazioni simultanee. Vedere Usare le colonne Identity in Delta Lake.

IN edizione Standard RT (1) UPDATE, DELETE, MERGE INTO OPTIMIZE
INSERT Impossibile conflitto
UPDATE, DELETE, MERGE INTO Impossibile conflitto in WriteSerializable. Può entrare in conflitto in Serializable durante la modifica della stessa riga; vedere Limitazioni per la concorrenza a livello di riga. MERGE INTO richiede Photon per la risoluzione dei conflitti a livello di riga. Può essere in conflitto quando si modifica la stessa riga; vedere Limitazioni per la concorrenza a livello di riga.
OPTIMIZE Impossibile conflitto Impossibile conflitto Impossibile conflitto

Importante

(1) Tutte le INSERT operazioni nelle tabelle precedenti descrivono le operazioni di accodamento che non leggono dati dalla stessa tabella prima del commit. INSERT le operazioni che contengono sottoquery che leggono la stessa tabella supportano la stessa concorrenza di MERGE.

Conflitti di scrittura senza concorrenza a livello di riga

Nella tabella seguente vengono descritte le coppie di operazioni di scrittura in conflitto in ogni livello di isolamento.

Le tabelle non supportano la concorrenza a livello di riga se hanno partizioni definite o non dispongono di vettori di eliminazione abilitati. Databricks Runtime 14.2 o versione successiva è necessario per la concorrenza a livello di riga.

Nota

Le tabelle con colonne Identity non supportano transazioni simultanee. Vedere Usare le colonne Identity in Delta Lake.

IN edizione Standard RT (1) UPDATE, DELETE, MERGE INTO OPTIMIZE
INSERT Impossibile conflitto
UPDATE, DELETE, MERGE INTO Impossibile conflitto in WriteSerializable. Può entrare in conflitto in Serializable; vedere evitare conflitti con le partizioni. Può conflitto in Serializable e WriteSerializable; vedere evitare conflitti con le partizioni.
OPTIMIZE Impossibile conflitto Impossibile entrare in conflitto con nelle tabelle con vettori di eliminazione abilitati. In caso contrario, può essere in conflitto. Impossibile entrare in conflitto con nelle tabelle con vettori di eliminazione abilitati. In caso contrario, può essere in conflitto.

Importante

(1) Tutte le INSERT operazioni nelle tabelle precedenti descrivono le operazioni di accodamento che non leggono dati dalla stessa tabella prima del commit. INSERT le operazioni che contengono sottoquery che leggono la stessa tabella supportano la stessa concorrenza di MERGE.

Limitazioni per la concorrenza a livello di riga

Alcune limitazioni si applicano per la concorrenza a livello di riga. Per le operazioni seguenti, la risoluzione dei conflitti segue la normale concorrenza per i conflitti di scrittura in Azure Databricks. Vedere Conflitti di scrittura senza concorrenza a livello di riga.

  • OPTIMIZE comandi con ZORDER BY.
  • Comandi con clausole condizionali complesse, tra cui:
    • Condizioni su tipi di dati complessi, ad esempio struct, matrici o mappe.
    • Condizioni che usano espressioni non deterministiche e sottoquery.
    • Condizioni che contengono sottoquery correlate.
  • Per MERGE i comandi, è necessario usare un predicato esplicito nella tabella di destinazione per filtrare le righe corrispondenti alla tabella di origine. Per la risoluzione di merge, il filtro viene usato per analizzare solo le righe che potrebbero essere in conflitto in base alle condizioni di filtro nelle operazioni simultanee.

Nota

Il rilevamento dei conflitti a livello di riga può aumentare il tempo di esecuzione totale. Nel caso di molte transazioni simultanee, il writer assegna priorità alla latenza sulla risoluzione dei conflitti e sui conflitti.

Si applicano anche tutte le limitazioni per i vettori di eliminazione. Vedere Limitazioni.

Quando viene eseguito il commit di Delta Lake senza leggere la tabella?

Le operazioni Delta Lake INSERT o append non leggono lo stato della tabella prima di eseguire il commit se vengono soddisfatte le condizioni seguenti:

  1. La logica viene espressa usando la INSERT logica SQL o la modalità di accodamento.
  2. La logica non contiene sottoquery o condizionali che fanno riferimento alla tabella di destinazione dell'operazione di scrittura.

Come in altri commit, Delta Lake convalida e risolve le versioni della tabella su commit usando metadati nel log delle transazioni, ma non viene effettivamente letta alcuna versione della tabella.

Nota

Molti modelli comuni usano MERGE operazioni per inserire dati in base alle condizioni della tabella. Sebbene sia possibile riscrivere questa logica usando INSERT istruzioni, se un'espressione condizionale fa riferimento a una colonna nella tabella di destinazione, queste istruzioni presentano le stesse limitazioni di concorrenza di MERGE.

Scrivere livelli di isolamento serializzabili e serializzabili

Il livello di isolamento di una tabella definisce il grado in cui una transazione deve essere isolata dalle modifiche apportate dalle transazioni simultanee. Delta Lake in Azure Databricks supporta due livelli di isolamento: Serializable e WriteSerializable.

  • Serializzabile: livello di isolamento più sicuro. Garantisce che le operazioni di scrittura di cui è stato eseguito il commit e che tutte le letture siano serializzabili. Le operazioni sono consentite purché esista una sequenza seriale di esecuzione una alla volta che genera lo stesso risultato visualizzato nella tabella. Per le operazioni di scrittura, la sequenza seriale corrisponde esattamente a quella illustrata nella cronologia della tabella.

  • WriteSerializable (impostazione predefinita): livello di isolamento più debole rispetto a Serializable. Garantisce solo che le operazioni di scrittura (ovvero non le letture) siano serializzabili. Tuttavia, questo è ancora più forte rispetto all'isolamento dello snapshot . WriteSerializable è il livello di isolamento predefinito perché offre un ottimo equilibrio tra coerenza e disponibilità dei dati per le operazioni più comuni.

    In questa modalità, il contenuto della tabella Delta può essere diverso da quello previsto dalla sequenza di operazioni visualizzate nella cronologia delle tabelle. Questo avviene perché questa modalità consente a determinate coppie di scritture simultanee (ad esempio, operazioni X e Y) di procedere in modo che il risultato sia come se Y fosse stato eseguito prima di X (ovvero serializzabile tra di essi) anche se la cronologia mostrerebbe che Y è stato eseguito dopo X. Per non consentire questo riordinamento, impostare il livello di isolamento della tabella su Serializzabile in modo che queste transazioni non riescano.

Le operazioni di lettura usano sempre l'isolamento dello snapshot. Il livello di isolamento scrittura determina se è possibile che un lettore visualizzi uno snapshot di una tabella, che in base alla cronologia"non esiste mai".

Per il livello serializzabile, un lettore vede sempre solo le tabelle conformi alla cronologia. Per il livello WriteSerializable, un lettore potrebbe visualizzare una tabella che non esiste nel log Delta.

Si consideri ad esempio txn1, un'eliminazione a esecuzione prolungata e txn2, che inserisce i dati eliminati da txn1. txn2 e txn1 completi e vengono registrati in tale ordine nella cronologia. In base alla cronologia, i dati inseriti in txn2 non dovrebbero esistere nella tabella. Per il livello serializzabile, un lettore non visualizzerà mai i dati inseriti da txn2. Tuttavia, per il livello WriteSerializable, un lettore potrebbe a un certo punto vedere i dati inseriti da txn2.

Per altre informazioni sui tipi di operazioni che possono entrare in conflitto tra loro in ogni livello di isolamento e sui possibili errori, vedere Evitare conflitti tramite partizionamento e condizioni di comando non contigue.

Impostare il livello di isolamento

Impostare il livello di isolamento usando il ALTER TABLE comando .

ALTER TABLE <table-name> SET TBLPROPERTIES ('delta.isolationLevel' = <level-name>)

dove <level-name> è Serializable o WriteSerializable.

Ad esempio, per modificare il livello di isolamento dal valore predefinito WriteSerializable a Serializable, eseguire:

ALTER TABLE <table-name> SET TBLPROPERTIES ('delta.isolationLevel' = 'Serializable')

Evitare conflitti usando il partizionamento e le condizioni di comando non contigue

In tutti i casi contrassegnati come "possono essere in conflitto", se le due operazioni saranno in conflitto dipende dal fatto che operano sullo stesso set di file. È possibile creare due set di file disgiunti partizionando la tabella dalle stesse colonne usate nelle condizioni delle operazioni. Ad esempio, i due comandi UPDATE table WHERE date > '2010-01-01' ... e DELETE table WHERE date < '2010-01-01' saranno in conflitto se la tabella non è partizionata per data, poiché entrambi possono tentare di modificare lo stesso set di file. Il partizionamento della tabella da date eviterà il conflitto. Di conseguenza, il partizionamento di una tabella in base alle condizioni comunemente usate nel comando può ridurre in modo significativo i conflitti. Tuttavia, il partizionamento di una tabella in base a una colonna con cardinalità elevata può causare altri problemi di prestazioni a causa del numero elevato di sottodirectory.

Eccezioni di conflitto

Quando si verifica un conflitto fra transazioni, si osserverà una delle eccezioni seguenti:

ConcurrentAppendException

Questa eccezione si verifica quando un'operazione simultanea aggiunge file nella stessa partizione (o ovunque in una tabella non partizionata) che l'operazione legge. Le aggiunte di file possono essere causate da INSERToperazioni , DELETE, UPDATEo MERGE .

Con il livello di isolamento predefinito di WriteSerializable, i file aggiunti da operazioni non vedentiINSERT (ovvero le operazioni che accodano i dati senza leggere dati) non sono in conflitto con alcuna operazione, anche se toccano la stessa partizione (o ovunque in una tabella non partizionata). Se il livello di isolamento è impostato su Serializable, le appendhe non vedenti potrebbero entrare in conflitto.

Questa eccezione viene spesso generata durante le operazioni simultanee DELETE, UPDATE, o MERGE . Anche se le operazioni simultanee possono aggiornare fisicamente directory di partizione diverse, una di esse può leggere la stessa partizione che l'altra aggiorna simultaneamente, causando così un conflitto. È possibile evitarlo rendendo esplicita la separazione nella condizione dell'operazione. Si consideri l'esempio seguente.

// Target 'deltaTable' is partitioned by date and country
deltaTable.as("t").merge(
    source.as("s"),
    "s.user_id = t.user_id AND s.date = t.date AND s.country = t.country")
  .whenMatched().updateAll()
  .whenNotMatched().insertAll()
  .execute()

Si supponga di eseguire il codice precedente simultaneamente per date o Paesi diversi. Poiché ogni processo lavora su una partizione indipendente nella tabella Delta di destinazione, non ci si aspetta alcun conflitto. Tuttavia, la condizione non è abbastanza esplicita, può analizzare l'intera tabella ed essere in conflitto con operazioni simultanee che aggiornano qualsiasi altra partizione. È invece possibile riscrivere l'istruzione per aggiungere una data e un Paese specifici alla condizione di merge, come illustrato nell'esempio seguente.

// Target 'deltaTable' is partitioned by date and country
deltaTable.as("t").merge(
    source.as("s"),
    "s.user_id = t.user_id AND s.date = t.date AND s.country = t.country AND t.date = '" + <date> + "' AND t.country = '" + <country> + "'")
  .whenMatched().updateAll()
  .whenNotMatched().insertAll()
  .execute()

Questa operazione ora è sicura per l'esecuzione simultanea in date e Paesi diversi.

ConcurrentDeleteReadException

Questa eccezione si verifica quando un'operazione simultanea ha eliminato un file letto dall'operazione. Le cause comuni sono un'operazione DELETE, UPDATEo MERGE che riscrive i file.

ConcurrentDeleteDeleteException

Questa eccezione si verifica quando un'operazione simultanea ha eliminato un file eliminato anche dall'operazione. Ciò potrebbe essere causato da due operazioni di compattazione simultanee che riscrivono gli stessi file.

MetadataChangedException

Questa eccezione si verifica quando una transazione simultanea aggiorna i metadati di una tabella Delta. Le cause comuni sono ALTER TABLE operazioni o scritture nella tabella Delta che aggiornano lo schema della tabella.

ConcurrentTransactionException

Se una query di streaming che usa la stessa posizione del checkpoint viene avviata più volte contemporaneamente e tenta di scrivere nella tabella Delta contemporaneamente. Non è mai necessario disporre di due query di streaming che usano lo stesso percorso del checkpoint ed eseguire contemporaneamente.

ProtocolChangedException

Questa eccezione può verificarsi nei casi seguenti:

  • Quando la tabella Delta viene aggiornata a una nuova versione del protocollo. Per un esito positivo delle operazioni future, potrebbe essere necessario aggiornare il runtime di Databricks.
  • Quando più writer creano o sostituiscono una tabella contemporaneamente.
  • Quando più writer scrivono contemporaneamente in un percorso vuoto.

Per altri dettagli, vedere Come azure Databricks gestisce la compatibilità delle funzionalità Delta Lake?

Comportamento di anteprima della concorrenza a livello di riga (legacy)

Questa sezione descrive i comportamenti di anteprima per la concorrenza a livello di riga in Databricks Runtime 14.1 e versioni successive. La concorrenza a livello di riga richiede sempre vettori di eliminazione.

In Databricks Runtime 13.3 LTS e versioni successive le tabelle con clustering liquido abilitato abilitano automaticamente la concorrenza a livello di riga.

In Databricks Runtime 14.0 e 14.1 è possibile abilitare la concorrenza a livello di riga per le tabelle con vettori di eliminazione impostando la configurazione seguente per il cluster o SparkSession:

spark.databricks.delta.rowLevelConcurrencyPreview = true

In Databricks Runtime 14.1 e versioni successive, il calcolo non Photon supporta solo la concorrenza a livello di riga per DELETE le operazioni.