メインコンテンツまでスキップ

ABAC ポリシーの UDF のベスト プラクティス

備考

プレビュー

この機能は パブリック プレビュー段階です。

このページでは、Unity Catalog の ABAC 行フィルター ポリシーと列マスク ポリシーで使用する高パフォーマンスのユーザー定義関数 (UDF) を記述する方法について説明します。

UDF パフォーマンスが重要な理由

ABAC ポリシーは、該当するすべての行または列の クエリ実行ごとに 実行されます。非効率な UDF には次のような可能性があります。

  • 並列実行と述語のプッシュダウンをブロックする
  • 高価な結合または行ごとのルックアップを強制する
  • クエリ時間をミリ秒から秒 (またはそれ以上) に増やします。
  • キャッシュとベクトル化の中断
  • アナリティクスとレポートの信頼性を低下させる

本番運用環境では、これによりデータガナンスがイネーブラーではなくボトルネックになる可能性があります。

ABAC UDF の黄金律

  • シンプルに保ちます : 基本的な CASE ステートメントと明確なブール式を優先します。
  • 決定論的であり続ける : 同じ入力で、キャッシュと一貫性のある結合に対して常に同じ出力を生成する必要があります。
  • 外部呼び出しを避ける: API 呼び出しや他のデータベースへのルックアップはありません。
  • UDF内の組み込み関数のみを使用する :UDF内から他のUDFを呼び出さないでください。
  • 大規模なテスト : 少なくとも 100 万行のパフォーマンスを検証します。
  • 複雑さを最小限に抑える : マルチレベルのネストや不要な関数呼び出しを回避します。
  • 列のみの参照 : ターゲット テーブルの列のみを参照して、プッシュダウンを有効にします。
  • パフォーマンス向上のため、 Python UDF よりも SQL を優先します 。Python を使用する必要がある場合は、非決定論的なロジックと依存関係が関係しない UDF を明示的にDETERMINISTICとしてマークします。

避けるべき一般的なアンチパターン

  • 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
-- 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から除外する
  • マスクされたデータセット間で結合を保持します
  • バージョンタグ ()を意図的にヒストリカルデータを壊さずにキー回転をサポートする

列マスク: 正規表現ホットスポットなしの部分表示

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

ABAC列マスク型鋳造

Databricks 、ABAC ポリシーから解決された列マスク関数の出力を、ターゲットカラムのデータ型と一致するように自動的にキャストします。 これにより、列をマスクする際の型の一貫性と信頼性の高いクエリ動作が保証されます。

自動キャストの仕組み

  1. ポリシー評価 : ABAC ポリシーは、マスキングを適用する必要があるかどうかを決定します。
  2. マスク関数の実行 : マスクが必要な場合は、マスク関数が実行されます。
  3. 自動キャスト : マスク関数の結果はターゲットカラムのデータ型に自動的にキャストされます。
  4. 結果の戻り : 適切に入力された結果がクエリに返されます。

すべてのキャスト動作は、 CAST操作の ANSI SQL 標準に従います。完全な互換性の詳細については、 「返品」を参照してください。

型安全なマスキングのベストプラクティス

マスク関数が自動型キャストで確実に動作し、障害を回避するには、これらのパターンに従ってください。

型一貫性のあるマスク関数を設計する

マスク関数の戻り値の型がターゲットカラムと互換性があることを確認します。

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;

柔軟なマスキングにはVARIANTを使用する

さまざまな出力タイプを持つ複雑なマスキング シナリオでは、さまざまなデータ タイプに対応するためにVARIANTを使用します。

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;

多様なデータシナリオでテストする

本番運用のデプロイの前に、さまざまなデータ パターンとユーザー コンテキストを使用してマスク関数をテストします。

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>);

制限事項

行フィルターと列マスクの制限については、 「制限事項」を参照してください。