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