Níveis de isolamento e conflitos de gravação no Azure Databricks
O nível de isolamento de uma tabela define o grau em que uma transação deve ser isolada das modificações feitas por operações simultâneas. Os conflitos de gravação no Azure Databricks dependem do nível de isolamento.
O Delta Lake fornece garantias de transação ACID entre leituras e escritas. Isto significa que:
- Vários gravadores em vários clusters podem modificar simultaneamente uma partição de tabela. Os gravadores veem uma exibição instantânea consistente da tabela e as gravações ocorrem em uma ordem serial.
- Os leitores continuam a ver uma exibição de instantâneo consistente da tabela com a qual o trabalho do Azure Databricks começou, mesmo quando uma tabela é modificada durante um trabalho.
Consulte O que são garantias ACID no Azure Databricks?.
Nota
O Azure Databricks usa o Delta Lake para todas as tabelas por padrão. Este artigo descreve o comportamento do Delta Lake no Azure Databricks.
Importante
As alterações de metadados fazem com que todas as operações de gravação simultâneas falhem. Essas operações incluem alterações no protocolo da tabela, nas propriedades da tabela ou no esquema de dados.
As leituras de streaming falham quando encontram uma confirmação que altera os metadados da tabela. Se quiser que o fluxo continue, tem de reiniciá-lo. Para obter os métodos recomendados, consulte Considerações de produção para streaming estruturado.
Seguem-se exemplos de consultas que alteram metadados:
-- 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);
Conflitos de gravação com simultaneidade em nível de linha
A simultaneidade em nível de linha reduz os conflitos entre operações de gravação simultâneas, detetando alterações no nível da linha e resolvendo automaticamente os conflitos que ocorrem quando gravações simultâneas atualizam ou excluem linhas diferentes no mesmo arquivo de dados.
A simultaneidade em nível de linha está geralmente disponível no Databricks Runtime 14.2 e superior. A simultaneidade em nível de linha é suportada por padrão para as seguintes condições:
- Tabelas com vetores de exclusão habilitados e sem particionamento.
- Tabelas com agrupamento líquido, a menos que você tenha desabilitado vetores de exclusão.
As tabelas com partições não suportam simultaneidade em nível de linha, mas ainda podem evitar conflitos entre OPTIMIZE
e todas as outras operações de gravação quando os vetores de exclusão estão habilitados. Consulte Limitações para simultaneidade em nível de linha.
Para outras versões do Databricks Runtime, consulte Comportamento de visualização de simultaneidade em nível de linha (legado).
MERGE INTO
o suporte para simultaneidade em nível de linha requer Photon no Databricks Runtime 14.2. No Databricks Runtime 14.3 LTS e superior, o Photon não é necessário.
A tabela a seguir descreve quais pares de operações de gravação podem entrar em conflito em cada nível de isolamento com simultaneidade em nível de linha habilitada.
Nota
As tabelas com colunas de identidade não suportam transações simultâneas. Consulte Usar colunas de identidade no Delta Lake.
INSERIR (1) | ATUALIZAR, EXCLUIR, MESCLAR EM | OPTIMIZE | |
---|---|---|---|
INSERT | Não pode entrar em conflito | ||
ATUALIZAR, EXCLUIR, MESCLAR EM | Não é possível entrar em conflito em WriteSerializable. Pode entrar em conflito em Serializable ao modificar a mesma linha. Consulte Limitações para simultaneidade em nível de linha. | Pode entrar em conflito ao modificar a mesma linha. Consulte Limitações para simultaneidade em nível de linha. | |
OPTIMIZE | Não pode entrar em conflito | Pode entrar em conflito quando ZORDER BY é usado. Não pode entrar em conflito de outra forma. |
Pode entrar em conflito quando ZORDER BY é usado. Não pode entrar em conflito de outra forma. |
Importante
(1) Todas as INSERT
operações dos quadros acima descrevem operações de apêndice que não leem quaisquer dados do mesmo quadro antes de serem autorizadas. INSERT
As operações que contêm subconsultas que lêem a mesma tabela suportam a mesma simultaneidade que MERGE
.
REORG
As operações têm semântica de isolamento idêntica à OPTIMIZE
ao reescrever arquivos de dados para refletir as alterações registradas em vetores de exclusão. Quando você usa REORG
para aplicar uma atualização, os protocolos de tabela mudam, o que entra em conflito com todas as operações em andamento.
Escrever conflitos sem simultaneidade em nível de linha
A tabela a seguir descreve quais pares de operações de gravação podem entrar em conflito em cada nível de isolamento.
As tabelas não suportam simultaneidade em nível de linha se tiverem partições definidas ou não tiverem vetores de exclusão habilitados. O Databricks Runtime 14.2 ou superior é necessário para simultaneidade em nível de linha.
Nota
As tabelas com colunas de identidade não suportam transações simultâneas. Consulte Usar colunas de identidade no Delta Lake.
INSERIR (1) | ATUALIZAR, EXCLUIR, MESCLAR EM | OPTIMIZE | |
---|---|---|---|
INSERT | Não pode entrar em conflito | ||
ATUALIZAR, EXCLUIR, MESCLAR EM | Não é possível entrar em conflito em WriteSerializable. Pode entrar em conflito em Serializable. Consulte Evitar conflitos com partições. | Pode entrar em conflito em Serializable e WriteSerializable. Consulte Evitar conflitos com partições. | |
OPTIMIZE | Não pode entrar em conflito | Não pode entrar em conflito com tabelas com vetores de exclusão habilitados, a menos que ZORDER BY seja usado. Pode entrar em conflito de outra forma. |
Não pode entrar em conflito com tabelas com vetores de exclusão habilitados, a menos que ZORDER BY seja usado. Pode entrar em conflito de outra forma. |
Importante
(1) Todas as INSERT
operações dos quadros acima descrevem operações de apêndice que não leem quaisquer dados do mesmo quadro antes de serem autorizadas. INSERT
As operações que contêm subconsultas que lêem a mesma tabela suportam a mesma simultaneidade que MERGE
.
REORG
As operações têm semântica de isolamento idêntica à OPTIMIZE
ao reescrever arquivos de dados para refletir as alterações registradas em vetores de exclusão. Quando você usa REORG
para aplicar uma atualização, os protocolos de tabela mudam, o que entra em conflito com todas as operações em andamento.
Limitações para simultaneidade em nível de linha
Algumas limitações se aplicam à simultaneidade em nível de linha. Para as operações a seguir, a resolução de conflitos segue a simultaneidade normal para conflitos de gravação no Azure Databricks. Consulte Gravar conflitos sem simultaneidade no nível da linha.
- Comandos com cláusulas condicionais complexas, incluindo o seguinte:
- Condições em tipos de dados complexos, como structs, arrays ou mapas.
- Condições usando expressões não determinísticas e subconsultas.
- Condições que contêm subconsultas correlacionadas.
- No Databricks Runtime 14.2,
MERGE
os comandos devem usar um predicado explícito na tabela de destino para filtrar linhas correspondentes à tabela de origem. Para resolução de mesclagem, o filtro verifica apenas as linhas que podem entrar em conflito com base nas condições do filtro em operações simultâneas.
Nota
A deteção de conflitos em nível de linha pode aumentar o tempo total de execução. No caso de muitas transações simultâneas, o escritor prioriza a latência sobre a resolução de conflitos e conflitos podem ocorrer.
Todas as limitações para vetores de exclusão também se aplicam. Consulte Limitações.
Quando o Delta Lake se compromete sem ler a tabela?
As operações Delta Lake INSERT
ou append não leem o estado da tabela antes de se comprometerem se as seguintes condições forem satisfeitas:
- A lógica é expressa usando
INSERT
lógica SQL ou modo de acréscimo. - A lógica não contém subconsultas ou condicionais que façam referência à tabela direcionada pela operação de gravação.
Como em outras confirmações, o Delta Lake valida e resolve as versões da tabela na confirmação usando metadados no log de transações, mas nenhuma versão da tabela é realmente lida.
Nota
Muitos padrões comuns usam MERGE
operações para inserir dados com base nas condições da tabela. Embora seja possível reescrever essa lógica usando INSERT
instruções, se qualquer expressão condicional fizer referência a uma coluna na tabela de destino, essas instruções terão as mesmas limitações de simultaneidade que MERGE
.
Escrever níveis de isolamento serializáveis vs. serializáveis
O nível de isolamento de uma tabela define o grau em que uma transação deve ser isolada das modificações feitas por transações simultâneas. O Delta Lake no Azure Databricks oferece suporte a dois níveis de isolamento: Serializable e WriteSerializable.
Serializável: O nível de isolamento mais forte. Ele garante que as operações de gravação confirmadas e todas as leituras sejam serializáveis. As operações são permitidas desde que exista uma sequência serial de execução uma de cada vez que gere o mesmo resultado visto na tabela. Para as operações de gravação, a sequência serial é exatamente a mesma vista no histórico da tabela.
WriteSerializable (Default): Um nível de isolamento mais fraco do que Serializable. Ele garante apenas que as operações de gravação (ou seja, não leituras) sejam serializáveis. No entanto, isso ainda é mais forte do que o isolamento do Snapshot . WriteSerializable é o nível de isolamento padrão porque fornece um grande equilíbrio de consistência e disponibilidade de dados para a maioria das operações comuns.
Neste modo, o conteúdo da tabela Delta pode ser diferente do que é esperado da sequência de operações vista no histórico da tabela. Isso ocorre porque esse modo permite que certos pares de gravações simultâneas (digamos, operações X e Y) prossigam de tal forma que o resultado seria como se Y fosse executado antes de X (ou seja, serializável entre eles), mesmo que o histórico mostrasse que Y foi cometido depois de X. Para não permitir essa reordenação, defina o nível de isolamento da tabela como Serializável para fazer com que essas transações falhem.
As operações de leitura sempre usam isolamento de instantâneo. O nível de isolamento de escrita determina se é ou não possível para um leitor ver um instantâneo de uma tabela, que de acordo com o histórico, "nunca existiu".
Para o nível Serializável, um leitor sempre vê apenas tabelas que estão de acordo com o histórico. Para o nível WriteSerializable, um leitor pode ver uma tabela que não existe no log Delta.
Por exemplo, considere txn1, uma exclusão de longa execução e txn2, que insere dados excluídos por txn1. txn2 e txn1 completam-se e são registados por esta ordem na história. De acordo com o histórico, os dados inseridos no txn2 não devem existir na tabela. Para o nível serializável, um leitor nunca veria dados inseridos pelo txn2. No entanto, para o nível WriteSerializable, um leitor pode, em algum momento, ver os dados inseridos pelo txn2.
Para obter mais informações sobre quais tipos de operações podem entrar em conflito entre si em cada nível de isolamento e os possíveis erros, consulte Evitar conflitos usando particionamento e condições de comando separado.
Definir o nível de isolamento
Você define o nível de isolamento usando o ALTER TABLE
comando.
ALTER TABLE <table-name> SET TBLPROPERTIES ('delta.isolationLevel' = <level-name>)
onde <level-name>
está Serializable
ou WriteSerializable
.
Por exemplo, para alterar o nível de isolamento do padrão WriteSerializable
para Serializable
, execute:
ALTER TABLE <table-name> SET TBLPROPERTIES ('delta.isolationLevel' = 'Serializable')
Evite conflitos usando condições de particionamento e comando separado
Em todos os casos marcados como "pode entrar em conflito", se as duas operações entrarão em conflito depende se elas operam no mesmo conjunto de arquivos. Pode fazer os dois conjuntos de ficheiros desagrupar criando partições na tabela pelas mesmas colunas utilizadas nas condições das operações. Por exemplo, os dois comandos UPDATE table WHERE date > '2010-01-01' ...
e DELETE table WHERE date < '2010-01-01'
entrarão em conflito se a tabela não estiver particionada por data, uma vez que ambos podem tentar modificar o mesmo conjunto de ficheiros. Particionar a tabela por date
evitará o conflito. Portanto, particionar uma tabela de acordo com as condições comumente usadas no comando pode reduzir os conflitos significativamente. No entanto, particionar uma tabela por uma coluna que tem alta cardinalidade pode levar a outros problemas de desempenho devido ao grande número de subdiretórios.
Exceções de conflitos
Quando ocorrer um conflito de transações, observará uma das seguintes exceções:
ConcurrentAppendException
Esta exceção ocorre quando uma operação simultânea adiciona ficheiros na mesma partição (ou em qualquer parte de uma tabela não particionada) que a operação lê. As adições de arquivo podem ser causadas por INSERT
, DELETE
, UPDATE
, ou MERGE
operações.
Com o nível de isolamento padrão de WriteSerializable
, os arquivos adicionados por operações cegas INSERT
(ou seja, operações que acrescentam dados cegamente sem ler nenhum dado) não entram em conflito com nenhuma operação, mesmo que toquem na mesma partição (ou em qualquer lugar em uma tabela não particionada). Se o nível de isolamento estiver definido como Serializable
, os acréscimos cegos poderão entrar em conflito.
Essa exceção geralmente é lançada durante operações simultâneas DELETE
, UPDATE
ou MERGE
. Embora as operações simultâneas possam estar a atualizar fisicamente diretórios de partições diferentes, um deles pode ler a mesma partição que a outra atualiza em simultâneo, causando assim um conflito. Pode evitar esta situação ao tornar a separação explícita na condição de operação. Considere o seguinte exemplo.
// 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()
Suponha que executa o código acima em simultâneo para diferentes datas ou países. Uma vez que cada tarefa está a trabalhar numa partição independente na tabela Delta de destino, não espera quaisquer conflitos. No entanto, a condição não é explícita o suficiente e pode analisar toda a tabela e pode entrar em conflito com as operações simultâneas que atualizam quaisquer outras partições. Em vez disso, pode reescrever a sua declaração para adicionar uma data e um país específicos à condição de intercalação, conforme mostrado no exemplo seguinte.
// 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()
Agora é seguro executar esta operação em simultâneo em datas e países diferentes.
ConcurrentDeleteReadException
Essa exceção ocorre quando uma operação simultânea excluiu um arquivo lido pela operação. As causas comuns são um DELETE
, UPDATE
ou MERGE
operação que reescreve arquivos.
ConcurrentDeleteDeleteException
Essa exceção ocorre quando uma operação simultânea excluiu um arquivo que sua operação também exclui. Isto pode ser causado por duas operações de compactação simultâneas que reescrevem os mesmos ficheiros.
MetadataChangedException
Essa exceção ocorre quando uma transação simultânea atualiza os metadados de uma tabela Delta. As causas comuns são ALTER TABLE
operações ou gravações na tabela Delta que atualizam o esquema da tabela.
ConcurrentTransactionException
Se uma consulta de streaming usando o mesmo local de ponto de verificação for iniciada várias vezes simultaneamente e tentar gravar na tabela Delta ao mesmo tempo. Nunca deve ter duas consultas de transmissão em fluxo a utilizar a mesma localização de ponto de verificação e a ser executadas ao mesmo tempo.
ProtocolChangedException
Esta exceção pode ocorrer nos seguintes casos:
- Quando sua tabela Delta é atualizada para uma nova versão de protocolo. Para que operações futuras sejam bem-sucedidas, talvez seja necessário atualizar seu Databricks Runtime.
- Quando vários escritores estão a criar ou substituir uma tabela ao mesmo tempo.
- Quando vários escritores estão a escrever num caminho vazio ao mesmo tempo.
Consulte Como o Azure Databricks gerencia a compatibilidade de recursos do Delta Lake? para obter mais detalhes.
Comportamento de visualização de simultaneidade em nível de linha (legado)
Esta seção descreve comportamentos de visualização para simultaneidade em nível de linha no Databricks Runtime 14.1 e abaixo. A simultaneidade em nível de linha sempre requer vetores de exclusão.
No Databricks Runtime 13.3 LTS e superior, as tabelas com clustering líquido ativado ativam automaticamente a simultaneidade em nível de linha.
No Databricks Runtime 14.0 e 14.1, você pode habilitar a simultaneidade em nível de linha para tabelas com vetores de exclusão definindo a seguinte configuração para o cluster ou SparkSession:
spark.databricks.delta.rowLevelConcurrencyPreview = true
No Databricks Runtime 14.1 e inferior, a computação não-Photon suporta apenas simultaneidade em nível de linha para DELETE
operações.