Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
As políticas de filtro de linha e máscara de coluna introduzem a lógica que é executada no momento da consulta, portanto, o desempenho depende de como você projeta suas políticas. Não há uma única abordagem correta para cada carga de trabalho. A melhor abordagem depende do volume de dados, dos padrões de consulta, de como os usuários interagem com tabelas protegidas e do comportamento desejado de mascaramento ou filtragem. As seções a seguir abrangem as considerações de desempenho mais comuns. Use-as como uma lista de verificação ao projetar suas políticas e teste com consultas representativas antes de implantar em produção.
Visão geral do desempenho
| Consideração | Description |
|---|---|
| Reduzir a complexidade da UDF | A lógica UDF complexa pode inibir o desempenho da consulta; funções simples têm melhor desempenho. |
| Abordagem para alvos principais | Decida se a lógica baseada em principal deve ser implementada nas cláusulas da política TO/EXCEPT ou dentro da UDF usando funções de identidade. |
| Usar expressões determinísticas e com segurança de erro | Funções e expressões não determinísticas que podem gerar erros reduzem a capacidade do otimizador de armazenar em cache resultados e reordenar operações. |
| Avoid Python UDFs | Use UDFs do SQL em vez de Python UDFs sempre que possível. |
| Manter tabelas de pesquisa pequenas | UDFs que fazem referência a tabelas externas têm o melhor desempenho quando essas tabelas são pequenas o suficiente para serem transmitidas. |
| Entender o predicado pushdown em tabelas protegidas | As consultas em tabelas protegidas podem não se beneficiar da poda de partição ou do agrupamento líquido se os predicados tiverem efeitos colaterais. |
| Reutilizar máscaras de coluna sempre que possível | Cada máscara distinta em uma tabela adiciona sobrecarga; reutilizando a mesma função entre colunas pode reduzi-la. |
| Evitar mascaramento regex em campos de texto grandes | O mascaramento baseado em Regex em documentos serializados força o mecanismo a varrer e reescrever todo o payload para cada linha de dados. |
Reduzir a complexidade da UDF
A UDF em uma política ABAC é executada para cada linha (filtros de linha) ou cada valor de coluna correspondente (máscaras de coluna) durante a execução da consulta. A complexidade da UDF afeta diretamente o desempenho da consulta.
Do:
- Mantenha as UDFs simples. Favoreça instruções básicas
CASEe expressões boolianas simples. - Referencie somente colunas de tabela de destino em UDFs o máximo possível. Isso habilita o pushdown do predicado.
- Se o UDF precisar fazer referência a tabelas externas, mantenha qualquer referência externa pequena o suficiente para ser transmitida. Verifique se as tabelas referenciadas são otimizadas e particionadas para corresponder ao padrão de acesso da política. Por exemplo, particione uma tabela de pesquisa de política por nome de usuário.
- Evite aninhamento em múltiplos níveis e chamadas de função desnecessárias. Use funções SQL internas o máximo possível.
Evitar:
- Chamadas ou consultas a APIs externas para outros bancos de dados em UDFs. As chamadas de rede podem introduzir latência e tempos limite adicionais.
- Subconsultas complexas ou junções em tabelas grandes. Elas impedem junções de hash de transmissão e forçam junções de loop aninhadas.
- Regex pesado em campos de texto grandes. Consulte Regex em campos de texto grandes.
- Consultas de metadados por linha, por exemplo, consultando
information_schema.
Abordagem para direcionamento de principais
Ao escrever uma política ABAC, você decide onde implementar a lógica baseada no principal: nas cláusulas da política TO/EXCEPT, ou dentro da UDF usando funções de identidade como current_user() e is_account_group_member().
Em geral, use as cláusulas da política TO/EXCEPT para definir a quais entidades uma política se aplica. Isso mantém a definição de política mais simples e a UDF focada na transformação, filtragem ou mascaramento de dados. A cláusula EXCEPT elimina a política completamente para os usuários isentos, o que significa que não haverá execução de UDF para esses usuários.
Quando a lógica condicional é muito complexa para as cláusulas principais da política, as funções de identidade dentro da UDF são uma alternativa possível. Essas funções são resolvidas uma vez durante a análise de consulta, não por linha. Várias chamadas para funções de identidade, como is_account_group_member() com argumentos de grupo diferentes, resultam em uma única chamada à API uc, portanto, o impacto no desempenho normalmente é mínimo.
A UDF a seguir é eficiente porque se baseia apenas em funções de identidade, que são resolvidas uma vez durante a análise de consulta:
CREATE OR REPLACE FUNCTION rowfilter()
RETURNS BOOLEAN
RETURN
CASE
WHEN is_account_group_member('auditors') OR is_account_group_member('external-auditors') THEN true
WHEN is_account_group_member('low-privileged') THEN false
WHEN session_user() = 'admin@organization.com' THEN true
ELSE false
END;
Por outro lado, a UDF a seguir é mais lenta porque codifica privilégios em uma tabela secundária, o que requer uma pesquisa de tabela adicional:
CREATE OR REPLACE FUNCTION rowfilter()
RETURNS BOOLEAN
RETURN
CASE WHEN EXISTS(SELECT 1 FROM access_lease WHERE user = session_user()) THEN true
ELSE false END;
Use expressões determinísticas seguras contra erros
Use expressões determinísticas que não podem gerar erros em UDFs de política e em consultas em tabelas protegidas.
Funções não determinísticas (funções que retornam resultados diferentes para a mesma entrada, como rand() ou now()) impedem que o otimizador armazena resultados em cache ou aplique dobramento constante. As UDFs sql e Python dão suporte à palavra-chave DETERMINISTIC na instrução CREATE FUNCTION. Para UDFs do SQL, o otimizador deriva o determinismo do corpo da função automaticamente, mas você também pode defini-lo explicitamente. Para Python UDFs, o otimizador não pode inspecionar o corpo da função, portanto, marcar explicitamente um Python UDF como determinístico é importante para habilitar o cache de resultados para chamadas com argumentos idênticos.
Algumas expressões gerarão erros se as entradas não forem válidas, como a divisão ANSI em um denominador zero. Quando o compilador do SQL detecta essa possibilidade, ele não pode aplicar operações como filtros dentro do plano de consulta. Isso pode gerar erros que revelam informações sobre valores antes que a filtragem ou o mascaramento entrem em vigor. Use alternativas com segurança de erro como try_divide , em vez de /, try_cast em vez de CAST, e try_to_number em vez de to_number. Elas retornam NULL em caso de falha em vez de gerar, o que permite que o otimizador reorganize e dobre expressões livremente.
Avoid Python UDFs
Evite Python UDFs em políticas ABAC sempre que possível. Python UDFs devem ser envolvidos em um UDF do SQL para serem usados em políticas. Eles também geralmente são mais lentos que UDFs do SQL porque o otimizador não pode incluí-los ou otimizá-las, e a função Python é executada para cada linha da tabela de destino.
Se um Python UDF for inevitável, consulte Expressões determinísticas seguras contra erros para saber como marcá-lo como DETERMINISTIC para habilitar o cache de resultados.
Manter tabelas de pesquisa pequenas
Um padrão comum é verificar os direitos de acesso em relação a uma tabela de pesquisa pequena (por exemplo, uma tabela que mapeia os usuários para níveis de prioridade permitidos). Se a tabela de pesquisa for significativamente menor que a tabela de destino, o otimizador converterá a subconsulta em um broadcast hash join. A tabela de pesquisa é copiada para cada executor e armazenada na memória como um hashmap, o que permite filtragem rápida durante a verificação da tabela. Para um exemplo de código, consulte tabelas de pesquisa nas UDFs da política ABAC.
- Se a tabela de pesquisa for grande, o otimizador retornará para uma junção aleatória, que é mais lenta.
- Se o predicado de pesquisa for complexo (não uma verificação de igualdade simples), a junção de broadcast também poderá se tornar inelegível.
- Mesmo com o broadcast hash join, cada linha ainda incorre no custo de uma consulta a tabela de hash durante a execução.
Entender predicate pushdown em tabelas protegidas
O pushdown de predicado é uma otimização de desempenho em que o mecanismo envia suas condições de filtro para a camada de armazenamento. Isso permite que o mecanismo ignore partições inteiras de dados que não correspondem à consulta, reduzindo significativamente a E/S e acelerando a execução.
Para tabelas protegidas por filtros de linha e máscaras de coluna, essa otimização é mais complexa. Essa é a fonte mais comum de problemas de desempenho com tabelas protegidas e a mais difícil de resolver, pois os autores da política não podem controlar quais consultas os usuários executam em tabelas protegidas.
Como a barreira afeta o rebaixamento de predicado SecureView
Tanto o ABAC quanto os filtros de linha e máscaras de coluna de nível de tabela usam uma SecureView barreira para impedir que predicados com efeitos colaterais ultrapassem o limite da política. Isso protege contra vazamento de dados de canal lateral, mas também pode bloquear a eliminação de partições e otimizações de clusterização, o que pode forçar varreduras completas de tabela. Isso se aplica mesmo quando a política UDF é resolvida para uma constante true (o que significa que nenhuma linha é realmente filtrada). A presença de uma política em uma tabela introduz a SecureView barreira.
Filtros afetados pela barreira
Geralmente, o otimizador pode enviar somente predicados sem efeito colateral pela SecureView barreira.
-
Empurrado para baixo (rápido): comparações de igualdade simples (
WHERE col = 'value') e comparações básicas de intervalo (WHERE col > 100). Elas estão livres de efeitos colaterais e não correm o risco de vazar dados. -
Bloqueado (mais lento): Predicados que chamam funções (
WHERE date_format(col, 'yyyy-MM-dd') = '1995-07-29') ou introduzem conversões de tipo implícitas. Eles são mantidos acima da barreiraSecureView, o que significa que o mecanismo deve examinar a tabela antes de aplicar o filtro.
O exemplo a seguir mostra a diferença. Considere uma tabela com uma chave de partição em o_orderdate e uma consulta que filtra usando date_format.
EXPLAIN SELECT * FROM orders
WHERE date_format(o_orderdate, 'yyyy-MM-dd') = '1995-07-29'
Sem uma política, o predicado date_format aparece no PartitionFilters dentro do nó PhotonScan, o que significa que a poda de partição está ativa.
+- PhotonScan parquet orders[...]
PartitionFilters: [isnotnull(o_orderdate),
(date_format(cast(o_orderdate as timestamp), yyyy-MM-dd, ...))]
Com uma política (mesmo uma que sempre retorna true), a SecureView barreira bloqueia o predicado. Ele se move para PhotonFilter acima do scan em vez de permanecer em PartitionFilters, o que resulta em uma verificação completa da tabela:
+- PhotonFilter (date_format(cast(o_orderdate as timestamp),
yyyy-MM-dd, ...) = 1995-07-29)
+- PhotonSecureView orders
+- PhotonScan parquet orders[...]
PartitionFilters: [isnotnull(o_orderdate)]
Um predicado mais simples como WHERE o_orderdate = '1995-07-29' não tem efeitos colaterais e ainda pode ser empurrado para baixo mesmo com a SecureView barreira em vigor:
+- PhotonSecureView orders
+- PhotonScan parquet orders[...]
PartitionFilters: [isnotnull(o_orderdate),
(o_orderdate = 1995-07-29)]
Use predicados de igualdade simples em tabelas protegidas quando possível. Para usuários isentos, use a EXCEPT cláusula na política para eliminar totalmente a SecureView barreira, restabelecendo completamente o pushdown de predicados.
Reutilizar máscaras de coluna sempre que possível
Aplicar muitas máscaras de coluna distintas a uma única tabela aumenta o custo por coluna. Mascarar somente colunas que contêm dados verdadeiramente confidenciais.
Quando várias colunas exigem a mesma transformação (por exemplo, ocultar NULL ou substituir por uma string fixa), reutilize a mesma função de mascaramento em vez de criar uma função separada para cada coluna.
Azure Databricks reconhece as políticas que fazem referência à mesma UDF com os mesmos argumentos e, portanto, as considera como a mesma máscara efetiva, assim, a reutilização de funções evita sobrecarga desnecessária.
Evitar mascaramento regex em campos de texto grandes
Usar regexp_replace dentro de uma máscara de coluna para redigir elementos em um documento serializado (XML ou JSON armazenado como uma coluna STRING) é caro.
regexp_replace percorre a string completa para cada linha. O otimizador trata a coluna STRING como um valor opaco e não pode podar partes não usadas do documento. O mecanismo lê e reescreve todo o conteúdo mesmo quando a consulta precisa apenas de alguns campos.
-- Expensive: regex masking on serialized XML
CREATE FUNCTION mask_xml_pii(raw_xml STRING)
RETURNS STRING
RETURN CASE
WHEN is_account_group_member('sensitive_data_viewers') THEN raw_xml
ELSE regexp_replace(raw_xml, '<SSN>[^<]*</SSN>', '<SSN>***</SSN>')
END;
Em vez disso, materialize os campos confidenciais em colunas tipadas em uma tabela separada e aplique máscaras de coluna a essas colunas escalares. Em seguida, a função de máscara opera em um único valor pequeno por linha, em vez de todo o documento serializado.
-- Source table stores raw XML as STRING
-- Example XML: <person><SSN>123-45-6789</SSN><name>Alice</name><dob>1990-01-01</dob></person>
-- Recommended: extract fields into a table, then mask scalar values
CREATE TABLE person_data AS
SELECT
id,
xpath_string(raw_xml, 'person/SSN') AS ssn,
xpath_string(raw_xml, 'person/name') AS name,
xpath_string(raw_xml, 'person/dob') AS date_of_birth,
raw_xml
FROM raw_records;
-- Simple scalar mask, applied to each extracted column
CREATE FUNCTION redact(val STRING) RETURNS STRING
RETURN CASE
WHEN is_account_group_member('sensitive_data_viewers') THEN val
ELSE '***'
END;
Se você puder armazenar os dados como uma coluna de estrutura em vez de XML, use o padrão de máscara flexível VARIANT para mascarar campos individuais dentro da estrutura. Consulte as colunas de struct do Mask com VARIANT.
Testar o desempenho da UDF
Teste em escala
Teste o desempenho da UDF em pelo menos 1 milhão de linhas antes de implantar em produção. Além dos testes de escala sintética, execute consultas que representam a carga de trabalho real esperada na tabela protegida. Faça alterações incrementais em suas funções de política e meça o efeito de cada alteração em vez de testar apenas a versão final.
WITH test_data AS (
SELECT
id,
your_mask_function(id) AS masked_id,
current_timestamp() AS ts
FROM (
SELECT CONCAT('ID', LPAD(CAST(id AS STRING), 6, '0')) AS id
FROM range(1000000)
)
)
SELECT
COUNT(*) AS rows_processed,
MAX(ts) - MIN(ts) AS total_duration
FROM test_data;
Substitua your_mask_function pela UDF que você está testando. Compare os resultados com e sem a política aplicada para isolar a sobrecarga da política.