Pular para o conteúdo principal

Melhores práticas de políticas de UDFs para ABAC

info

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 CASE bá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 DETERMINISTIC quando 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() ou is_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

SQL
-- 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

SQL
-- 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

SQL
-- 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

SQL
-- 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

SQL
-- 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:

SQL
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

  1. Avaliação da política : A política ABAC determina se o mascaramento deve ser aplicado.
  2. Execução da função de máscara : Se o mascaramento for necessário, a função de máscara é executada.
  3. Conversão automática : O resultado da função de máscara é convertido automaticamente para o tipo de dados da coluna de destino.
  4. 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:

SQL
-- 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:

SQL
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:

SQL
-- 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.