Melhores práticas de políticas de UDFs para ABAC
Visualização
Este recurso está em Pré-visualização Pública.
Esta página mostra como escrever funções definidas pelo usuário (UDFs) de alto desempenho para uso em políticas de filtro de linha e máscara de coluna ABAC no Unity Catalog.
Por que o desempenho do UDF é importante
As políticas ABAC são executadas em cada execução de consulta para cada linha ou coluna aplicável. UDFs ineficientes podem:
- Bloqueie a execução paralela e a redução de predicados
 - Forçar junções ou pesquisas caras por linha
 - Aumente o tempo de consulta de milissegundos para segundos (ou mais)
 - Quebre o armazenamento em cache e a vetorização
 - Tornar a análise e os relatórios não confiáveis
 
Em ambientes de produção, isso pode tornar a governança de dados um gargalo em vez de um facilitador.
Regras de ouro para UDFs ABAC
- Mantenha a simplicidade : dê preferência a declarações 
CASEbásicas e expressões booleanas claras. - Mantenha-se determinista : a mesma entrada deve sempre produzir a mesma saída para armazenamento em cache e junção consistente.
 - Evite chamadas externas : não há chamadas de API ou pesquisas em outros bancos de dados.
 - Use apenas funções integradas dentro do seu UDF : não chame outros UDFs de dentro de um UDF.
 - Teste em escala : valide o desempenho em pelo menos 1 milhão de linhas.
 - Minimize a complexidade : evite aninhamento em vários níveis e chamadas de funções desnecessárias.
 - Referências somente de coluna: faça referência somente às colunas da tabela de destino para ativar o pushdown.
 - Prefira UDFs SQL a Python : para melhor desempenho. Se você precisar usar Python, marque explicitamente UDFs como 
DETERMINISTICquando elas não envolverem lógica e dependências não determinísticas. 
Antipadrões comuns a serem evitados
- Chamadas de API externas dentro de UDFs : causam latência de rede, timeouts e pontos únicos de falha.
 - Subconsultas ou junções complexas : força a junção de loops aninhados e impede a otimização.
 - Regex pesado em texto grande : uso intenso de CPU e memória por linha.
 - Pesquisas de metadados : evite verificações por linha que atinjam metadados ou fontes de identidade, como consultas 
information_schema, pesquisas de perfil de usuário,is_member()ouis_account_group_member(). Adiciona digitalizações extras para cada registro. - Geração de SQL dinâmico : não há otimização de consultas e impede o armazenamento em cache.
 - Lógica não determinística : impede o armazenamento em cache e a união consistente, consulte Impacto da lógica não determinística.
 
Mantenha as verificações de acesso nas políticas, não nas UDFs
Um erro comum é chamar is_account_group_member() ou is_member() diretamente dentro de um UDF. Isso torna a função mais lenta e dificulta a reutilização da UDF.
Em vez disso, siga esse padrão:
- Função UDF : Concentrar-se apenas em como transformar, mascarar ou filtrar os dados. Use somente as colunas e os parâmetros passados para ele.
 - Função da política : Defina quem (diretores, grupos) e quando (tags) o UDF deve ser aplicado, fazendo referência aos diretores na política ABAC.
 
Impacto da lógica não determinística
Alguns cenários (como o mascaramento aleatório para pesquisa) exigem uma saída diferente a cada vez.
Se você precisar usar funções não determinísticas:
- Espere um desempenho mais lento devido à ausência de cache.
 - pode falhar ou retornar resultados inconsistentes.
 - Os relatórios podem mostrar dados diferentes entre as execuções.
 - A solução de problemas e a validação podem ser mais difíceis.
 
Exemplos de UDF
abaixo são padrões de fácil produção para mascaramento de colunas e filtragem de linhas. Todos os exemplos seguem as práticas recomendadas de desempenho do ABAC: lógica simples, comportamento determinístico, nenhuma chamada externa e uso apenas de funções integradas.
Máscara de coluna: rápida e determinística
-- Deterministically pseudonymize patient_id with a version tag for rotation.
CREATE OR REPLACE FUNCTION mask_patient_id_fast(patient_id STRING)
RETURNS STRING
DETERMINISTIC
RETURN CONCAT('REF_', SHA2(CONCAT(patient_id, ':v1'), 256));
Por que isso funciona:
- Declaração CASE simples
 - Sem dependências externas
 - Resultados determinísticos para junção consistente
 - Manter a lógica principal fora do UDF
 
Máscara de coluna: revelação parcial sem pontos ativos de regex
-- Reveal only the last 4 digits of an SSN, masking the rest.
CREATE OR REPLACE FUNCTION mask_ssn_last4(ssn STRING)
RETURNS STRING
DETERMINISTIC
RETURN CASE
  WHEN ssn IS NULL THEN NULL
  WHEN LENGTH(ssn) >= 4 THEN CONCAT('XXX-XX-', RIGHT(REGEXP_REPLACE(ssn, '[^0-9]', ''), 4))
  ELSE 'MASKED'
END;
Por que isso funciona:
- Usa um único regex leve para remover números que não sejam dígitos
 - Evita várias passagens de regex em campos de texto grandes
 
Máscara de coluna: pseudonimização determinística com controle de versão
-- Create a consistent pseudonymized reference ID.
CREATE OR REPLACE FUNCTION mask_id_deterministic(id STRING)
RETURNS STRING
DETERMINISTIC
RETURN CONCAT('REF_', SHA2(CONCAT(id, ':v1'), 256));
Por que isso funciona:
- Preserva a união em todo o conjunto de dados mascarados
 - Inclui uma tag de versão () para suportar a rotação do key sem quebrar os dados históricos intencionalmente
 
Filtro de linha: fácil de pressionar por região
-- Returns TRUE if the row's state is in the allowed set.
CREATE OR REPLACE FUNCTION filter_by_region(state STRING, allowed ARRAY<STRING>)
RETURNS BOOLEAN
DETERMINISTIC
RETURN array_contains(TRANSFORM(allowed, x -> lower(x)), lower(state));
Por que isso funciona:
- Lógica booleana simples
 - Referencia somente colunas da tabela
 - Permite a inserção e vetorização de predicados
 
Filtro de linha: multicondição determinística
-- Returns TRUE if the row's region is in the allowed set.
CREATE OR REPLACE FUNCTION filter_region_in(region STRING, allowed_regions ARRAY<STRING>)
RETURNS BOOLEAN
DETERMINISTIC
RETURN array_contains(TRANSFORM(allowed_regions, x -> lower(x)), lower(region));
Por que isso funciona:
- Suporta várias funções e regiões geográficas em uma função
 - Mantém a lógica plana para uma melhor otimização
 
Teste de desempenho do UDF
Use testes de escala sintética para validar o comportamento e o desempenho antes da produção. Por exemplo:
WITH test_data AS (
  SELECT
    patient_id,
    your_mask_function(patient_id) as masked_id,
    current_timestamp() as start_time
  FROM (
    SELECT CONCAT('PAT', LPAD(seq, 6, '0')) as patient_id
    FROM range(1000000)  -- 1 million test rows
  )
)
SELECT
  COUNT(*) as rows_processed,
  MAX(start_time) - MIN(start_time) as total_duration,
  COUNT(*) / EXTRACT(EPOCH FROM (MAX(start_time) - MIN(start_time))) as rows_per_second
FROM test_data;
fundição do tipo máscara de coluna ABAC
O Databricks converte automaticamente a saída das funções de máscara de coluna resolvidas a partir de políticas ABAC para corresponder ao tipo de dados da coluna de destino. Isso garante a consistência de tipos e um comportamento de consulta confiável ao mascarar colunas.
Como funciona a fundição automática
- Avaliação da política : A política ABAC determina se o mascaramento deve ser aplicado.
 - Execução da função de máscara : Se o mascaramento for necessário, a função de máscara é executada.
 - Conversão automática : O resultado da função de máscara é convertido automaticamente para o tipo de dados da coluna de destino.
 - Resultado retornado : O resultado digitado corretamente é retornado à consulta.
 
Todo o comportamento de conversão segue os padrões ANSI SQL para operações CAST . Para obter detalhes completos sobre compatibilidade, consulte a seção Devoluções.
Melhores práticas para mascaramento seguro de tipos
Siga esses padrões para garantir que suas funções de máscara funcionem de forma confiável com a conversão automática de tipos e evitar falhas em tempo de execução.
Funções de máscara consistentes com o tipo de design
Garantir que as funções de máscara retornem tipos compatíveis com a coluna de destino:
-- Successful: Returns same type as target column
CREATE FUNCTION safe_salary_mask(salary DOUBLE, user_role STRING)
RETURNS DOUBLE
RETURN CASE
  WHEN user_role IN ('admin', 'hr') THEN salary
  WHEN user_role = 'manager' THEN ROUND(salary / 1000) * 1000
  ELSE 0.0
END;
-- Unsuccessful: Returns different type that might not cast
CREATE FUNCTION risky_salary_mask(salary DOUBLE, user_role STRING)
RETURNS STRING
RETURN CASE
  WHEN user_role IN ('admin', 'hr') THEN CAST(salary AS STRING)
  ELSE 'CONFIDENTIAL'  -- This will fail casting to DOUBLE
END;
Use VARIANT para mascaramento flexível.
Para cenários de mascaramento complexos com tipos de saída variados, use VARIANT para acomodar diferentes tipos de dados:
CREATE FUNCTION flexible_mask(data VARIANT)
RETURNS VARIANT
RETURN CASE
  WHEN schema_of_variant(data) = 'INT' THEN 0::VARIANT
  WHEN schema_of_variant(data) = 'DATE' THEN DATE'1970-01-01'::VARIANT
  WHEN schema_of_variant(data) = 'DOUBLE' THEN 0.00::VARIANT
  ELSE NULL::VARIANT
END;
Teste com diversos cenários de dados.
Teste as funções de máscara com diferentes padrões de dados e contextos de usuário antes da implantação em produção:
-- Test different access levels using different users/groups
SELECT * FROM sensitive_table;
-- Test casting behavior for different regional scenarios
SELECT CAST(mask_transaction(transaction_info, 'US') AS STRUCT<id INT, amount DOUBLE>);
SELECT CAST(mask_transaction(transaction_info, 'JP') AS STRUCT<id INT, amount DOUBLE>);
SELECT CAST(mask_transaction(transaction_info, 'CN') AS STRUCT<id INT, amount DOUBLE>);
Limitações
Para limitações em filtros de linha e máscaras de coluna, consulte Limitações.