# Lakehouse Architecture Design and Patterns **Last updated:** 2026-02 **Status:** GA **Category:** Data Engineering for AI --- ## Introduksjon Lakehouse-arkitekturen kombinerer de beste egenskapene fra data lakes (fleksibel lagring av strukturerte, semi-strukturerte og ustrukturerte data) med data warehouse-funksjonalitet (ACID-transaksjoner, skjemahåndtering og høy spørringsytelse). Microsoft Fabric standardiserer på Delta Lake-formatet, som gir denne hybridkapabiliteten nativt på tvers av alle Fabric-opplevelser. For AI-løsninger er lakehouse-arkitekturen spesielt verdifull fordi den muliggjør lagring av rådata, mellomtransformasjoner og ferdige feature-sett i ett og samme system -- med full versjonskontroll og tidsreise. Dette eliminerer behovet for separate data lakes og data warehouses, og forenkler dataflytene mellom data engineering og data science-team. Norsk offentlig sektor har strenge krav til datalagring, personvern og sporbarhet. Lakehouse-arkitekturen på Fabric adresserer dette gjennom ACID-garantier, Delta Lake-revisjonssporbarhet (audit trail), og OneLake-basert dataforvaltning som sikrer at data holdes i norsk/europeisk region. --- ## Delta Lake Transaction Semantics ### ACID-egenskaper i Delta Lake Delta Lake gir full ACID-transaksjonsstøtte over Apache Parquet-filer: | Egenskap | Implementasjon | Konsekvens for AI | |---|---|---| | **Atomicity** | Alle endringer i en transaksjon committes komplett eller ikke i det hele tatt | Treningsdata er alltid konsistent | | **Consistency** | Schema enforcement hindrer ugyldig data | Feature-kvalitet opprettholdes | | **Isolation** | Serializable isolation via optimistic concurrency | Samtidige les/skriv-operasjoner er trygge | | **Durability** | Data persistert til OneLake i Parquet-format | Ingen tap ved feil | ### Transaksjonsloggen (_delta_log) ``` lakehouse/Tables/customer_features/ ├── _delta_log/ │ ├── 00000000000000000000.json # Initial create │ ├── 00000000000000000001.json # First insert │ ├── 00000000000000000002.json # Update/merge │ └── 00000000000000000003.json # Delete ├── part-00000-*.parquet ├── part-00001-*.parquet └── part-00002-*.parquet ``` Hver JSON-fil i `_delta_log` inneholder: - **Add file**: Nye Parquet-filer som legges til - **Remove file**: Parquet-filer som logisk fjernes - **Metadata**: Skjemaendringer og tabellegenskaper - **Protocol**: Minimums reader/writer-versjoner ### Concurrent Writes ```python # Delta Lake håndterer samtidige skrivinger via optimistic concurrency # Eksempel: To jobber skriver til samme tabell # Jobb 1: Batch-oppdatering fra Data Factory spark.read.format("delta").table("silver.features") \ .where("region = 'Norway'") \ .write.format("delta").mode("overwrite").option("replaceWhere", "region = 'Norway'") \ .saveAsTable("silver.features") # Jobb 2: Streaming append fra Eventstream stream.writeStream.format("delta").outputMode("append").toTable("silver.features") # Begge operasjonene kan kjøre samtidig uten konflikter # Delta Lake bruker optimistic concurrency control (OCC) ``` --- ## Schema-on-Read versus Schema-on-Write Tradeoffs ### Sammenligning | Aspekt | Schema-on-Write | Schema-on-Read | |---|---|---| | **Skjemadefinisjon** | Ved skriving (enforce) | Ved lesing (infer) | | **Datakvalitet** | Høy -- ugyldig data avvises | Variabel -- feil oppdages sent | | **Fleksibilitet** | Lav -- skjemaendringer krever migrering | Høy -- nye felter aksepteres | | **Ytelse** | Raskere lesing (kjent skjema) | Tregere lesing (skjemainferens) | | **Bruk i Lakehouse** | Silver/Gold-lag | Bronze-lag | ### Schema Enforcement i Delta Lake ```python # Schema enforcement er aktivert som standard # Forsøk på å legge til kolonne som ikke finnes feiler: try: new_data_with_extra_col.write.format("delta").mode("append") \ .saveAsTable("silver.strict_table") except Exception as e: print(f"Schema mismatch: {e}") # For å tillate schema evolution: spark.conf.set("spark.databricks.delta.schema.autoMerge.enabled", "true") # Eller per operasjon: new_data.write.format("delta") \ .mode("append") \ .option("mergeSchema", "true") \ .saveAsTable("silver.flexible_table") ``` ### Anbefalt strategi per lag | Lag | Schema-strategi | Begrunnelse | |---|---|---| | **Bronze** | Schema-on-read + autoMerge | Aksepter alle kildedata, inkl. nye felter | | **Silver** | Schema enforcement med mergeSchema | Kontrollert evolusjon, avvis ugyldig data | | **Gold** | Streng schema enforcement | ML-features må ha forutsigbart format | --- ## Time-Travel and Data Versioning ### Tidsreise i Delta Lake Delta Lake lagrer historikk for alle endringer, noe som muliggjør "tidsreise" -- spørring mot tidligere versjoner av data. ```python # Les en spesifikk versjon df_v0 = spark.read.format("delta").option("versionAsOf", 0).load("Tables/customer_features") df_v5 = spark.read.format("delta").option("versionAsOf", 5).load("Tables/customer_features") # Les data slik de var på et gitt tidspunkt df_yesterday = spark.read.format("delta") \ .option("timestampAsOf", "2026-02-10T00:00:00Z") \ .load("Tables/customer_features") # Vis historikk from delta.tables import DeltaTable dt = DeltaTable.forPath(spark, "Tables/customer_features") history = dt.history() display(history.select("version", "timestamp", "operation", "operationMetrics")) ``` ### Bruksområder for tidsreise i AI | Bruksområde | Teknikk | Eksempel | |---|---|---| | **Reproduserbar trening** | `versionAsOf` | Tren modell på eksakt samme data | | **Feature drift-analyse** | Sammenlign versjoner | Finn endringer i feature-distribusjon | | **Rollback** | `RESTORE TABLE` | Angre feilaktig dataoppdatering | | **Audit trail** | `DESCRIBE HISTORY` | Dokumenter alle dataendringer | | **Point-in-time lookup** | `timestampAsOf` | Feature lookup for historisk inferens | ### Rollback ```sql -- SQL: Gjenopprett tabell til versjon 3 RESTORE TABLE silver.customer_features TO VERSION AS OF 3; -- Eller til et tidspunkt RESTORE TABLE silver.customer_features TO TIMESTAMP AS OF '2026-02-01T00:00:00Z'; ``` ```python # PySpark: Rollback dt = DeltaTable.forPath(spark, "Tables/silver/customer_features") dt.restoreToVersion(3) ``` ### Retensjons- og VACUUM-policy ```python # Fjern gamle filer (frigjør lagring, fjerner tidsreise-mulighet) # Standard: 7 dager dt.vacuum(168) # Timer (7 * 24) # For å sette annen retensjon: spark.conf.set("spark.databricks.delta.retentionDurationCheck.enabled", "false") dt.vacuum(24) # Behold kun siste 24 timer # VIKTIG: Etter VACUUM kan du ikke tidsreise til slettede versjoner ``` --- ## Upsert and Merge Patterns for Slowly-Changing Dimensions ### MERGE (Upsert) Operasjoner Delta Lake MERGE støtter SQL-standard og utvidet syntaks: ```python from delta.tables import DeltaTable # Target: eksisterende dimensjonstabell target = DeltaTable.forPath(spark, "Tables/silver/dim_customer") # Source: nye/endrede rader source = spark.read.format("delta").table("bronze.crm_customers") # MERGE: SCD Type 1 (overskrive) target.alias("t").merge( source.alias("s"), "t.customer_id = s.customer_id" ).whenMatchedUpdate( set={ "customer_name": "s.customer_name", "email": "s.email", "phone": "s.phone", "updated_at": "current_timestamp()" } ).whenNotMatchedInsert( values={ "customer_id": "s.customer_id", "customer_name": "s.customer_name", "email": "s.email", "phone": "s.phone", "created_at": "current_timestamp()", "updated_at": "current_timestamp()" } ).execute() ``` ### SCD Type 2 (historikk-bevaring) ```python from pyspark.sql.functions import current_timestamp, lit, col # SCD Type 2: Bevar full historikk # Steg 1: Identifiser endrede rader changes = source.alias("s").join( target.toDF().alias("t"), (col("s.customer_id") == col("t.customer_id")) & (col("t.is_current") == True), "inner" ).where( (col("s.customer_name") != col("t.customer_name")) | (col("s.email") != col("t.email")) ).select("s.*") # Steg 2: Lukk eksisterende rader target.alias("t").merge( changes.alias("s"), "t.customer_id = s.customer_id AND t.is_current = true" ).whenMatchedUpdate( set={ "is_current": lit(False), "end_date": current_timestamp() } ).execute() # Steg 3: Sett inn nye versjoner new_rows = changes.withColumn("is_current", lit(True)) \ .withColumn("start_date", current_timestamp()) \ .withColumn("end_date", lit(None).cast("timestamp")) new_rows.write.format("delta").mode("append").saveAsTable("silver.dim_customer") ``` ### SQL MERGE-syntaks ```sql -- SQL-ekvivalent for upsert MERGE INTO silver.dim_customer AS target USING bronze.crm_customers AS source ON target.customer_id = source.customer_id WHEN MATCHED THEN UPDATE SET target.customer_name = source.customer_name, target.email = source.email, target.updated_at = current_timestamp() WHEN NOT MATCHED THEN INSERT (customer_id, customer_name, email, created_at, updated_at) VALUES (source.customer_id, source.customer_name, source.email, current_timestamp(), current_timestamp()) WHEN NOT MATCHED BY SOURCE AND target.is_active = true THEN UPDATE SET target.is_active = false; ``` --- ## Lakehouse Performance Tuning ### V-Order Optimalisering Fabric bruker V-Order som standard ved skriving til Delta-tabeller. V-Order er en skrive-tids-optimalisering av Parquet-filer som gir raskere lesing: | Optimalisering | Effekt | Automatisk i Fabric | |---|---|---| | **V-Order** | Raskere les for alle Fabric-engines | Ja | | **Bin Compaction** | Slår sammen små filer | Manuell eller planlagt | | **Z-Order** | Organiserer data for raskere filtrering | Manuell | | **Deletion Vectors** | Raskere DELETE/UPDATE uten rewrite | Ja (Runtime 1.2+) | | **Liquid Clustering** | Automatisk dataorganisering (preview) | Manuell aktivering | ### Table Maintenance ```sql -- Optimaliser tabell (bin compaction + V-Order) OPTIMIZE silver.customer_features; -- Z-Order for ofte filtrerte kolonner OPTIMIZE silver.customer_features ZORDER BY (region, customer_segment); -- Fjern gamle filer VACUUM silver.customer_features RETAIN 168 HOURS; -- Analyser tabell for statistikk ANALYZE TABLE silver.customer_features COMPUTE STATISTICS; ``` ### Partisjoneringsstrategier | Strategi | Anbefalt for | Eksempel | |---|---|---| | **Dato-partisjonering** | Tidsseriedata, inkrementelle laster | `PARTITIONED BY (date)` | | **Region-partisjonering** | Geografisk filtrering, RLS | `PARTITIONED BY (region)` | | **Z-Order** | Multi-kolonne filtrering | `ZORDER BY (customer_id, date)` | | **Liquid Clustering** | Dynamisk endring av klyngenøkler | `CLUSTER BY (customer_id)` | | **Ingen partisjonering** | Tabeller < 1 GB | Standard | ### Performance Best Practices ```python # 1. Bruk predicate pushdown # Dårlig: Les alt, filtrer etterpå df = spark.read.format("delta").table("silver.events").filter("date = '2026-02-10'") # Bedre: Partition pruning (hvis partisjonert på date) # Delta Lake hopper automatisk over irrelevante partisjoner # 2. Bruk column pruning # Dårlig: Velg alle kolonner df = spark.read.format("delta").table("gold.features") # Bedre: Velg kun nødvendige kolonner df = spark.read.format("delta").table("gold.features").select("feature_1", "feature_2", "target") # 3. Cache for gjentatt bruk df_cached = spark.read.format("delta").table("gold.ml_features").cache() df_cached.count() # Trigger caching # 4. Optimalisert skriving spark.conf.set("spark.microsoft.delta.optimizeWrite.enabled", "true") spark.conf.set("spark.microsoft.delta.optimizeWrite.binSize", "128") # MB per fil ``` --- ## Medallion Architecture Deployment ### Pattern 1: Alle lag som Lakehouses ``` Workspace: Bronze-LH Workspace: Silver-LH Workspace: Gold-LH ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐ │ Raw data │ │ Validated data │ │ Business-ready │ │ - Original format │──>│ - Deduplicated │──>│ - Aggregated │ │ - Minimal transform│ │ - Typed columns │ │ - Feature tables │ │ - Append-only │ │ - Referential int. │ │ - Semantic models │ └────────────────────┘ └────────────────────┘ └────────────────────┘ │ │ │ SQL Endpoint SQL Endpoint SQL Endpoint (read-only) (read-only) (read-only) ``` ### Pattern 2: Bronze+Silver Lakehouse, Gold Data Warehouse ``` Lakehouse (Bronze + Silver) Data Warehouse (Gold) ┌────────────────────────────┐ ┌────────────────────────┐ │ bronze.raw_events │ │ gold.fact_predictions │ │ bronze.raw_transactions │ │ gold.dim_customer │ │ silver.validated_events │─────>│ gold.dim_product │ │ silver.customer_360 │ │ gold.agg_daily_metrics │ │ silver.feature_base │ │ Stored Procedures │ └────────────────────────────┘ │ Views, Functions │ └────────────────────────┘ ``` --- ## Referanser - [What is a Lakehouse in Microsoft Fabric?](https://learn.microsoft.com/en-us/fabric/data-engineering/lakehouse-overview) -- Oversikt over Fabric Lakehouse - [Understand medallion lakehouse architecture for Microsoft Fabric](https://learn.microsoft.com/en-us/fabric/onelake/onelake-medallion-lakehouse-architecture) -- Medallion-arkitektur - [Delta Lake table format interoperability](https://learn.microsoft.com/en-us/fabric/fundamentals/delta-lake-interoperability) -- Delta Lake-kompatibilitet på tvers av Fabric - [Better together: the Lakehouse and Warehouse](https://learn.microsoft.com/en-us/fabric/data-warehouse/get-started-lakehouse-sql-analytics-endpoint) -- Kombinasjon av Lakehouse og Warehouse - [Greenfield lakehouse on Microsoft Fabric](https://learn.microsoft.com/en-us/azure/architecture/example-scenario/data/greenfield-lakehouse-fabric) -- Referansearkitektur - [Upsert into a Delta Lake table using merge](https://learn.microsoft.com/en-us/azure/databricks/delta/merge) -- MERGE-syntaks og mønstre - [Delta Lake table optimization and V-Order](https://learn.microsoft.com/en-us/fabric/data-engineering/delta-optimization-and-v-order) -- Ytelsesoptimalisering - [Lakehouse end-to-end scenario](https://learn.microsoft.com/en-us/fabric/data-engineering/tutorial-lakehouse-introduction) -- Komplett tutorial --- ## For Cosmo - **Bruk denne referansen** når brukeren planlegger en ny dataarkitektur på Fabric, vurderer lakehouse vs warehouse, eller trenger mønstre for SCD, MERGE og Delta Lake-optimalisering for AI-treningsdata. - Anbefal **medallion architecture** (bronze/silver/gold) som standard for alle AI-prosjekter. Bronze for rådata, Silver for validert/denormalisert data, Gold for ML-features og aggregater. - For norsk offentlig sektor: Fremhev **tidsreise** som et viktig verktøy for revisjonsporbarhet og etterlevelse av Utredningsinstruksen -- alle dataendringer er sporbare og reverserbare. - **V-Order** er aktivert som standard i Fabric og gir 2-3x raskere lesing for Power BI Direct Lake. Ikke deaktiver dette med mindre det er spesifikke grunner. - Bruk **separate workspaces** per lag (bronze, silver, gold) for bedre tilgangskontroll og governance, spesielt i organisasjoner med flere team som deler data.