Steg 9 (R4): unified migrate-corpus.mjs --write over engineering/governance/ infrastructure/security. 327 filer mutert, verified=null, prosa byte-identisk (fra første ## seksjon), advisor urørt (0 endringer). To applier-fixes oppdaget under kjøring (TDD, RED→GREEN): - insertHeaderFields: anker faller nå tilbake når en meta-linje selv passerer 500B (2 filer pakket et avsnitt i **Status:** → Type/Source landet utenfor scan-vinduet, applierens post-write-assertion fanget + restaurerte). - normalizeStaleVerified: fjerner nå ALLE stale non-date **Verified:** i 500B-vinduet, inkl. stray body-dup rett under --- (9 mlops-genaiops-filer var ellers falskt "verified"/fresh, droppet fra worklist). Operatør-godkjent utvidelse av carve-out; kun stray metadata-linjer, aldri prosa. test-transform-criterion: precondition oppdatert til post-migrasjons-sannhet (fila bærer nå Source). Suite 728/728 grønn.
420 lines
16 KiB
Markdown
420 lines
16 KiB
Markdown
# ETL vs ELT Strategies for AI Workloads
|
|
|
|
**Last updated:** 2026-06-24
|
|
**Status:** GA
|
|
**Category:** Data Engineering for AI
|
|
**Type:** reference
|
|
**Source:** https://learn.microsoft.com/fabric/data-factory/data-factory-overview
|
|
|
|
---
|
|
|
|
## Innhold
|
|
|
|
- [Introduksjon](#introduksjon)
|
|
- [ELT Advantages: Cost, Scalability, Schema-Flexibility](#elt-advantages-cost-scalability-schema-flexibility)
|
|
- [ETL Data Minimization for Regulated Environments](#etl-data-minimization-for-regulated-environments)
|
|
- [Hybrid ETL/ELT Patterns](#hybrid-etlelt-patterns)
|
|
- [Data Staging and Incremental Processing](#data-staging-and-incremental-processing)
|
|
- [Compute Cost Allocation: ETL vs ELT](#compute-cost-allocation-etl-vs-elt)
|
|
- [Referanser](#referanser)
|
|
- [For Cosmo](#for-cosmo)
|
|
|
|
## Introduksjon
|
|
|
|
Valget mellom ETL (Extract, Transform, Load) og ELT (Extract, Load, Transform) er en av de mest grunnleggende arkitekturbeslutningene for dataintegrasjon i AI-prosjekter. Tradisjonell ETL transformerer data før lasting i et dedikert transformasjonsengine, mens moderne ELT laster rådata først og utnytter målsystemets beregningskraft for transformasjon. Microsoft Fabric støtter begge tilnærminger og hybride mønstre.
|
|
|
|
For norsk offentlig sektor er dette valget påvirket av flere faktorer: regulatoriske krav til dataminimering (GDPR artikkel 5), behov for sporbarhet, budsjettbegrensninger, og kompetanse i organisasjonen. ELT-tilnærmingen har blitt dominerende for AI-arbeidsbelastninger fordi den bevarer rådata for utforskende analyse og gir fleksibilitet til å endre transformasjonslogikk uten re-innhenting fra kildesystemer.
|
|
|
|
Denne referansen sammenligner ETL og ELT for AI-brukstilfeller, med fokus på Fabric-spesifikke implementasjoner, hybridmønstre, inkrementell prosessering, og kostnadsoptimalisering.
|
|
|
|
---
|
|
|
|
## ELT Advantages: Cost, Scalability, Schema-Flexibility
|
|
|
|
### Hvorfor ELT dominerer for AI
|
|
|
|
| Fordel | Beskrivelse | AI-relevans |
|
|
|---|---|---|
|
|
| **Bevarer rådata** | Original data tilgjengelig for nye analyser | Nye features uten re-innhenting |
|
|
| **Skalerer med beregning** | Fabric Spark/SQL skalerer elastisk | Store treningsdatasett |
|
|
| **Schema-fleksibilitet** | Schema-on-read i Bronze | Nye datakilder uten re-design |
|
|
| **Forenkling** | Ingen separat transformasjonsserver | Lavere TCO |
|
|
| **Parallellisering** | Transformasjoner på distribuert Spark | Raskere feature engineering |
|
|
|
|
### ELT i Fabric
|
|
|
|
```
|
|
Kilde ──> Extract ──> Load (OneLake) ──> Transform (Spark/SQL)
|
|
│
|
|
┌─────▼──────┐
|
|
│ Lakehouse │
|
|
│ (Bronze) │
|
|
│ Rådata i │
|
|
│ Delta Lake │
|
|
└─────┬──────┘
|
|
│ Spark Notebook / SQL
|
|
┌─────▼──────┐
|
|
│ Lakehouse │
|
|
│ (Silver) │
|
|
│ Validert │
|
|
└─────┬──────┘
|
|
│ Spark Notebook / SQL
|
|
┌─────▼──────┐
|
|
│ Lakehouse │
|
|
│ (Gold) │
|
|
│ ML-features│
|
|
└────────────┘
|
|
```
|
|
|
|
### Fabric ELT Implementation
|
|
|
|
```python
|
|
# Steg 1: Extract + Load (Copy Job / Pipeline)
|
|
# Data Factory laster rådata direkte til Bronze Lakehouse
|
|
|
|
# Steg 2: Transform i Lakehouse med Spark
|
|
# Bronze → Silver transformasjon
|
|
bronze_data = spark.read.format("delta").table("bronze.raw_transactions")
|
|
|
|
silver_data = (
|
|
bronze_data
|
|
# Fjern duplikater
|
|
.dropDuplicates(["transaction_id"])
|
|
# Typevalidering
|
|
.withColumn("amount", col("amount").cast("double"))
|
|
.filter(col("amount") > 0)
|
|
# Standardiser datoformat
|
|
.withColumn("transaction_date",
|
|
F.to_timestamp("transaction_date", "yyyy-MM-dd'T'HH:mm:ss"))
|
|
# Fjern null i obligatoriske felt
|
|
.filter(col("customer_id").isNotNull())
|
|
)
|
|
|
|
silver_data.write.format("delta").mode("overwrite") \
|
|
.saveAsTable("silver.validated_transactions")
|
|
|
|
# Steg 3: Silver → Gold (ML-features)
|
|
gold_features = (
|
|
spark.read.format("delta").table("silver.validated_transactions")
|
|
.groupBy("customer_id")
|
|
.agg(
|
|
F.count("*").alias("total_transactions"),
|
|
F.sum("amount").alias("total_amount"),
|
|
F.avg("amount").alias("avg_amount"),
|
|
F.stddev("amount").alias("std_amount"),
|
|
F.max("transaction_date").alias("last_transaction_date")
|
|
)
|
|
)
|
|
|
|
gold_features.write.format("delta").mode("overwrite") \
|
|
.saveAsTable("gold.customer_features")
|
|
```
|
|
|
|
---
|
|
|
|
## ETL Data Minimization for Regulated Environments
|
|
|
|
### Når ETL er riktig valg
|
|
|
|
ETL er foretrukket i regulerte miljøer der dataminimering er påkrevd:
|
|
|
|
| Scenario | Begrunnelse | Regulering |
|
|
|---|---|---|
|
|
| **PII-filtrering** | Fjern personnummer før lasting | GDPR Art. 5(1)(c) |
|
|
| **Dataminimering** | Last kun nødvendige felter | GDPR Art. 5(1)(c) |
|
|
| **Kryptering** | Krypter sensitive felt i transit | Sikkerhetskrav |
|
|
| **Konsolidering** | Slå sammen kilder før lasting | Kostnadsbegrensning |
|
|
| **Format-konvertering** | Konverter proprietære formater | Interoperabilitet |
|
|
|
|
### ETL i Fabric med Dataflow Gen2
|
|
|
|
```
|
|
Kilde ──> Dataflow Gen2 (Transform) ──> Lakehouse (Silver/Gold)
|
|
│
|
|
├── Fjern PII-kolonner
|
|
├── Masker fødselsnummer
|
|
├── Aggreger til anonymt nivå
|
|
├── Validerer datatyper
|
|
└── Berik med referansedata
|
|
```
|
|
|
|
### Dataflow Gen2 for ETL
|
|
|
|
Dataflow Gen2 bruker Power Query Online med over 300 transformasjoner:
|
|
|
|
```m
|
|
// M-kode (Power Query) for ETL med dataminimering
|
|
let
|
|
Source = Sql.Database("server.database.windows.net", "hrdb"),
|
|
employees = Source{[Schema="dbo", Item="Employees"]}[Data],
|
|
|
|
// Fjern sensitive kolonner (dataminimering)
|
|
removedPII = Table.RemoveColumns(employees,
|
|
{"SocialSecurityNumber", "BankAccount", "HomeAddress"}),
|
|
|
|
// Masker e-post
|
|
maskedEmail = Table.TransformColumns(removedPII,
|
|
{{"Email", each Text.BeforeDelimiter(_, "@") & "@***.no"}}),
|
|
|
|
// Aggreger alder til aldersgrupper
|
|
addAgeGroup = Table.AddColumn(maskedEmail, "AgeGroup",
|
|
each if [Age] < 30 then "Under 30"
|
|
else if [Age] < 50 then "30-49"
|
|
else "50+"),
|
|
|
|
// Fjern eksakt alder (kun aldersgruppe beholdes)
|
|
removedAge = Table.RemoveColumns(addAgeGroup, {"Age"}),
|
|
|
|
// Filtrer kun aktive ansatte
|
|
filtered = Table.SelectRows(removedAge, each [Status] = "Active")
|
|
in
|
|
filtered
|
|
```
|
|
|
|
---
|
|
|
|
## Hybrid ETL/ELT Patterns
|
|
|
|
### Pattern: Pre-filter ETL + In-place ELT
|
|
|
|
```
|
|
ETL (Dataflow Gen2) ELT (Spark)
|
|
┌──────────────────┐ ┌──────────────────┐
|
|
Kildesystem ───────>│ Fjern PII │──> Bronze ─│ Feature engineer │──> Silver
|
|
│ Masker sensitive │ │ Aggreger │
|
|
│ Validerer format │ │ Join │
|
|
└──────────────────┘ │ Dedupliser │
|
|
└──────────────────┘
|
|
```
|
|
|
|
### Pattern: Metadata-driven Hybrid Pipeline
|
|
|
|
```python
|
|
# Metadata-drevet pipeline som velger ETL eller ELT per datakilde
|
|
pipeline_config = {
|
|
"sources": [
|
|
{
|
|
"name": "crm_customers",
|
|
"strategy": "ETL", # Inneholder PII
|
|
"reason": "PII-filtrering påkrevd",
|
|
"transformations": ["remove_ssn", "mask_email", "age_to_group"],
|
|
"tool": "dataflow_gen2"
|
|
},
|
|
{
|
|
"name": "traffic_events",
|
|
"strategy": "ELT", # Ingen sensitive data
|
|
"reason": "Stort volum, ingen PII",
|
|
"transformations": ["aggregate_5min", "add_road_segment"],
|
|
"tool": "spark_notebook"
|
|
},
|
|
{
|
|
"name": "health_records",
|
|
"strategy": "ETL", # Helseopplysninger (særkategori)
|
|
"reason": "GDPR Art. 9 - særlig kategori",
|
|
"transformations": [
|
|
"pseudonymize_patient_id",
|
|
"remove_diagnosis_text",
|
|
"aggregate_to_cohort"
|
|
],
|
|
"tool": "dataflow_gen2"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Fabric Pipeline Orchestration
|
|
|
|
```json
|
|
{
|
|
"name": "HybridETL_ELT_Pipeline",
|
|
"properties": {
|
|
"activities": [
|
|
{
|
|
"name": "ETL_SensitiveData",
|
|
"type": "DataflowGen2",
|
|
"description": "ETL for PII-data via Dataflow Gen2",
|
|
"typeProperties": {
|
|
"dataflowName": "pii_cleansing_flow"
|
|
}
|
|
},
|
|
{
|
|
"name": "ELT_BulkLoad",
|
|
"type": "Copy",
|
|
"description": "ELT: Last rådata direkte til Bronze",
|
|
"dependsOn": [],
|
|
"typeProperties": {
|
|
"source": { "type": "BlobSource" },
|
|
"sink": { "type": "LakehouseSink" }
|
|
}
|
|
},
|
|
{
|
|
"name": "Transform_Silver",
|
|
"type": "Notebook",
|
|
"description": "ELT: Transform i Spark",
|
|
"dependsOn": [
|
|
{ "activity": "ETL_SensitiveData" },
|
|
{ "activity": "ELT_BulkLoad" }
|
|
],
|
|
"typeProperties": {
|
|
"notebookPath": "silver_transformations"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Data Staging and Incremental Processing
|
|
|
|
### Inkrementelle lastingsmønstre
|
|
|
|
| Mønster | Beskrivelse | Bruk i Fabric |
|
|
|---|---|---|
|
|
| **Full load** | Last alt hver gang | Copy Job (full load) |
|
|
| **Incremental append** | Last kun nye rader | Copy Job (append) + watermark |
|
|
| **CDC (Change Data Capture)** | Strøm av endringer | Copy Job (CDC), Mirroring |
|
|
| **Watermark** | Last rader etter siste timestamp | Pipeline med parameter |
|
|
| **Delta load** | Merge nye/endrede rader | Copy Job (upsert) |
|
|
|
|
### Watermark-basert inkrementell lasting
|
|
|
|
```python
|
|
# Hent siste watermark (høyeste lastede timestamp)
|
|
last_watermark = spark.sql("""
|
|
SELECT MAX(loaded_timestamp) as watermark
|
|
FROM bronze.raw_events
|
|
""").collect()[0]["watermark"]
|
|
|
|
# Last kun nye data
|
|
new_data = (
|
|
spark.read
|
|
.format("jdbc")
|
|
.option("url", jdbc_url)
|
|
.option("dbtable", f"""
|
|
(SELECT * FROM events
|
|
WHERE modified_date > '{last_watermark}') AS new_events
|
|
""")
|
|
.load()
|
|
)
|
|
|
|
# Append til Bronze
|
|
new_data.withColumn("loaded_timestamp", F.current_timestamp()) \
|
|
.write.format("delta").mode("append") \
|
|
.saveAsTable("bronze.raw_events")
|
|
|
|
print(f"Lastet {new_data.count()} nye rader etter {last_watermark}")
|
|
```
|
|
|
|
### Staging Layer Pattern
|
|
|
|
```
|
|
Kildesystem ──> Staging Area ──> Bronze ──> Silver ──> Gold
|
|
│
|
|
├── Midlertidig lagring
|
|
├── Validering før insert
|
|
├── Duplikatsjekk
|
|
└── Slettet etter vellykket last
|
|
```
|
|
|
|
```python
|
|
# Staging → Bronze med validering
|
|
staging_data = spark.read.format("delta").table("staging.incoming_data")
|
|
|
|
# Valider
|
|
valid_records = staging_data.filter(
|
|
col("customer_id").isNotNull() &
|
|
col("amount").isNotNull() &
|
|
(col("amount") > 0)
|
|
)
|
|
|
|
rejected_records = staging_data.subtract(valid_records)
|
|
|
|
# Last gyldige poster til Bronze
|
|
valid_records.write.format("delta").mode("append").saveAsTable("bronze.validated_events")
|
|
|
|
# Logg avviste poster
|
|
rejected_records.write.format("delta").mode("append").saveAsTable("governance.rejected_records")
|
|
|
|
# Rydd opp staging
|
|
spark.sql("TRUNCATE TABLE staging.incoming_data")
|
|
```
|
|
|
|
---
|
|
|
|
## Compute Cost Allocation: ETL vs ELT
|
|
|
|
### Kostnadsmodell i Fabric
|
|
|
|
| Komponent | Fabric CU-meter | Kostnadsdriver |
|
|
|---|---|---|
|
|
| **Copy Job / Activity** | Data Movement | Datamengde (GB) |
|
|
| **Dataflow Gen2** | Standard Compute / High Scale Compute | Kompleksitet, rader |
|
|
| **Spark Notebook** | Spark Compute | vCores x tid |
|
|
| **Pipeline Orchestration** | Data Orchestration | Antall aktiviteter |
|
|
| **OneLake Storage** | OneLake Storage | Lagret data (GB/mnd) |
|
|
|
|
### ETL vs ELT kostnadsprofil
|
|
|
|
| Aspekt | ETL (Dataflow Gen2) | ELT (Spark) |
|
|
|---|---|---|
|
|
| **Oppsettskostnad** | Lav (no-code) | Medium (kode) |
|
|
| **Kjøretidskostnad per rad** | Høyere (transformasjon + last) | Lavere (kun last, transform i batch) |
|
|
| **Skalerbarhet** | Begrenset (single-node-lik) | Høy (distribuert Spark) |
|
|
| **Lagringskostnad** | Lavere (kun transformert data) | Høyere (rådata + transformert) |
|
|
| **Vedlikeholdskostnad** | Lav (visuell editor) | Medium (kodevedlikehold) |
|
|
| **Optimal for** | < 10 GB, enkel transformasjon | > 10 GB, kompleks transformasjon |
|
|
|
|
### Kostnadsoptimalisering
|
|
|
|
```python
|
|
# 1. Bruk Copy Job i stedet for Copy Activity for bulk-lasting
|
|
# Copy Job: Automatisk CDC, inkrementell lasting, lavere kostnad
|
|
|
|
# 2. Bruk Optimized Write for å redusere små filer
|
|
spark.conf.set("spark.microsoft.delta.optimizeWrite.enabled", "true")
|
|
|
|
# 3. Bruk Spark autoscale for å matche beregning med behov
|
|
# Konfigureres i Workspace Settings > Spark Compute
|
|
|
|
# 4. Planlegg tunge transformasjoner utenfor peak-timer
|
|
# Pipeline schedule: "0 2 * * *" (kl 02:00)
|
|
|
|
# 5. Bruk materialized lake views for repetitive transformasjoner
|
|
# Unngår gjentatt beregning av samme transformasjoner
|
|
```
|
|
|
|
### Beslutningsmatrise
|
|
|
|
| Faktor | Velg ETL | Velg ELT | Velg Hybrid |
|
|
|---|---|---|---|
|
|
| **Datamengde** | < 10 GB | > 10 GB | Variabel |
|
|
| **PII i kildedata** | Ja (GDPR) | Nei | Noen kilder med PII |
|
|
| **Schema-stabilitet** | Stabilt | Variabel | Blandet |
|
|
| **Team-kompetanse** | Power Query/Excel | PySpark/SQL | Blandet |
|
|
| **Transformasjonskompleksitet** | Enkel (filter, rename) | Kompleks (vindus, ML) | Blandet |
|
|
| **Latenskrav** | Minutter | Sekunder-minutter | Variabel |
|
|
| **Budget** | Begrenset | Fleksibelt | Variabel |
|
|
|
|
---
|
|
|
|
## Referanser
|
|
|
|
- [What is Data Factory in Microsoft Fabric?](https://learn.microsoft.com/en-us/fabric/data-factory/data-factory-overview) -- Oversikt med ETL/ELT-sammenligning
|
|
- [Extract, transform, and load (ETL)](https://learn.microsoft.com/en-us/azure/architecture/data-guide/relational-data/etl) -- Azure Architecture Center ETL/ELT guide
|
|
- [Dimensional modeling: Load tables](https://learn.microsoft.com/en-us/fabric/data-warehouse/dimensional-modeling-load-tables) -- ETL for dimensjonsmodellering
|
|
- [Data Factory end-to-end scenario](https://learn.microsoft.com/en-us/fabric/data-factory/tutorial-end-to-end-introduction) -- Komplett tutorial
|
|
- [Differences between Azure Data Factory and Fabric Data Factory](https://learn.microsoft.com/en-us/fabric/data-factory/compare-fabric-data-factory-and-azure-data-factory) -- Migrasjon fra ADF
|
|
- [Data Factory pricing in Microsoft Fabric](https://learn.microsoft.com/en-us/fabric/data-factory/pricing-overview) -- Prismodell og CU-metere
|
|
- [Migration planning: ADF to Fabric Data Factory](https://learn.microsoft.com/en-us/fabric/data-factory/migrate-planning-azure-data-factory) -- Migrasjonsguide
|
|
|
|
---
|
|
|
|
## For Cosmo
|
|
|
|
- **Bruk denne referansen** når brukeren evaluerer dataintegrerings-strategier for AI, vurderer kostnader og ytelse, eller trenger å håndtere sensitive data i pipelines.
|
|
- For norsk offentlig sektor: Anbefal **hybrid ETL/ELT** som standard -- ETL for kilder med personopplysninger (Dataflow Gen2 med PII-filtrering), ELT for alle andre kilder (Copy Job + Spark).
|
|
- **ELT er generelt best for AI** fordi det bevarer rådata i Bronze, noe som muliggjør eksperimentering med nye features uten å måtte re-hente data fra kildesystemer.
|
|
- **Dataflow Gen2** er undervurdert for ETL i offentlig sektor -- det er et Power Query-basert verktøy som mange forvaltere allerede kjenner fra Excel/Power BI, og det håndterer dataminimering visuelt.
|
|
- Ved kostnadsestimering: Husk at ELT har **høyere lagringskostnad** (bevarer rådata) men **lavere beregningskostnad** for gjentatte transformasjoner. For AI-prosjekter veier fleksibilitetsgevinsten vanligvis opp for ekstra lagring.
|