ABAC ポリシーの UDF のベスト プラクティス
このページでは、Unity Catalog の ABAC 行フィルター ポリシーと列マスク ポリシーで使用する高パフォーマンスのユーザー定義関数 (UDF) を記述する方法について説明します。
UDF パフォーマンスが重要な理由
ABAC ポリシーは、該当するすべての行または列の クエリ実行ごとに 実行されます。非効率な UDF には次のような可能性があります。
- 並列実行と述語のプッシュダウンをブロックする
- 高価な結合または行ごとのルックアップを強制する
- クエリ時間をミリ秒から秒 (またはそれ以上) に増やします。
- キャッシュとベクトル化の中断
- アナリティクスとレポートの信頼性を低下させる
本番運用環境では、これによりデータガナンスがイネーブラーではなくボトルネックになる可能性があります。
ABAC UDF の黄金律
- シンプルに保ちます : 基本的な
CASE
ステートメントと明確なブール式を優先します。 - 決定論的であり続ける : 同じ入力で、キャッシュと一貫性のある結合に対して常に同じ出力を生成する必要があります。
- 外部呼び出しを避ける: API 呼び出しや他のデータベースへのルックアップはありません。
- UDF内の組み込み関数のみを使用する :UDF内から他のUDFを呼び出さないでください。
- 大規模なテスト : 少なくとも 100 万行のパフォーマンスを検証します。
- 複雑さを最小限に抑える : マルチレベルのネストや不要な関数呼び出しを回避します。
- 列のみの参照 : ターゲット テーブルの列のみを参照して、プッシュダウンを有効にします。
避けるべき一般的なアンチパターン
- UDF 内の外部 API 呼び出し : ネットワーク遅延、タイムアウト、単一障害点が発生します。
- 複雑なサブクエリまたは結合 : ネストされたループ結合を強制し、最適化を妨げます。
- 大きなテキストに対する重い正規表現 : 行ごとに CPU とメモリを大量に消費します。
- メタデータ参照 : メタデータまたは ID ソース (
information_schema
クエリ、ユーザープロファイル検索、is_member()
、is_account_group_member()
など) にヒットする行ごとのチェックは避けてください。 各レコードにスキャンを追加します。 - 動的 SQL 生成 : クエリの最適化がなく、キャッシュが防止されます。
- 非決定論的ロジック : キャッシュと一貫性のある結合を防止します ( 「非決定論的ロジックの影響」を参照してください)。
アクセス・チェックを UDF ではなくポリシーに保持する
よくある間違いは、UDF 内で直接 is_account_group_member
() または is_member()
を呼び出すことです。これにより、関数の速度が低下し、UDF の再利用が難しくなります。
代わりに、次のパターンに従ってください。
- UDF ロール : データの変換、マスク、またはフィルター処理の方法のみに焦点を当てます。渡された列とパラメーターのみを使用します。
- ポリシー role : ABAC ポリシーのプリンシパルを参照して、 UDF を適用するユーザー (principals, グループ) といつ (タグ) を定義します。
非決定論的ロジックの影響
一部のシナリオ (研究用のランダム化マスキングなど) では、毎回異なる出力が必要です。
非決定論的関数を使用する必要がある場合:
- キャッシュがないため、パフォーマンスが低下することが予想されます。
- JOIN は失敗したり、一貫性のない結果を返したりする可能性があります。
- レポートには、実行間で異なるデータが表示される場合があります。
- トラブルシューティングと検証は、より困難になる可能性があります。
UDF の例
以下は、列マスキングと行フィルタリングの本番運用に適したパターンです。 すべての例は、単純なロジック、決定論的な動作、外部呼び出しなし、組み込み関数のみの使用など、ABAC パフォーマンスのベスト プラクティスに従っています。
カラムマスク:高速で決定論的
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));
これが機能する理由:
- 単純な CASE ステートメント
- 外部依存関係なし
- 一貫性のある結合の決定論的な結果
- プリンシパル ロジックを UDF から除外する
列マスク: 正規表現ホットスポットなしの部分表示
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;
これが機能する理由:
- 単一の軽量正規表現を使用して、数字以外のものを削除します
- 大きなテキストフィールドでの複数の正規表現パスを回避します
列マスク:バージョン管理による決定論的仮名化
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));
これが機能する理由:
- マスクされたデータセット間で結合を保持します
- バージョンタグ ()を意図的にヒストリカルデータを壊さずにキー回転をサポートする
行フィルター: 地域別のプッシュダウン フレンドリー
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));
これが機能する理由:
- 単純なブール論理
- テーブル列のみを参照
- 述語のプッシュダウンとベクトル化が可能
行フィルター:決定論的複数条件
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));
これが機能する理由:
- 1つの機能で複数の役割と地域をサポート
- ロジックをフラットに保ち、最適化を改善
UDF パフォーマンスのテスト
本番運用の前に、合成スケールテストを使用して動作とパフォーマンスを検証します。 例えば:
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;