重要
此功能目前以公共预览版提供。
本页演示如何编写高性能用户定义函数(UDF),以便在 Unity 目录中的 ABAC 行筛选器和列掩码策略中使用。
为什么 UDF 性能很重要
ABAC 策略针对每个适用的行或列 的每个查询执行 运行。 低效 UDF 可以:
- 阻止并行执行和谓词下推
- 强制每行的昂贵联接或查找
- 将查询时间从毫秒增加到秒(或更多)
- 中断缓存和矢量化
- 使分析和报告不可靠
在生产环境中,这会使数据管理成为瓶颈,而不是启用器。
ABAC UDF 的黄金规则
-
保持简单:支持基本
CASE语句和清除布尔表达式。 - 保持确定性:相同的输入应始终生成相同的输出来缓存和一致的联接。
- 避免外部调用:没有对其他数据库的 API 调用或查找。
- 仅使用 UDF 中的内置函数:不要从 UDF 中调用其他 UDF。
- 大规模测试:验证至少 100 万行的性能。
- 尽量减少复杂性:避免多级嵌套和不必要的函数调用。
- 仅列引用:仅引用目标表的列以启用下推。
-
首选 SQL 而不是 Python UDF:以提高性能。 如果必须使用 Python,请在 UDF 不涉及非确定性逻辑和依赖项的情况下显式标记为
DETERMINISTIC。
要避免的常见反模式
- UDF 中的外部 API 调用:导致网络延迟、超时和单一故障点。
- 复杂的子查询或联接:强制嵌套循环联接并防止优化。
- 大型文本上的重正则表达式:每行 CPU 和内存密集型。
-
元数据查找:避免按行检查命中元数据或标识源,例如
information_schema查询、用户配置文件查找或is_member()is_account_group_member()。 为每个记录添加额外的扫描。 - 动态 SQL 生成:无查询优化并阻止缓存。
- 非确定性逻辑:防止缓存和一致联接,请参阅 非确定性逻辑的影响。
在策略中保留访问检查,而不是 UDF
常见的错误是调用 is_account_group_member() 或 is_member() 直接在 UDF 内部。 这使得函数变慢,并使 UDF 更易于重用。
相反,请遵循以下模式:
- UDF 角色:仅关注如何转换、屏蔽或筛选数据。 仅使用传入它的列和参数。
- 策略角色:定义谁(主体、组)以及何时(标记)UDF 应通过引用 ABAC 策略中的主体来应用。
非确定性逻辑的影响
某些方案(如用于研究的随机掩码)每次都需要不同的输出。
如果必须使用非确定性函数:
- 由于没有缓存,预期性能会降低。
- JOIN 可能会失败或返回不一致的结果。
- 报表可能会显示运行之间的不同数据。
- 故障排除和验证可能更难。
UDF 示例
下面是用于列掩码和行筛选的生产友好模式。 所有示例都遵循 ABAC 性能最佳做法:简单的逻辑、确定性行为、没有外部调用,并且只使用内置函数。
列掩码:使用版本控制进行确定性假名化
-- 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));
为什么这样工作:
- 简单 CASE 语句
- 无外部依赖项
- 一致联接的确定性结果
- 将核心逻辑保留在 UDF 之外
- 跨掩码数据集保留联接
- 包括版本标记(:v1),以支持密钥轮换,而无需有意破坏历史数据
列掩码:不带正则表达式热点的部分显示
-- 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;
为什么这样工作:
- 使用单个轻型正则表达式去除非数字
- 避免在大型文本字段中传递多个正则表达式
行筛选器:按区域对下推友好
-- 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));
为什么这样工作:
- 简单的布尔逻辑
- 仅引用表列
- 启用谓词下推和向量化
行筛选器:确定性多条件
-- 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));
为什么这样工作:
- 支持一个函数中的多个角色和地理位置
- 保持逻辑平面,以便更好地优化
测试 UDF 性能
使用综合规模测试在生产之前验证行为和性能。 例如:
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;
ABAC 列掩码类型转换
Databricks 自动将从 ABAC 策略解析出的列掩码函数的输出转换,以匹配目标列的数据类型。 这可确保屏蔽列时类型一致性和可靠的查询行为。
自动类型转换的工作原理
- 策略评估:ABAC 策略确定是否应应用掩码。
- 掩码函数执行:如果需要掩码,掩码函数将执行。
- 自动强制转换:掩码函数的结果会自动转换为目标列的数据类型。
- 结果返回:正确键入的结果将返回到查询。
所有类型转换行为均遵循 ANSI SQL 标准进行 CAST 操作。 有关完整兼容性详细信息,请参阅 “返回”。
类型安全掩码的最佳做法
遵循这些模式,确保您的掩码函数在自动类型转换下可靠工作,并避免运行时故障。
设计类型一致性掩码函数
确保掩码函数返回与目标列兼容的类型:
-- 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;
使用 VARIANT 进行灵活的掩码处理
对于具有不同输出类型的复杂掩码方案,用于 VARIANT 适应不同的数据类型:
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;
使用各种数据方案进行测试
在生产部署之前,使用不同数据模式和用户上下文测试掩码函数:
-- 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>);
局限性
有关行文件文件和列掩码的限制,请参阅“限制”。