Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
本页演示如何编写高性能用户定义函数(UDF),以便在 Unity 目录中的 ABAC 行筛选器和列掩码策略中使用。
为什么 UDF 性能很重要
ABAC 策略针对每个适用的行或列 的每个查询执行 运行。 低效 UDF 可以:
- 阻止并行执行和谓词下推
- 强制每行的昂贵联接或查找
- 将查询时间从毫秒增加到秒(或更多)
- 中断缓存和矢量化
- 使分析和报告不可靠
在生产环境中,这会使数据管理成为瓶颈,而不是启用器。
ABAC UDF 的黄金规则
-
保持简单:支持基本
CASE
语句和清除布尔表达式。 - 保持确定性:相同的输入应始终生成相同的输出来缓存和一致的联接。
- 避免外部调用:没有对其他数据库的 API 调用或查找。
- 仅使用 UDF 中的内置函数:不要从 UDF 中调用其他 UDF。
- 大规模测试:验证至少 100 万行的性能。
- 尽量减少复杂性:避免多级嵌套和不必要的函数调用。
- 仅列引用:仅引用目标表的列以启用下推。
要避免的常见反模式
- 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 性能最佳做法:简单的逻辑、确定性行为、没有外部调用,并且只使用内置函数。
列掩码:快速确定性
-- 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));
为什么这样工作:
- 简单 CASE 语句
- 无外部依赖项
- 一致联接的确定性结果
- 使主体逻辑远离 UDF
列掩码:不带正则表达式热点的部分显示
-- 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;
为什么这样工作:
- 使用单个轻型正则表达式去除非数字
- 避免在大型文本字段中传递多个正则表达式
列掩码:使用版本控制进行确定性假名化
-- 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));
为什么这样工作:
- 跨掩码数据集保留联接
- 包括版本标记(:v1),以支持密钥轮换,而无需有意破坏历史数据
行筛选器:按区域对下推友好
-- 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;