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: 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:
- Declaração CASE simples
- Sem dependências externas
- Resultados determinísticos para junção consistente
- Mantém a lógica principal fora da UDF (função definida pelo usuário).
- 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
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
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.