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>
16 KiB
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
- ELT Advantages: Cost, Scalability, Schema-Flexibility
- ETL Data Minimization for Regulated Environments
- Hybrid ETL/ELT Patterns
- Data Staging and Incremental Processing
- Compute Cost Allocation: ETL vs ELT
- Referanser
- For arkitekten
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
# 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-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
# 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
{
"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
# 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
# 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
# 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? -- Oversikt med ETL/ELT-sammenligning
- Extract, transform, and load (ETL) -- Azure Architecture Center ETL/ELT guide
- Dimensional modeling: Load tables -- ETL for dimensjonsmodellering
- Data Factory end-to-end scenario -- Komplett tutorial
- Differences between Azure Data Factory and Fabric Data Factory -- Migrasjon fra ADF
- Data Factory pricing in Microsoft Fabric -- Prismodell og CU-metere
- Migration planning: ADF to Fabric Data Factory -- Migrasjonsguide
For arkitekten
- 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.