LakeFormation & Unity Catalog: Data Governance 2026

Gepubliceerd: 14 mei 2026
Leestijd: 12 minuten
Dataplatformen

Vergelijk AWS LakeFormation en Databricks Unity Catalog voor centrale toegangscontrole, lineage en governance in moderne data platforms.

Data Governance in 2026: De Nieuwe Realiteit

Data governance was lange tijd een bijzaak — iets voor compliance-teams en auditeurs, ver weg van de dagelijkse praktijk van data engineers. Dat tijdperk is definitief voorbij. In 2026 is data governance een strategische kernfunctie geworden: organisaties die hun data niet goed kunnen beheren, beveiligen én traceerbaar maken, lopen achter op wet- en regelgeving, verliezen het vertrouwen van klanten en missen zakelijke kansen.

Twee platformen domineren momenteel het gesprek over moderne data governance: AWS Lake Formation voor AWS-native omgevingen en Databricks Unity Catalog voor de lakehouse-wereld. Beide bieden fijnmazige toegangscontrole, data lineage en gecentraliseerd beheer — maar ze doen dat op fundamenteel verschillende manieren, voor verschillende architecturen en met andere trade-offs.

Wat is Data Governance?

Data governance is het geheel van processen, beleid, rollen en technologieën dat zorgt dat data beschikbaar, bruikbaar, integer en beveiligd is binnen een organisatie. In de context van dit artikel focussen we op de technische laag: wie mag welke data zien, waar komt die data vandaan, en hoe houd je dat op schaal bij?

Voor Nederlandse data engineers is dit onderwerp extra relevant door de AVG/GDPR-regelgeving, de toenemende druk vanuit toezichthouders zoals de AP (Autoriteit Persoonsgegevens), en de komst van de EU Data Act. Organisaties moeten kunnen aantonen wie toegang heeft gehad tot welke data, en waarom. Handmatig is dat onbeheersbaar. Geautomatiseerde governance-platformen zijn geen luxe meer — ze zijn essentieel.

AWS Lake Formation: Diepgaand Overzicht

AWS Lake Formation is in 2019 gelanceerd en heeft zich sindsdien ontwikkeld tot het centrale governance-platform voor AWS-native data lakes. Het werkt als een meta-laag bovenop services als Amazon S3, Athena, Redshift Spectrum, EMR en Glue.

1

Data Catalog Registration

Je registreert S3-locaties als "data lake locations". Lake Formation wordt de centrale autoriteit voor toegang tot die locaties — ook al liggen de bestanden fysiek op S3.

2

Glue Data Catalog Integratie

Metadata (tabellen, databases, kolommen) wordt bijgehouden in de AWS Glue Data Catalog. Lake Formation zit als beleidslaag bovenop: elke query naar Athena of Redshift Spectrum passeert Lake Formation voor autorisatie.

3

LF-Tags en Fine-Grained Access Control

Via LF-Tags (attribute-based access control) kun je beleid koppelen aan attributen zoals sensitivity=PII of department=finance. Dit schaalt beter dan resource-based policies per tabel.

4

Row- en Column-Level Security

Lake Formation ondersteunt row filters en column-level masking direct in de catalogus. Geen aanpassingen aan queries nodig — de filtering gebeurt transparant bij data-access.

5

Audit Logging via CloudTrail

Elke data-access wordt gelogd in AWS CloudTrail. Dit vormt de basis voor compliance-rapportage en forensisch onderzoek.

Lake Formation via AWS CDK (Infrastructure as Code)

import aws_cdk as cdk
from aws_cdk import (
    aws_lakeformation as lf,
    aws_iam as iam,
    aws_glue as glue,
    Stack
)

class DataGovernanceStack(Stack):
    def __init__(self, scope, construct_id, **kwargs):
        super().__init__(scope, construct_id, **kwargs)

        # Data Lake Administrator instellen
        admin_role = iam.Role.from_role_arn(
            self, "DataLakeAdmin",
            role_arn=f"arn:aws:iam::{self.account}:role/DataLakeAdminRole"
        )

        lf.CfnDataLakeSettings(
            self, "DataLakeSettings",
            admins=[lf.CfnDataLakeSettings.DataLakePrincipalProperty(
                data_lake_principal_identifier=admin_role.role_arn
            )]
        )

        # LF-Tag aanmaken: sensitivity classificatie
        lf.CfnTag(
            self, "SensitivityTag",
            tag_key="sensitivity",
            tag_values=["public", "internal", "confidential", "pii"]
        )

        # LF-Tag aanmaken: afdeling
        lf.CfnTag(
            self, "DepartmentTag",
            tag_key="department",
            tag_values=["finance", "hr", "marketing", "engineering"]
        )

        # Tag-gebaseerde permission voor data analysts
        analyst_role = iam.Role.from_role_arn(
            self, "AnalystRole",
            role_arn=f"arn:aws:iam::{self.account}:role/DataAnalystRole"
        )

        # Analysts mogen alleen 'public' en 'internal' data zien
        lf.CfnPrincipalPermissions(
            self, "AnalystTagPermission",
            principal=lf.CfnPrincipalPermissions.DataLakePrincipalProperty(
                data_lake_principal_identifier=analyst_role.role_arn
            ),
            resource=lf.CfnPrincipalPermissions.ResourceProperty(
                lf_tag_policy=lf.CfnPrincipalPermissions.LFTagPolicyResourceProperty(
                    catalog_id=self.account,
                    resource_type="TABLE",
                    expression=[
                        lf.CfnPrincipalPermissions.LFTagProperty(
                            tag_key="sensitivity",
                            tag_values=["public", "internal"]
                        )
                    ]
                )
            ),
            permissions=["SELECT"],
            permissions_with_grant_option=[]
        )

Pro Tip: LF-Tags vs. Resource-Based Policies

Begin altijd met LF-Tags (ABAC) in plaats van resource-based policies per tabel. Bij een data lake met honderden tabellen is resource-based beheer onschaalbaar. Met LF-Tags kun je met één beleidsdefinitie duizenden tabellen tegelijk dekken.

Unity Catalog: Het Databricks Governance Framework

Unity Catalog is Databricks' antwoord op de behoefte aan gecentraliseerde governance in de lakehouse-architectuur. Geïntroduceerd in 2022 en inmiddels volwassen in 2026, biedt Unity Catalog een drie-laags naamruimte (catalog.schema.table) en werkt platform-onafhankelijk over AWS, Azure en GCP.

Unity Catalog Naamruimte

Unity Catalog hanteert een hiërarchisch model:
Metastore → Catalog → Schema (Database) → Table/View/Function

Een typisch voorbeeld: prod_catalog.finance_schema.transactions. Dit maakt multi-team governance eenvoudig: teams bezitten hun eigen catalog, maar data kan cross-catalog gedeeld worden via Delta Sharing.

Unity Catalog via Terraform

# providers.tf
terraform {
  required_providers {
    databricks = {
      source  = "databricks/databricks"
      version = "~> 1.30"
    }
  }
}

# Catalog aanmaken per business domain
resource "databricks_catalog" "finance" {
  name    = "finance_prod"
  comment = "Finance domein - productie data"
  
  properties = {
    owner       = "finance-data-team"
    environment = "production"
    cost_center = "FIN-001"
  }
}

# Schema met data classificatie
resource "databricks_schema" "transactions" {
  catalog_name = databricks_catalog.finance.name
  name         = "transactions"
  comment      = "Transactie data - bevat PII"

  properties = {
    sensitivity = "confidential"
    pii_present = "true"
    retention   = "7years"
  }
}

# Tabel met column-level security
resource "databricks_sql_table" "klanten" {
  catalog_name = databricks_catalog.finance.name
  schema_name  = databricks_schema.transactions.name
  name         = "klanten"
  table_type   = "MANAGED"
  
  column {
    name    = "klant_id"
    type    = "BIGINT"
    comment = "Interne klant identifier"
  }
  column {
    name    = "naam"
    type    = "STRING"
    comment = "Volledige naam - PII"
    mask {
      function_name = "prod_catalog.security.naam_mask"
      # Alleen data stewards zien de echte naam
      using_column_names = ["klant_id"]
    }
  }
  column {
    name    = "bsn"
    type    = "STRING"
    comment = "BSN nummer - Strikt vertrouwelijk"
    mask {
      function_name = "prod_catalog.security.bsn_mask"
    }
  }
  column {
    name    = "transactie_bedrag"
    type    = "DECIMAL(15,2)"
    comment = "Bedrag in euros"
  }
}

# Row-level filter: medewerkers zien alleen hun regio
resource "databricks_row_filter" "regio_filter" {
  name         = "regio_toegang_filter"
  catalog_name = databricks_catalog.finance.name
  schema_name  = databricks_schema.transactions.name
  input_column_names = ["regio_code"]
  
  function_body = <<-EOT
    regio_code IN (
      SELECT regio 
      FROM prod_catalog.access_control.medewerker_regio_mapping 
      WHERE medewerker_email = current_user()
    )
  EOT
}

# Grant op catalog niveau
resource "databricks_grants" "finance_catalog_grants" {
  catalog = databricks_catalog.finance.name

  grant {
    principal  = "finance-analysts@bedrijf.nl"
    privileges = ["USE_CATALOG", "USE_SCHEMA"]
  }

  grant {
    principal  = "finance-data-stewards@bedrijf.nl"
    privileges = ["USE_CATALOG", "USE_SCHEMA", "SELECT", "MODIFY"]
  }
}

# Grant op tabel niveau
resource "databricks_grants" "klanten_tabel_grants" {
  table = "${databricks_catalog.finance.name}.${databricks_schema.transactions.name}.klanten"

  grant {
    principal  = "finance-analysts@bedrijf.nl"
    privileges = ["SELECT"]
    # Column masking is automatisch van toepassing
  }
}

Data Lineage Opvragen via Unity Catalog API

import requests
import json
from databricks.sdk import WorkspaceClient

# Databricks SDK initialiseren
w = WorkspaceClient(
    host  = "https://jouw-workspace.azuredatabricks.net",
    token = "dapi..."
)

def haal_tabel_lineage_op(catalog: str, schema: str, table: str) -> dict:
    """
    Haalt upstream en downstream lineage op voor een tabel.
    Vereist Unity Catalog en System Tables toegang.
    """
    endpoint = f"{w.config.host}/api/2.0/lineage-tracking/table-lineage"
    headers = {"Authorization": f"Bearer {w.config.token}"}
    
    payload = {
        "table_name": f"{catalog}.{schema}.{table}",
        "include_entity_lineage": True
    }
    
    response = requests.get(endpoint, headers=headers, params=payload)
    lineage_data = response.json()
    
    print(f"\n=== Lineage voor: {catalog}.{schema}.{table} ===")
    
    # Upstream bronnen (waar komt de data vandaan?)
    print("\n📥 UPSTREAM BRONNEN:")
    for upstream in lineage_data.get("upstreams", []):
        tabel_info = upstream.get("tableInfo", {})
        print(f"  → {tabel_info.get('catalog_name')}.{tabel_info.get('schema_name')}.{tabel_info.get('name')}")
        
        # Notebooks/jobs die deze relatie creëren
        for notebook in upstream.get("notebookInfos", []):
            print(f"     via Notebook: {notebook.get('path')}")
    
    # Downstream consumenten (wie gebruikt deze data?)
    print("\n📤 DOWNSTREAM CONSUMENTEN:")
    for downstream in lineage_data.get("downstreams", []):
        tabel_info = downstream.get("tableInfo", {})
        if tabel_info:
            print(f"  → {tabel_info.get('catalog_name')}.{tabel_info.get('schema_name')}.{tabel_info.get('name')}")
    
    return lineage_data

# Gebruik
lineage = haal_tabel_lineage_op(
    catalog="finance_prod",
    schema="transactions", 
    table="klanten"
)

# Kolom-level lineage
def haal_kolom_lineage_op(catalog: str, schema: str, table: str, column: str):
    """Toont welke kolommen bijdragen aan een specifieke output-kolom."""
    endpoint = f"{w.config.host}/api/2.0/lineage-tracking/column-lineage"
    headers = {"Authorization": f"Bearer {w.config.token}"}
    
    payload = {
        "table_name": f"{catalog}.{schema}.{table}",
        "column_name": column
    }
    
    response = requests.get(endpoint, headers=headers, params=payload)
    return response.json()

Vergelijking: Lake Formation vs. Unity Catalog

Beide platformen lossen het governance-vraagstuk op, maar vanuit heel andere invalshoeken. Hieronder een gedetailleerde vergelijking op de dimensies die voor Nederlandse data engineering teams het meest relevant zijn.

Dimensie AWS Lake Formation Unity Catalog
Cloud-ondersteuning Alleen AWS AWS, Azure, GCP (multi-cloud)
Naamruimte model Database → Table (Glue Catalog) Catalog → Schema → Table (3 lagen)
Row-level security ✅ Via row filters in Athena ✅ Via row filter functions in SQL
Column masking ✅ Data masking in Athena queries ✅ Column mask functions (flexibeler)
Data lineage Beperkt (via AWS Glue, handmatig) ✅ Automatisch, kolom-niveau
Tag-gebaseerd beleid (ABAC) ✅ LF-Tags (volwassen) ✅ Via table/column properties + tags
Delta Lake integratie Beperkt (via S3 + Glue) ✅ Native (eerste klas ondersteuning)
Streaming data governance Via Kinesis Data Analytics (beperkt) ✅ Delta Live Tables integratie
Data sharing (extern) Via AWS Clean Rooms / S3 policies ✅ Delta Sharing (open protocol)
AVG/GDPR tooling Via Macie + Lake Formation Via System Tables + eigen queries
Query engines Athena, EMR, Redshift Spectrum Databricks SQL, Spark, DBT
Open standaarden Gedeeltelijk (Apache Iceberg support) ✅ Delta Lake, Iceberg, Hudi
Kosten model Per API-call + S3 kosten Inbegrepen in Databricks licentie
Leercurve Matig (IAM complexiteit) Laag (SQL-gebaseerd)

Kies Lake Formation als...

Je volledig AWS-native bent, al zwaar investeert in Athena/Glue/Redshift, en je governance-vereisten Athena-queries centraal staan.

Kies Unity Catalog als...

Je Databricks gebruikt, multi-cloud wilt, behoefte hebt aan automatische data lineage en een uniforme governance-laag over alle lakehouse-workloads.

Gebruik beide als...

Je een hybride architectuur hebt: Lake Formation voor S3/Athena-toegang, Unity Catalog voor Databricks-workloads. Delta Sharing overbrugt de kloof.

Best Practices voor Productie-implementaties

Na honderden gesprekken met Nederlandse data engineering teams en praktijkervaring bij implementaties, zijn dit de meest waardevolle lessen voor productie-waardige data governance.

1. Begin met een Data Classification Framework

Technische governance zonder classificatiebeleid is zinloos. Definieer eerst je klassen:

# data_classificatie.py - Automatische classificatie van nieuwe tabellen

from pyspark.sql import SparkSession
from databricks.sdk import WorkspaceClient
import re

# PII-gevoelige patronen (GDPR-relevant)
PII_PATRONEN = {
    "bsn":          r'\b\d{9}\b',
    "email":        r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
    "telefoon":     r'\b(\+31|0)[1-9]\d{8}\b',
    "iban":         r'\bNL\d{2}[A-Z]{4}\d{10}\b',
    "postcode":     r'\b[1-9]\d{3}\s?[A-Z]{2}\b',
    "geboortedatum": r'\b\d{2}[-/]\d{2}[-/]\d{4}\b'
}

CLASSIFICATIE_NIVEAUS = {
    "public":       0,   # Vrij deelbaar
    "internal":     1,   # Alleen intern
    "confidential": 2,   # Need-to-know
    "restricted":   3    # Strikt vertrouwelijk (PII, financieel)
}

def classificeer_tabel(catalog: str, schema: str, tabel: str, 
                        steekproef_grootte: int = 1000) -> dict:
    """
    Scant een tabel op PII-indicatoren en stelt classificatie voor.
    Integreert met Unity Catalog table properties.
    """
    spark = SparkSession.builder.getOrCreate()
    w = WorkspaceClient()
    
    volledig_naam = f"{catalog}.{schema}.{tabel}"
    df = spark.table(volledig_naam).limit(steekproef_grootte)
    
    gevonden_pii = {}
    max_classificatie = "public"
    
    for kolom in df.columns:
        kolom_data = df.select(kolom).dropna().limit(100)
        waarden = [str(row[0]) for row in kolom_data.collect()]
        sample_tekst = " ".join(waarden)
        
        for pii_type, patroon in PII_PATRONEN.items():
            if re.search(patroon, sample_tekst):
                gevonden_pii[kolom] = pii_type
                max_classificatie = "restricted"
                break
    
    resultaat = {
        "tabel":           volledig_naam,
        "classificatie":   max_classificatie,
        "pii_kolommen":    gevonden_pii,
        "aanbeveling":     genereeer_aanbeveling(gevonden_pii)
    }
    
    # Automatisch tabel-properties bijwerken in Unity Catalog
    if gevonden_pii:
        spark.sql(f"""
            ALTER TABLE {volledig_naam} 
            SET TBLPROPERTIES (
                'sensitivity'    = '{max_classificatie}',
                'pii_present'    = 'true',
                'pii_columns'    = '{",".join(gevonden_pii.keys())}',
                'gdpr_relevant'  = 'true',
                'scan_timestamp' = '{spark.sql("SELECT current_timestamp()").collect()[0][0]}'
            )
        """)
        print(f"✅ Tabel {volledig_naam} geclassificeerd als: {max_classificatie}")
        print(f"⚠️  PII gevonden in kolommen: {list(gevonden_pii.keys())}")
    
    return resultaat

def genereeer_aanbeveling(pii_kolommen: dict) -> list:
    aanbevelingen = []
    for kolom, pii_type in pii_kolommen.items():
        if pii_type == "bsn":
            aanbevelingen.append(f"MASK kolom '{kolom}': BSN is strikt vertrouwelijk")
        elif pii_type == "email":
            aanbevelingen.append(f"MASK kolom '{kolom}': e-mailadres is PII")
        elif pii_type == "iban":
            aanbevelingen.append(f"MASK kolom '{kolom}': IBAN is financieel-gevoelig")
    return aanbevelingen

2. Gebruik System Tables voor Governance Monitoring

-- Unity Catalog System Tables: AVG compliance dashboard
-- Draaien in Databricks SQL

-- Wie heeft de afgelopen 30 dagen PII-tabellen benaderd?
SELECT 
    user_identity.email                     AS gebruiker,
    request_params.full_name_arg            AS benaderde_tabel,
    COUNT(*)                                AS aantal_queries,
    MAX(event_time)                         AS laatste_toegang,
    MIN(event_time)                         AS eerste_toegang
FROM system.access.audit
WHERE 
    event_date >= CURRENT_DATE - INTERVAL 30 DAYS
    AND action_name IN ('commandSubmit', 'runCommand')
    AND request_params.full_name_arg LIKE '%klanten%'  -- PII tabellen
    -- Uitbreiden met je eigen PII-tabel-lijst
GROUP BY 1, 2
ORDER BY aantal_queries DESC;

-- Detecteer ongebruikelijke toegangspatronen (potentieel datalek)
WITH toegang_per_uur AS (
    SELECT 
        user_identity.email     AS gebruiker,
        DATE_TRUNC('hour', event_time) AS uur,
        COUNT(*)                AS queries_per_uur
    FROM system.access.audit
    WHERE event_date >= CURRENT_DATE - INTERVAL 7 DAYS
    GROUP BY 1, 2
),
gemiddelde_per_gebruiker AS (
    SELECT 
        gebruiker,
        AVG(queries_per_uur)    AS gemiddeld,
        STDDEV(queries_per_uur) AS standaarddev
    FROM toegang_per_uur
    GROUP BY 1
)
SELECT 
    t.gebruiker,
    t.uur,
    t.queries_per_uur,
    g.gemiddeld,
    ROUND((t.queries_per_uur - g.gemiddeld) / NULLIF(g.standaarddev, 0), 2) AS z_score
FROM toegang_per_uur t
JOIN gemiddelde_per_gebruiker g USING (gebruiker)
WHERE ABS((t.queries_per_uur - g.gemiddeld) / NULLIF(g.standaarddev, 0)) > 3
ORDER BY z_score DESC;

Veelgemaakte Fouten in Productie

  • Te brede rollen: Vermijd SELECT * grants op catalog-niveau. Begin restrictief en breidt uit op verzoek.
  • Geen governance op streaming data: Delta Live Tables in combinatie met Unity Catalog biedt governance ook voor realtime pipelines — gebruik het.
  • IAM vs. LF-rechten conflict: In Lake Formation moet je expliciet de IAM-rechten beperken en Lake Formation de controle geven. Vergeten IAM-policies overschrijven LF-beleid.
  • Geen data lineage bij externe data: Externe tools (dbt, Fivetran) integreren niet altijd automatisch. Gebruik de Databricks Lineage API of OpenLineage voor completere coverage.
  • Governance als afterthought: Bouw governance in vanaf het begin van je pipeline, niet als laag er bovenop achteraf.

3. Automatiseer Compliance-rapportage

# avg_compliance_rapport.py
# Genereert automatisch een maandelijks AVG-rapport

import boto3
from datetime import datetime, timedelta
import pandas as pd

def genereer_lake