Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
As políticas de filtro de linhas e máscaras de coluna introduzem lógica que corre no momento da consulta, pelo que o desempenho depende de como desenha as suas políticas. Não existe uma abordagem única certa para cada carga de trabalho. A melhor abordagem depende do volume de dados, dos padrões de consulta, da forma como os utilizadores interagem com tabelas protegidas e do comportamento de mascaramento ou filtragem desejado. As secções seguintes abordam as considerações de desempenho mais comuns. Utilize-as como lista de verificação ao elaborar as suas políticas e teste-as com consultas representativas antes de implementar em produção.
Visão geral da performance
| Consideração | Description |
|---|---|
| Reduzir a complexidade do UDF | A lógica UDF complexa pode inibir o desempenho das consultas; Funções simples desempenham melhor. |
| Abordagem para direcionar os responsáveis | Decida se implementa a lógica baseada em principal nas cláusulas da política TO/EXCEPT ou dentro do UDF usando funções de identidade. |
| Usar expressões determinísticas e seguras contra erros | 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. |
| Evite Python UDFs | Use UDFs SQL em vez de UDFs em Python sempre que possível. |
| Mantenha as tabelas de consulta pequenas | Os UDFs que referenciam tabelas externas têm melhor desempenho quando essas tabelas são suficientemente pequenas para serem transmitidas. |
| Compreender o pushdown de predicados em tabelas protegidas | Consultas realizadas em tabelas protegidas podem não beneficiar da poda de partições ou do liquid clustering se os predicados tiverem efeitos colaterais. |
| Reutilizar máscaras de coluna sempre que possível | Cada máscara distinta numa tabela acrescenta custos adicionais; Reutilizar a mesma função entre colunas pode reduzi-la. |
| Evite mascaramento regex em campos de texto grandes | O mascaramento baseado em regex em documentos serializados obriga o motor a escanear e reescrever toda a carga útil para cada linha. |
Reduzir a complexidade do UDF
O UDF numa política ABAC é executado para cada linha (filtros de linhas) ou para cada valor de coluna correspondente (máscaras de colunas) durante a execução da consulta. A complexidade do UDF afeta diretamente o desempenho das consultas.
Do:
- **
Mantenha os UDFs simples. Favoreça afirmações básicas
CASEe expressões booleanas simples. - Referenciar apenas as colunas da tabela de alvos nos UDFs tanto quanto possível. Isto permite o uso de predicate pushdown.
- Se o seu UDF tiver de referenciar tabelas externas, mantenha qualquer referência externa pequena o suficiente para ser transmitida. Certifique-se de que as tabelas referenciadas estão otimizadas e particionadas para corresponder ao padrão de acesso da política. Por exemplo, particionar uma tabela de consulta de políticas por nome de utilizador.
- Evite o aninhamento multinível e chamadas de funções desnecessárias. Use funções SQL incorporadas tanto quanto possível.
Evitar:
- Chamadas a APIs externas ou consultas a outras bases de dados em UDFs. As chamadas de rede podem introduzir latência adicional e tempos de espera.
- Subconsultas ou junções complexas com tabelas grandes. Estes impedem as junções de hash broadcast e forçam as junções de loops aninhados.
- Utilização intensiva de expressões regulares em campos de texto grandes. Veja Regex em campos de texto grandes.
- Consultas de metadados a nível de linha, por exemplo, ao consultar
information_schema.
Abordagem para direcionar entidades principais
Quando escreve uma política ABAC, decide onde implementar lógica baseada em princípios: nas cláusulas da TO/EXCEPT política, ou dentro do UDF usando funções identidade como current_user() e .is_account_group_member()
De um modo geral, utilize as cláusulas da TO/EXCEPT apólice para definir a quais princípios a política se aplica. Isto mantém a definição da política mais simples e o UDF focado na transformação, filtragem ou mascaramento de dados. A EXCEPT cláusula elimina completamente a política para utilizadores isentos, o que significa que não há execução UDF para esses utilizadores.
Quando a lógica condicional é demasiado complexa para as cláusulas principais da política, as funções de identidade dentro de uma UDF são uma possível alternativa. Estas funções são resolvidas uma vez durante a análise da consulta, não por linha. Múltiplas chamadas para funções identidade, como is_account_group_member() com diferentes argumentos de grupo, resultam numa única chamada UC API, pelo que o impacto no desempenho é tipicamente mínimo.
O seguinte UDF é eficiente porque depende apenas de funções identidade, que são resolvidas uma vez durante a análise de consultas:
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;
Em contraste, o seguinte UDF é mais lento porque codifica privilégios numa tabela secundária, o que requer uma consulta adicional de tabela:
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;
Usar expressões determinísticas e seguras contra erros
Utilize expressões determinísticas que não gerem erros em UDFs de políticas e em consultas a tabelas protegidas.
Funções não determinísticas (funções que retornam resultados diferentes para a mesma entrada, como rand() ou now()) impedem o otimizador de armazenar resultados em cache ou de aplicar folding constante. Tanto o SQL como o Python UDFs suportam a palavra-chave DETERMINISTIC na instrução CREATE FUNCTION. Para UDFs SQL, o otimizador deriva automaticamente o determinismo a partir do corpo da função, mas também pode defini-lo explicitamente. Para UDFs Python, o otimizador não pode inspecionar o corpo da função, por isso marcar explicitamente um UDF Python como determinístico é importante para permitir a cache de resultados para chamadas com argumentos idênticos.
Algumas expressões geram erros se as entradas não forem válidas, como a divisão ANSI num denominador zero. Quando o compilador SQL deteta esta possibilidade, não pode empurrar operações como filtros para baixo no plano de consulta. Fazê-lo pode desencadear erros que revelam informações sobre valores antes de o filtro ou mascaramento entrar em vigor. Use alternativas seguras para erros como try_divide em vez de /, try_cast em vez de CAST, e try_to_number em vez de to_number. Estes retornam NULL em caso de falha em vez de serem lançados, o que permite ao otimizador rearranjar e dobrar expressões livremente.
Evitando Python UDFs
Evite UDFs em Python nas políticas ABAC sempre que possível. Python UDFs devem ser encapsulados num UDF SQL para serem utilizados em políticas. São também geralmente mais lentos do que os UDFs SQL porque o otimizador não os consegue fazer em linha nem otimizá-los, e a função Python é executada para cada linha da tabela de destino.
Se um UDF Python for inevitável, consulte Expressões determinísticas, seguras contra erros para saber como marcá-lo como DETERMINISTIC para permitir a cache de resultados.
Mantenha as tabelas de consulta pequenas
Um padrão comum é verificar os direitos de acesso contra uma pequena tabela de consulta (por exemplo, uma tabela que mapeia os utilizadores para níveis de prioridade permitidos). Se a tabela de consulta for significativamente menor do que a tabela de destino, o otimizador converte a subconsulta numa junção de hash distribuída. A tabela de consulta é copiada para cada executor e armazenada na memória como um hashmap, o que permite filtragem rápida durante a varredura da tabela. Para um exemplo de código, veja Tabelas de consulta em UDFs de políticas ABAC.
- Se a tabela de consulta for grande, o otimizador recorre a um "shuffle join", que é mais lento.
- Se o predicado de pesquisa for complexo (não uma simples verificação de igualdade), a junção broadcast também pode tornar-se inelegível.
- Mesmo com o uso de "broadcast hash join," cada linha ainda incorre no custo de consulta a uma tabela hash durante a execução.
Compreender o pushdown de predicados em tabelas protegidas
O pushdown de predicados é uma otimização de desempenho em que o motor empurra as condições do filtro para a camada de armazenamento. Isto permite ao motor saltar partições inteiras de dados que não correspondem à sua consulta, reduzindo significativamente a E/S e acelerando a execução.
Para tabelas protegidas por filtros de linha e máscaras de coluna, esta otimização é mais complexa. Esta é a fonte mais comum de problemas de desempenho com tabelas protegidas e a mais difícil de resolver, porque os autores de políticas não conseguem controlar que consultas os utilizadores executam contra tabelas protegidas.
Como a SecureView barreira afeta o empurramento do predicado
Tanto o ABAC como os filtros de linha ao nível da tabela e máscaras de coluna utilizam uma SecureView barreira para evitar que predicados com efeitos secundários possam ultrapassar o limite da política. Isto protege contra fugas de dados em canais laterais, mas também pode bloquear a poda de partições e as otimizações de clustering líquido, o que pode levar a varreduras completas da tabela. Isto aplica-se mesmo quando o UDF da política resulta numa constante true (ou seja, nenhuma linha é realmente filtrada). A presença de uma política numa tabela introduz a SecureView barreira.
Filtros afetados pela barreira
Geralmente, o otimizador pode empurrar apenas predicados sem efeitos colaterais através da SecureView barreira.
-
Pressionado para baixo (rápido): Comparações simples de igualdade (
WHERE col = 'value') e comparações básicas de intervalo (WHERE col > 100). Estas não têm efeitos secundários e não correm o risco de vazar dados. -
Bloqueado (mais lento): Os predicados que chamam funções (
WHERE date_format(col, 'yyyy-MM-dd') = '1995-07-29') ou introduzem conversões de tipo implícitas. Estas são mantidas acima daSecureViewbarreira, o que significa que o motor tem de varrer a tabela antes de aplicar o filtro.
O exemplo seguinte 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 em PartitionFilters dentro do nó PhotonScan, o que significa que a poda de partições está ativa.
+- PhotonScan parquet orders[...]
PartitionFilters: [isnotnull(o_orderdate),
(date_format(cast(o_orderdate as timestamp), yyyy-MM-dd, ...))]
Com uma política (mesmo que retorne sempre true), a barreira SecureView bloqueia o predicado. Move-se para um PhotonFilter acima da varredura em vez de permanecer em PartitionFilters, o que resulta numa varredura 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, tal como WHERE o_orderdate = '1995-07-29', não tem efeitos secundários e ainda pode ser propagado mesmo com a barreira SecureView em posição:
+- PhotonSecureView orders
+- PhotonScan parquet orders[...]
PartitionFilters: [isnotnull(o_orderdate),
(o_orderdate = 1995-07-29)]
Use predicados simples de igualdade em tabelas protegidas sempre que possível. Para utilizadores isentos, use a EXCEPT cláusula da apólice para eliminar completamente a SecureView barreira, o que restaura o empurramento total do predicado.
Reutilizar máscaras de coluna sempre que possível
Aplicar muitas máscaras de coluna distintas a uma única tabela complica o custo associado a cada coluna. Mascarem apenas colunas que contenham dados verdadeiramente sensíveis.
Quando múltiplas colunas requerem a mesma transformação (por exemplo, redacção para NULL ou substituição por uma cadeia fixa), reutilize a mesma função de mascaramento em vez de criar uma função separada por coluna.
O Azure Databricks reconhece políticas que referenciam o mesmo UDF com os mesmos argumentos como a mesma máscara efetiva, pelo que reutilizar funções evita sobrecarga desnecessária.
Evite mascaramento regex em campos de texto grandes
Usar regexp_replace numa máscara de coluna para ocultar elementos num documento serializado (XML ou JSON armazenado como uma coluna STRING) é dispendioso.
regexp_replace Percorre a cadeia completa em cada linha. O otimizador trata a coluna STRING como um valor opaco e não pode podar as partes não utilizadas do documento. O motor lê e reescreve toda a carga útil mesmo quando a consulta só precisa 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 sensíveis em colunas com tipos definidos numa tabela separada e depois aplique máscaras às colunas escalares. A função de máscara opera então com 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 conseguir armazenar os dados como uma coluna de estrutura em vez de XML, use o padrão de mascaramento flexível VARIANT para mascarar campos individuais dentro da estrutura. Veja colunas de estruturas de máscara com VARIANT.
Desempenho do UDF em testes
Teste em grande escala
Teste o desempenho do UDF em pelo menos 1 milhão de linhas antes de implementar para produção. Para além dos testes de escala sintética, execute consultas que representem a carga de trabalho real que espera na tabela protegida. Faça alterações incrementais às suas funções políticas 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;
Substitui your_mask_function pelo UDF que estás a testar. Compare os resultados com e sem a apólice aplicada para isolar os custos gerais da apólice.