ms-ai-architect/skills/ms-ai-engineering/references/data-engineering/lakehouse-architecture-design.md
Kjell Tore Guttormsen 3a73eeafdc refactor(ms-ai-architect): R13 del 1 — nøytraliser Cosmo-personaen i ref-korpusets headinger, etter å ha rettet en gate som var målt usann to ganger
Ordre 20260912T193441Z-7358817909. Steg 1 var ikke transformen, men å rette
roadmapens R13-gate og få den ratifisert. Gaten `grep -rl "Cosmo"
skills/*/references -> 0` var usann på to uavhengige måter:

1. Ordren fanget den første: 451 av forekomstene er Azure Cosmos DB, ekte
   produktinnhold. Diskriminatoren er ikke bokstaven «s» — `Cosmos <norsk
   substantiv>` er genitiv av personaen (`### Cosmos tonalitet`), mens
   `Cosmos DB`/`CosmosClient`/`cosmos_ru` er produkt.
2. Denne økten fant den andre: 132 persona-forekomster ligger i prosa,
   tabeller, dialog-replikker og proveniens-linjer. Heading-nøytralisering
   kan ikke nå dem, så «0 persona» er uoppnåelig også under den ratifiserte
   formen. Operatøren ratifiserte alternativ A: gaten speiler formen, og de
   132 bokføres til R13b/R14.

Tre korreksjoner av premisser som sto i ordren og STATE:
  «ca 320 produkt»   -> 451 (case-sensitivt nett manglet 327 lowercase
                        TOC-ankre + 99 identifikatorer; sann nevner 1 638)
  «169 headinger»    -> 401. 169 var `^## For Cosmo`-prefikset (168) og var
                        internt inkonsistent med sin egen topp-variant (204)
  «417 matcher ingen
   populasjon»       -> 417 er cosmo-headinger utenfor kodefences; briefens
                        nevner var reell hele tiden

Fence-bevissthet er målt skadelig, ikke nødvendig: begge toggle-regler er
gale på dette korpuset (naiv toggle skjuler en ekte heading i
chain-of-thought-prompting.md, CommonMark-regelen ubalanserer
service-level-documentation-dr.md). Fence-agnostisk deteksjon finner 401
heading-linjer i nøyaktig de samme 40 variantene som fence-bevisst finner
400 i — ingen kodeblokk-linje er byte-identisk til en persona-heading. Derfor
nøkles transformen på 40 enumererte heading-tekster og ignorerer fences. En
ukjent variant kaster; en slug-kollisjon kaster. Ingenting auto-fikses.

TOC-en regenereres ikke, den rettes kirurgisk: alle 327 persona-lenker hadde
lenketekst lik én av de 40 heading-tekstene og anker lik slugify av den
(327/327, 0 avvik), så heading og TOC-entry skrives i samme operasjon og
ingen mellomtilstand etterlater en død lenke.

Ratifisert målform: `For Cosmo`, `For Cosmo Skyberg` og `For arkitekten
(Cosmo)` konvergerer på `For arkitekten`. To filer kolliderte og er adjudisert
ved å lese dem, ikke ved regel.

Verifisering (alle 7 kriterier fra ordren):
  G1 persona på heading-linjer   401 -> 0
  G2 døde fragmentlenker         1 -> 1 (pre-eksisterende, unntatt)
  G3 produkt-forekomster         451 -> 451; `Cosmos DB|Azure Cosmos` 308 = 308
  de 3 kun-produkt-filene        byte-identiske
  nettet validert begge veier    injisert persona feller G1; genitiv feller G1;
                                 produkt-heading og de 3 filene passerer
  hele diffen                    802 heading-linjer + 654 TOC-linjer, ANNET = 0
  linjeantall                    728 lagt til = 728 slettet
  suite                          1120/1120 (1097 + 23 nye)
  validate-plugin                250 PASS / 0 FAIL
  stikkprøve                     10 filer, alle 5 skills, inkl. de 3 mest
                                 produkt-tunge (26/20/19) — kun heading+TOC

Utenfor scope, urørt: de 4 SKILL.md, de 23 commands, CLAUDE.md, README.md,
NOTICE.md, docs/ (alt R14).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 22:12:28 +02:00

16 KiB

Lakehouse Architecture Design and Patterns

Last updated: 2026-06-24 Status: GA Category: Data Engineering for AI Type: reference Source: https://learn.microsoft.com/fabric/data-engineering/lakehouse-overview


Innhold

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

# 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

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

# 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: 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';
# PySpark: Rollback
dt = DeltaTable.forPath(spark, "Tables/silver/customer_features")
dt.restoreToVersion(3)

Retensjons- og VACUUM-policy

# 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:

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)

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-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 (GA; erstatter partisjonering/Z-Order) Manuell aktivering (CLUSTER BY)

Table Maintenance

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

# 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


For arkitekten

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