Leistungsaspekte für ABAC-Richtlinien

Zeilenfilter- und Spaltenformatrichtlinien führen Logik ein, die zur Abfragezeit ausgeführt wird. Daher hängt die Leistung davon ab, wie Sie Ihre Richtlinien entwerfen. Es gibt keinen einzigen richtigen Ansatz für jede Arbeitsauslastung. Der beste Ansatz hängt von Ihrem Datenvolumen, Abfragemustern, der Interaktion Ihrer Benutzer mit geschützten Tabellen und dem gewünschten Maskierungs- oder Filterverhalten ab. In den folgenden Abschnitten werden die häufigsten Leistungsaspekte behandelt. Verwenden Sie sie als Prüfliste beim Entwerfen Ihrer Richtlinien, und testen Sie sie mit repräsentativen Abfragen , bevor Sie sie in der Produktion bereitstellen.

Leistungsübersicht

Überlegung Description
Verringern der UDF-Komplexität Komplexe UDF-Logik kann die Abfrageleistung behindern; Einfache Funktionen führen besser aus.
Ansatz zur Zielgruppenansprache Entscheiden Sie, ob prinzipalbasierte Logik in den Klauseln der Richtlinie TO/EXCEPT oder innerhalb der UDF mithilfe von Identitätsfunktionen implementiert werden soll.
Verwenden von deterministischen, fehlersicheren Ausdrücken Nicht deterministische Funktionen und Ausdrücke, die Fehler auslösen können, reduzieren die Fähigkeit des Optimierers, Ergebnisse zwischenzuspeichern und Vorgänge neu anzuordnen.
Avoid Python UDFs Verwenden Sie SQL UDFs anstelle von Python UDFs, wenn möglich.
Nachschlagetabellen klein halten UDFs, die auf externe Tabellen verweisen, funktionieren am besten, wenn diese Tabellen klein genug sind, um gebroadcastet zu werden.
Grundlegendes zum Prädikat-Pushdown für geschützte Tabellen Abfragen gegen geschützte Tabellen können möglicherweise nicht von Partition-Pruning oder Liquid Clustering profitieren, wenn Prädikate Nebenwirkungen haben.
Spaltenmasken nach Möglichkeit wiederverwenden Jede unterschiedliche Maske auf einer Tabelle fügt Mehraufwand hinzu; Durch erneutes Wiederverwenden derselben Funktion über Spalten hinweg kann dies reduziert werden.
Vermeiden der regex-Maskierung für große Textfelder Regex-basierte Maskierung für serialisierte Dokumente zwingt das Modul, die gesamte Nutzlast für jede Zeile zu scannen und neu zu schreiben.

Verringern der UDF-Komplexität

Die UDF in einer ABAC-Richtlinie wird während der Abfrageausführung für jede Zeile (Zeilenfilter) oder jeden übereinstimmenden Spaltenwert (Spaltenmasken) ausgeführt. Die Komplexität der UDF wirkt sich direkt auf die Abfrageleistung aus.

Zu tun:

  • Einfache UDFs beibehalten. Verwenden Sie bevorzugt grundlegende CASE Anweisungen und einfache boolesche Ausdrücke.
  • So weit wie möglich sollten Sie nur auf Zieltabellenspalten in UDFs verweisen. Dadurch wird das Prädikat-Pushdown ermöglicht.
  • Wenn Ihre UDF auf externe Tabellen verweisen muss, halten Sie alle externen Verweise klein genug, um zu übertragen. Stellen Sie sicher, dass referenzierte Tabellen optimiert und partitioniert sind, um dem Zugriffsmuster der Richtlinie zu entsprechen. Partitionieren Sie beispielsweise eine Richtlinien-Suchtabelle nach Benutzername.
  • Vermeiden Sie Schachtelung auf mehreren Ebenen und unnötige Funktionsaufrufe. Verwenden Sie integrierte SQL-Funktionen so weit wie möglich.

Vermeiden:

  • Externe API-Aufrufe oder Abfragen anderer Datenbanken in UDFs. Netzwerkanrufe können zusätzliche Latenzen und Timeouts einführen.
  • Komplexe Unterabfragen oder Verknüpfungen bei großen Tabellen. Diese verhindern Broadcast-Hash-Joins und erzwingen geschachtelte Schleifen-Joins.
  • Schweres Regex für große Textfelder. Siehe Regex für große Textfelder.
  • Abfragen pro Zeile, z. B. Abfragen von information_schema.

Ansatz für das Targeting von Entitäten

Wenn Sie eine ABAC-Richtlinie schreiben, entscheiden Sie, wo die prinzipalbasierte Logik implementiert werden soll: in den Klauseln der Richtlinie TO/EXCEPT oder innerhalb der UDF mithilfe von Identitätsfunktionen wie current_user() und .is_account_group_member()

Verwenden Sie im Allgemeinen die Klauseln der Richtlinie TO/EXCEPT , um zu definieren, für welche Prinzipale eine Richtlinie gilt. Dadurch bleibt die Richtliniendefinition einfacher, und die UDF konzentriert sich auf datentransformation, Filterung oder Maskierung. Die EXCEPT Klausel entfernt die Richtlinie vollständig für ausgenommene Benutzer, was bedeutet, dass keine UDF-Ausführung für diese Benutzer erfolgt.

Wenn die bedingte Logik für die Prinzipalklauseln der Richtlinie zu komplex ist, sind Identitätsfunktionen innerhalb der UDF eine mögliche Alternative. Diese Funktionen werden einmal während der Abfrageanalyse aufgelöst, nicht pro Zeile. Mehrere Aufrufe an Identitätsfunktionen wie is_account_group_member() bei unterschiedlichen Gruppenargumenten führen zu einem einzelnen UC-API-Aufruf, sodass die Leistung in der Regel minimal ist.

Die folgende UDF ist effizient, da sie nur auf Identitätsfunktionen basiert, die während der Abfrageanalyse einmal aufgelöst werden:

CREATE OR REPLACE FUNCTION rowfilter()
RETURNS BOOLEAN
RETURN
  CASE
    WHEN is_account_group_member('auditors') OR is_account_group_member('external-auditors') THEN true
    WHEN is_account_group_member('low-privileged') THEN false
    WHEN session_user() = 'admin@organization.com' THEN true
    ELSE false
  END;

Im Gegensatz dazu ist die folgende UDF langsamer, da sie Berechtigungen in einer sekundären Tabelle codiert, die eine zusätzliche Tabellensuche erfordert:

CREATE OR REPLACE FUNCTION rowfilter()
RETURNS BOOLEAN
RETURN
  CASE WHEN EXISTS(SELECT 1 FROM access_lease WHERE user = session_user()) THEN true
  ELSE false END;

Verwenden von deterministischen, fehlersicheren Ausdrücken

Verwenden Sie deterministische Ausdrücke, die keine Fehler in Richtlinien-UDFs und in Abfragen gegen geschützte Tabellen auslösen können.

Nicht deterministische Funktionen (Funktionen, die unterschiedliche Ergebnisse für dieselbe Eingabe zurückgeben, wie z. B. rand() oder now()), verhindern, dass der Optimierer Ergebnisse zwischenspeichert oder konstanten Faltung anwendet. Sowohl SQL als auch Python UDFs unterstützen das Schlüsselwort DETERMINISTIC in der CREATE FUNCTION-Anweisung. Bei SQL UDFs leitet der Optimierer den Determinismus automatisch vom Funktionstext ab, Sie können ihn aber auch explizit festlegen. Für Python UDFs kann der Optimierer den Funktionstext nicht untersuchen. Daher ist es wichtig, dass das explizite Markieren einer Python UDF als deterministisch wichtig ist, um das Zwischenspeichern von Ergebnissen für Aufrufe mit identischen Argumenten zu ermöglichen.

Einige Ausdrücke lösen Fehler aus, wenn die Eingaben nicht gültig sind, z. B. ANSI-Division auf einem Null-Nenner. Wenn der SQL-Compiler diese Möglichkeit erkennt, kann er keine Pushvorgänge wie Filter im Abfrageplan ausführen. Dadurch können Fehler ausgelöst werden, die Informationen zu Werten anzeigen, bevor das Filtern oder Maskieren wirksam wird. Verwenden Sie fehlersichere Alternativen wie try_divide anstelle von /, try_cast anstelle von CAST, und try_to_number anstelle von to_number. Diese rückgaben NULL auf Fehler anstatt zu werfen, wodurch der Optimierer Ausdrücke frei anordnen und falten kann.

Avoid Python UDFs

Vermeiden Sie Python UDFs in ABAC-Richtlinien, wenn Sie es vermeiden können. Python UDFs müssen in einer SQL-UDF eingebettet werden, um in Richtlinien verwendet zu werden. Sie sind im Allgemeinen langsamer als SQL UDFs, da der Optimierer sie nicht inline oder optimieren kann, und die Python-Funktion wird für jede Zeile in der Zieltabelle ausgeführt.

Wenn eine Python-UDF unvermeidbar ist, lesen Sie Deterministic, error-safe expressions, um zu erfahren, wie sie als DETERMINISTIC gekennzeichnet werden kann, um die Ergebniszwischenspeicherung zu aktivieren.

Nachschlagetabellen klein halten

Ein gängiges Muster ist das Überprüfen von Zugriffsrechten auf eine kleine Nachschlagetabelle (z. B. eine Tabelle, die Benutzern prioritätsstufen zuordnet). Wenn die Nachschlagetabelle wesentlich kleiner als die Zieltabelle ist, wandelt der Optimierer die Unterabfrage in einen Broadcast-Hash-Join um. Die Nachschlagetabelle wird in jeden Executor kopiert und im Arbeitsspeicher als Hashmap gespeichert, wodurch die schnelle Filterung während des Tabellenscans ermöglicht wird. Ein Codebeispiel finden Sie im Abschnitt Nachschlagetabellen in ABAC-Richtlinien-UDFs.

  • Wenn die Nachschlagetabelle groß ist, greift der Optimierer auf einen Shuffle-Join zurück, was langsamer ist.
  • Wenn das Nachschlageprädikat komplex ist (nicht eine einfache Gleichheitsprüfung), kann auch der Broadcast-Join möglicherweise nicht in Frage kommen.
  • Auch beim Broadcast Hash Join entstehen für jede einzelne Zeile während der Ausführung weiterhin die Kosten einer Hash-Tabellen-Suche.

Grundlegendes zum Prädikat-Pushdown für geschützte Tabellen

Prädikatspushdown ist eine Leistungsoptimierung, bei der die Engine Ihre Filterbedingungen auf die Speicherebene verschiebt. Auf diese Weise kann das Modul ganze Partitionen von Daten überspringen, die nicht ihrer Abfrage entsprechen, wodurch die E/A-Funktion erheblich reduziert und die Ausführung beschleunigt wird.

Bei Tabellen, die durch Zeilenfilter und Spaltenmasken geschützt sind, ist diese Optimierung komplexer. Dies ist die häufigste Quelle für Leistungsprobleme mit geschützten Tabellen und die schwerste Problembehandlung, da Richtlinienautoren nicht steuern können, welche Abfragen Benutzer für geschützte Tabellen ausführen.

Auswirkungen der SecureView-Barriere auf Prädikatspushdown

Sowohl ABAC- als auch Zeilenfilter auf Tabellenebene und Spaltenmasken verwenden eine SecureView Barriere, um zu verhindern, dass Prädikate mit Nebenwirkungen über die Richtliniengrenze verschoben werden. Dies schützt vor Datenleck durch Seitenkanäle, kann aber auch die Optimierungen bei Partitionsbereinigung und Liquid Clustering blockieren, was vollständige Tabellenscans erzwingen kann. Dies gilt auch, wenn die Richtlinien-UDF zu einer Konstante true aufgelöst wird (d. h., es werden keine Zeilen tatsächlich gefiltert). Das Vorhandensein einer Richtlinie auf einer Tabelle führt zur SecureView Barriere.

Filter, die von der Barriere betroffen sind

Im Allgemeinen kann der Optimierer nur seiteneffektfreie Prädikate durch die SecureView Barriere verschieben.

  • Gedrückt (schnell): Einfache Gleichheitsvergleiche (WHERE col = 'value') und einfache Bereichsabgleiche (WHERE col > 100). Sie sind frei von Nebenwirkungen und bergen kein Risiko eines Datenlecks.
  • Blockiert (langsamer): Prädikate, die Funktionen aufrufen (WHERE date_format(col, 'yyyy-MM-dd') = '1995-07-29') oder implizite Typumwandlungen einleiten. Diese werden über der SecureView Barriere gehalten, was bedeutet, dass das Modul die Tabelle scannen muss, bevor der Filter angewendet wird.

Das folgende Beispiel zeigt den Unterschied. Erwägen Sie eine Tabelle mit einem Partitionsschlüssel auf o_orderdate und einer Abfrage, die durch date_format gefiltert wird.

EXPLAIN SELECT * FROM orders
WHERE date_format(o_orderdate, 'yyyy-MM-dd') = '1995-07-29'

Ohne eine Richtlinie wird das Prädikat im date_formatPartitionFilters-Knoten dargestellt, was bedeutet, dass das PhotonScan Partition-Pruning aktiv ist.

+- PhotonScan parquet orders[...]
   PartitionFilters: [isnotnull(o_orderdate),
   (date_format(cast(o_orderdate as timestamp), yyyy-MM-dd, ...))]

Mit einer Richtlinie (selbst eine, die immer true zurückgibt) blockiert die SecureView-Barriere das Prädikat. Er wechselt zu einem Tabellenscan oberhalb des ursprünglichen Scans, anstatt in PartitionFilters zu bleiben, was zu einem vollständigen Tabellenscan führt.

+- PhotonFilter (date_format(cast(o_orderdate as timestamp),
   yyyy-MM-dd, ...) = 1995-07-29)
    +- PhotonSecureView orders
        +- PhotonScan parquet orders[...]
           PartitionFilters: [isnotnull(o_orderdate)]

Ein einfacheres Prädikat wie WHERE o_orderdate = '1995-07-29' hat keine Nebenwirkungen und kann auch bei der SecureView Barriere noch gedrückt werden:

+- PhotonSecureView orders
    +- PhotonScan parquet orders[...]
       PartitionFilters: [isnotnull(o_orderdate),
       (o_orderdate = 1995-07-29)]

Verwenden Sie nach Möglichkeit einfache Gleichheitsvorrädikate für geschützte Tabellen. Verwenden Sie für ausgenommene Benutzer die EXCEPT Klausel in der Richtlinie, um die SecureView Barriere vollständig zu beseitigen, wodurch der vollständige Prädikat-Pushdown wiederhergestellt wird.

Spaltenmasken nach Möglichkeit wiederverwenden

Durch das Anwenden vieler unterschiedlicher Spaltenmasken auf eine einzelne Tabelle steigt der Aufwand pro Spalte. Maskieren Sie nur Spalten, die wirklich vertrauliche Daten enthalten.

Wenn mehrere Spalten dieselbe Transformation erfordern (z. B. das Redacting zu NULL oder Ersetzen durch eine feste Zeichenfolge), verwenden Sie die gleiche Maskierungsfunktion, anstatt eine separate Funktion pro Spalte zu erstellen.

Azure Databricks erkennt Richtlinien, die auf die gleiche UDF mit denselben Argumenten verweisen wie dieselbe effektive Maske, sodass das erneute Verwenden von Funktionen unnötigen Aufwand verhindert.

Vermeiden Sie die RegEx-Maskierung für große Textfelder

Die Verwendung von regexp_replace innerhalb einer Spaltenmaske, um Elemente in einem serialisierten Dokument (XML oder JSON, das als STRING-Spalte gespeichert ist) zu redigieren, ist rechenintensiv. regexp_replace durchläuft die gesamte Zeichenfolge für jede Zeile. Der Optimierer behandelt die STRING-Spalte als undurchsichtigen Wert und kann nicht verwendete Teile des Dokuments nicht löschen. Das Modul liest und schreibt die gesamte Nutzlast neu, auch wenn die Abfrage nur einige Felder benötigt.

-- Expensive: regex masking on serialized XML
CREATE FUNCTION mask_xml_pii(raw_xml STRING)
RETURNS STRING
RETURN CASE
  WHEN is_account_group_member('sensitive_data_viewers') THEN raw_xml
  ELSE regexp_replace(raw_xml, '<SSN>[^<]*</SSN>', '<SSN>***</SSN>')
END;

Materialisieren Sie stattdessen die vertraulichen Felder in eine separate Tabelle mit typisierten Spalten und wenden Sie dann Spaltenmasken auf diese skalaren Spalten an. Die Maskenfunktion wird dann für einen einzelnen kleinen Wert pro Zeile und nicht für das gesamte serialisierte Dokument verwendet.

-- Source table stores raw XML as STRING
-- Example XML: <person><SSN>123-45-6789</SSN><name>Alice</name><dob>1990-01-01</dob></person>

-- Recommended: extract fields into a table, then mask scalar values
CREATE TABLE person_data AS
SELECT
  id,
  xpath_string(raw_xml, 'person/SSN') AS ssn,
  xpath_string(raw_xml, 'person/name') AS name,
  xpath_string(raw_xml, 'person/dob') AS date_of_birth,
  raw_xml
FROM raw_records;

-- Simple scalar mask, applied to each extracted column
CREATE FUNCTION redact(val STRING) RETURNS STRING
RETURN CASE
  WHEN is_account_group_member('sensitive_data_viewers') THEN val
  ELSE '***'
END;

Wenn Sie die Daten als Strukturspalte anstelle von XML speichern können, verwenden Sie das flexible Maskierungsmuster VARIANT, um einzelne Felder innerhalb der Struktur zu redagieren. Siehe Maskenstrukturspalten mit VARIANT.

Testen der UDF-Leistung

Tests im großen Umfang

Testen Sie die UDF-Leistung auf mindestens 1 Millionen Zeilen, bevor Sie sie in der Produktion bereitstellen. Führen Sie neben synthetischen Skalierungstests Abfragen aus, die die tatsächliche Arbeitsauslastung darstellen, die Sie in der geschützten Tabelle erwarten. Nehmen Sie inkrementelle Änderungen an Ihren Richtlinienfunktionen vor, und messen Sie die Auswirkungen jeder Änderung, anstatt nur die endgültige Version zu testen.

WITH test_data AS (
  SELECT
    id,
    your_mask_function(id) AS masked_id,
    current_timestamp() AS ts
  FROM (
    SELECT CONCAT('ID', LPAD(CAST(id AS STRING), 6, '0')) AS id
    FROM range(1000000)
  )
)
SELECT
  COUNT(*) AS rows_processed,
  MAX(ts) - MIN(ts) AS total_duration
FROM test_data;

Ersetzen Sie your_mask_function durch die UDF, die Sie testen. Vergleichen Sie Die Ergebnisse mit und ohne die Richtlinie, die angewendet wird, um den Aufwand der Richtlinie zu isolieren.