Data Warehousing Fundamenten

Wat een data warehouse daadwerkelijk anders maakt dan een productiedatabase: OLTP versus OLAP, columnar storage, MPP, de klassieke staging/warehouse/marts-architectuur, en waar dat overgaat in medallion architecture.

12 min leestijd Beginner Bijgewerkt: 2026-08-09 Fundamentals

Introductie

In Introduction to Data Engineering zagen we waarom een productiedatabase niet geschikt is om rechtstreeks op te rapporteren — een zware aggregatiequery vertraagt de applicatie die hij bedient. Dit hoofdstuk gaat een niveau dieper: wat maakt een data warehouse dan wél geschikt voor die queries? Het antwoord zit niet in "meer geheugen" of "een snellere server" — het zit in fundamenteel andere ontwerpkeuzes op het niveau van hoe data fysiek wordt opgeslagen en hoe queries worden uitgevoerd.

Dit is geen historisch overzicht om te doorstaan voordat je bij de "echte" tools mag; het is de basis waarop Snowflake, Databricks SQL en elk ander modern platform is gebouwd. Zonder dit fundament blijft "warehouse-performance" een black box die je alleen via trial-and-error kunt beïnvloeden.

OLTP versus OLAP: het Fundamentele Onderscheid

Elke discussie over data warehousing begint bij dit onderscheid, omdat het de reden ís dat warehouses bestaan.

OLTP (Online Transaction Processing) is wat een productiedatabase doet: veel korte, gelijktijdige transacties, elk gericht op een klein aantal rijen. Denk aan "voeg deze order toe", "werk de voorraad van dit product bij", "haal de gegevens van deze ene klant op". De database is geoptimaliseerd voor lees-/schrijfsnelheid op individuele rijen, met sterke consistentiegaranties (ACID) omdat een halve transactie — bijvoorbeeld geld afschrijven zonder het bij de ontvanger bij te schrijven — onacceptabel is.

OLAP (Online Analytical Processing) is wat een warehouse doet: weinig, maar zware queries die grote hoeveelheden rijen doorzoeken en aggregeren. Denk aan "wat was de totale omzet per regio per maand, afgelopen twee jaar". Deze query raakt mogelijk miljoenen rijen, maar wijzigt niets — het is puur lezen en aggregeren. De prioriteit is niet transactiesnelheid maar doorvoersnelheid over grote datavolumes.

Het probleem ontstaat wanneer je OLAP-werk op een OLTP-systeem probeert te doen: de fysieke opslagstructuur die transacties snel maakt (rij-georiënteerde opslag, zie hieronder) is exact de verkeerde structuur voor het scannen van miljoenen rijen om er twee kolommen uit te aggregeren.

Concreet: op een OLTP-database met tien miljoen orderregels en twintig kolommen per rij, moet een aggregatiequery die alleen category en amount nodig heeft, alsnog alle twintig kolommen van elke rij van schijf lezen omdat die fysiek aan elkaar vastzitten — al snel een veelvoud van de data die je daadwerkelijk nodig hebt. Op honderdduizenden gelijktijdige applicatietransacties komt die extra I/O-belasting bovenop, met merkbare vertraging voor elke gebruiker die op dat moment de applicatie gebruikt. Dat is het probleem dat een warehouse structureel oplost, niet door "meer hardware", maar door de opslagstructuur zelf anders in te richten — zie hieronder.

Columnar Storage: de Technische Kern van een Warehouse

Dit is het belangrijkste technische concept in dit hoofdstuk, en het verklaart bijna alles over waarom warehouses zo anders presteren dan productiedatabases.

Een traditionele (OLTP) database slaat data rij-georiënteerd op: alle kolommen van rij 1 staan fysiek bij elkaar op schijf, dan alle kolommen van rij 2, enzovoort. Dit is ideaal wanneer je "geef mij alle gegevens van klant X" opvraagt — één schijftoegang levert de complete rij. Maar wanneer je "som de kolom amount op over 10 miljoen rijen" vraagt, moet het systeem elke rij volledig inlezen, ook al gebruik je maar één van de twintig kolommen uit die rij.

Een data warehouse slaat data kolom-georiënteerd op: alle waarden van kolom amount staan fysiek bij elkaar, gescheiden van alle waarden van kolom customer_id, enzovoort. Voor de aggregatiequery hierboven leest het systeem nu alleen de fysieke blokken die bij amount horen — de andere negentien kolommen worden niet eens van schijf gehaald. Bij een brede tabel met tientallen kolommen is dit het verschil tussen een query die seconden duurt en een query die minuten duurt.

Columnar storage heeft nog een tweede, minder besproken voordeel: compressie. Omdat alle waarden in een kolom hetzelfde datatype hebben en vaak weinig variatie vertonen (denk aan een status-kolom met maar vier mogelijke waarden, of een country_code-kolom), comprimeren kolomsgewijze blokken veel effectiever dan rijsgewijze blokken — vaak met factor 5 tot 10. Minder data op schijf betekent minder data die van schijf naar geheugen moet, wat queries verder versnelt bovenop het voordeel van alleen-de-benodigde-kolommen-lezen.

-- Deze query illustreert het verschil:
-- Op een rij-georiënteerde OLTP-database moet elke rij volledig gelezen
-- worden, ook al gebruiken we maar 2 van de 20 kolommen van 'orders'.
-- Op een kolom-georiënteerd warehouse worden alleen 'category' en 'amount'
-- fysiek van schijf gehaald — de overige 18 kolommen raakt de engine niet aan.
SELECT category, SUM(amount) AS revenue
FROM orders
GROUP BY category;

MPP: Massively Parallel Processing

Columnar storage verklaart waarom één query minder data hoeft te lezen; MPP verklaart waarom moderne warehouses die query ook nog eens over meerdere machines tegelijk kunnen uitvoeren. In een MPP-architectuur wordt een tabel logisch verdeeld over meerdere compute-nodes, en wanneer je een query uitvoert, verwerkt elke node zijn eigen stuk van de data parallel, waarna de resultaten worden samengevoegd.

Snowflake, BigQuery, Redshift en Databricks SQL zijn allemaal, in de kern, MPP-systemen — het verschil zit vooral in hoe expliciet jij als engineer die parallellisatie moet aansturen. Snowflake regelt dit vrijwel volledig automatisch via micro-partities; bij Databricks/Spark heb je meer directe controle (en verantwoordelijkheid) over partitionering. Dit is de reden dat het opschalen van een query op deze platformen vaak zo simpel is als "een groter warehouse kiezen" — je voegt letterlijk meer parallelle rekenkracht toe, in plaats van de query zelf te herschrijven.

Data Warehouse versus Data Lake versus Lakehouse

Deze drie termen worden vaak door elkaar gebruikt, maar verwijzen naar verschillende ontwerpfilosofieën:

Data warehouse — gestructureerde data, schema wordt afgedwongen bij het schrijven (schema-on-write), geoptimaliseerd voor SQL-analytics. Klassiek voorbeeld: Snowflake, Redshift, on-premises Teradata.

Data lake — ruwe, vaak ongestructureerde of semi-gestructureerde data (JSON, logs, afbeeldingen, Parquet-bestanden), opgeslagen in goedkope objectopslag (S3, ADLS), zonder dat een schema wordt afgedwongen bij het schrijven — het schema wordt pas bepaald op het moment dat je de data leest (schema-on-read). Flexibeler, maar zonder discipline eindigt een data lake al snel als een "data swamp": een berg bestanden zonder betrouwbare structuur.

Lakehouse — de poging om het beste van beide te combineren: data lake-achtige opslag (goedkoop, flexibel, in objectopslag) met warehouse-achtige garanties bovenop (ACID-transacties, schema-afdwinging, indexering) via een tabelformaat als Delta Lake of Iceberg. Databricks is het meest bekende voorbeeld van dit patroon; zie Delta Lake voor de technische details van hoe dat precies werkt.

Voor de praktijk van dit handbook is het onderscheid vooral relevant omdat het bepaalt waar je bronze-laag (zie Medallion Architecture) leeft: in een puur warehouse-platform (Snowflake) landt zelfs ruwe bronze-data al in warehousetabellen; in een lakehouse-platform (Databricks) landt bronze vaak eerst als bestanden in objectopslag voordat het als Delta-tabel wordt geregistreerd.

De Klassieke Architectuur: Staging, Warehouse, Marts

Voordat "medallion architecture" de gangbare term werd, gebruikte de warehousing-wereld al decennialang een vergelijkbaar drielagen-model, en het is de moeite waard de link te zien:

Staging — een tijdelijk landingsgebied voor ruwe, ongewijzigde brondata, functioneel bijna identiek aan de bronze-laag. Data hier wordt regelmatig volledig ververst of zelfs weggegooid na verwerking.

Warehouse (of "integration layer") — schone, gededupliceerde, geïntegreerde data over bronnen heen, in een genormaliseerde of licht gedenormaliseerde vorm. Functioneel vergelijkbaar met silver.

Data marts — kleine, sterk gedenormaliseerde, onderwerpgerichte subsets van het warehouse, toegesneden op één afdeling of use case (een "sales mart", een "finance mart"). Functioneel vergelijkbaar met gold, met als verschil dat marts historisch vaak per-afdeling gescheiden werden gebouwd, terwijl moderne gold-tabellen vaker organisatiebreed herbruikbaar worden gehouden.

De terminologie is verschoven, maar het onderliggende inzicht niet: ruwe data, geïntegreerde data, en presentatieklare data zijn drie verschillende verantwoordelijkheden die drie verschillende lagen verdienen — of je ze nu staging/warehouse/marts noemt of bronze/silver/gold.

Praktisch Voorbeeld: een Warehouse Opzetten voor Verkoopdata

Stel je bouwt een warehouse voor een retailbedrijf met verkoopdata uit meerdere winkels. De staging-laag ontvangt dagelijks ruwe CSV-exports per winkel:

-- staging.sales_raw: exact zoals aangeleverd, geen aannames
CREATE TABLE staging.sales_raw (
    store_id      VARCHAR,
    sale_date     VARCHAR,   -- bewust nog VARCHAR: bronformaat is inconsistent
    product_sku   VARCHAR,
    quantity      VARCHAR,
    unit_price    VARCHAR,
    loaded_at     TIMESTAMP DEFAULT CURRENT_TIMESTAMP()
);

De integratielaag (silver) dwingt pas hier types en consistentie af — bewust niet eerder, omdat je in staging nooit data mag verliezen door een te vroege, te strikte cast:

CREATE OR REPLACE TABLE warehouse.sales AS
SELECT
    store_id,
    TRY_CAST(sale_date AS DATE) AS sale_date,
    product_sku,
    TRY_CAST(quantity AS INTEGER) AS quantity,
    TRY_CAST(unit_price AS NUMBER(10,2)) AS unit_price
FROM staging.sales_raw
WHERE TRY_CAST(sale_date AS DATE) IS NOT NULL;   -- rijen met onbruikbare datums expliciet eruit, niet stilletjes NULL

De TRY_CAST in plaats van CAST is bewust: een gewone CAST op een enkele foutieve waarde laat de hele query falen; TRY_CAST geeft NULL terug voor die ene rij en laat de rest van de batch gewoon doorgaan — waarna de WHERE-clausule expliciet beslist wat er met die onbruikbare rijen gebeurt, in plaats van dat de pipeline om 3 uur 's nachts crasht op één rommelige rij.

Een data mart (gold) voor het salesteam aggregeert dit vervolgens tot iets direct bruikbaars:

CREATE OR REPLACE TABLE marts.sales_by_store_day AS
SELECT
    store_id,
    sale_date,
    SUM(quantity * unit_price) AS revenue,
    SUM(quantity) AS units_sold
FROM warehouse.sales
GROUP BY store_id, sale_date;
Zelf zulke SQL genereren?

Probeer de AI SQL Generator voor GROUP BY-aggregaties, staging-modellen en meer.

Open SQL Generator

Slowly Changing Dimensions: een Preview

Eén vraag duikt onvermijdelijk op zodra je warehouse langer dan een paar weken meegaat: wat doe je als een attribuut van een entiteit verandert — een klant verhuist, een product wisselt van categorie? Overschrijf je de oude waarde, of bewaar je de historie? Dit is het domein van slowly changing dimensions (SCD), een van de kernconcepten van dimensioneel modelleren. We behandelen dit niet volledig in dit hoofdstuk — het krijgt de aandacht die het verdient in Star Schema & Dimensional Modeling — maar het is belangrijk om nu al te weten dat "gewoon de rij updaten" een bewuste modelleerkeuze is (SCD Type 1), niet de enige optie, en dat die keuze historische rapportage onherstelbaar kan beïnvloeden als je hem niet vooraf overweegt.

Best Practices

  • Cast nooit te vroeg in staging. Bewaar ruwe waarden als tekst totdat de integratielaag ze expliciet en foutentolerant (TRY_CAST) omzet — anders verlies je stilletjes rijen bij het laden.
  • Behandel columnar storage als een reden om breed te modelleren, niet smal. Extra kolommen in een warehousetabel zijn vrijwel gratis om op te slaan (dankzij compressie) en kosten niets bij queries die ze niet gebruiken — een andere afweging dan bij een rij-georiënteerde database.
  • Kies clustering-/partitiesleutels op basis van je meest voorkomende filterkolom, meestal een datum — dit bepaalt of MPP-parallellisatie je query daadwerkelijk versnelt of niet.
  • Scheid staging/bronze expliciet van warehouse/silver, ook al voelt het als dubbel werk voor een klein project — zie Introduction to Data Engineering voor waarom deze scheiding zich terugbetaalt zodra er een bug wordt gevonden.
  • Behandel data marts (gold) als wegwerpbaar en herbouwbaar. Als een mart niet zonder dataverlies herbouwd kan worden vanuit warehouse/silver, zit er ongemerkt businesslogica in een laag die dat niet zou moeten bevatten.

Veelgemaakte Fouten

  • Rapporteren rechtstreeks op de productiedatabase "omdat het maar een klein bedrijf is" — de OLTP/OLAP-mismatch treedt al op bij bescheiden datavolumes, lang voordat "big data" in beeld komt.
  • Alles in één brede, ongestructureerde tabel dumpen zonder onderscheid tussen staging, geïntegreerde data en presentatieklare data — werkt de eerste maand, wordt daarna onmogelijk te onderhouden.
  • Een CAST gebruiken waar een TRY_CAST hoort, waardoor één rommelige rij een hele nachtelijke load laat falen.
  • Denken dat een data lake een vervanging is voor een warehouse in plaats van een aanvulling — ruwe bestanden in objectopslag zonder schema-discipline lossen geen enkel analytisch probleem op, ze verplaatsen het probleem alleen.
  • Slowly changing dimensions negeren tot het te laat is — pas nadenken over historisering nadat een klant al drie keer is verhuisd en niemand meer weet welke omzet bij welke regio hoorde op welk moment.

Performance Tips

  • Vermijd SELECT * op brede warehousetabellen — het teniet doen van het columnar-voordeel door alle kolommen op te vragen terwijl je er twee nodig hebt, is een van de meest voorkomende manieren om onnodig warehouse-compute te verbranden.
  • Filter op je clustering-/partitiesleutel waar mogelijk, zodat de MPP-engine hele blokken data kan overslaan in plaats van ze te moeten inlezen en daarna pas te filteren.
  • Wees voorzichtig met veel kleine, frequente writes naar een warehouse. Warehouses zijn geoptimaliseerd voor grote, batch-gewijze writes; veel losse INSERT-statements (het OLTP-patroon) presteren hier merkbaar slechter dan op een echte transactiedatabase.
  • Gebruik geaggregeerde marts voor dashboards, niet ruwe silver-tabellen. Een dashboard dat live aggregeert over miljoenen silver-rijen bij elke paginaload is trager en duurder dan een vooraf berekende gold/mart-tabel die elke nacht wordt ververst.

Interviewvragen

"Wat is het verschil tussen OLTP en OLAP, en waarom kun je niet zomaar op je productiedatabase rapporteren?" Let op: begrip van gelijktijdigheid/locking bij OLTP versus doorvoer bij OLAP, en de fysieke reden (rij- versus kolomgeoriënteerde opslag) waarom die twee workloads elkaar in de weg zitten.

"Leg uit wat columnar storage is en waarom het analytische queries versnelt." Let op: het concrete voorbeeld dat alleen de benodigde kolommen gelezen worden, plus het compressie-voordeel — niet alleen "het is sneller".

"Wat is het verschil tussen een data warehouse, een data lake en een lakehouse?" Let op: schema-on-write versus schema-on-read als kernonderscheid, en het lakehouse als poging beide te combineren via een tabelformaat zoals Delta Lake.

"Waarom zou je TRY_CAST gebruiken in plaats van CAST bij het laden van staging-data?" Let op: het besef dat één foutieve waarde anders een hele batch laat falen, en dat expliciet beslissen wat er met onbruikbare rijen gebeurt beter is dan een pipeline die crasht.

"Wat is MPP, en hoe helpt het bij het schalen van een warehouse-query?" Let op: parallelle verwerking over meerdere compute-nodes, en de link naar waarom "een groter warehouse kiezen" op deze platformen vaak volstaat om een trage query te versnellen.

"Wat is een slowly changing dimension, en waarom is 'gewoon updaten' niet altijd het juiste antwoord?" Let op: het besef dat overschrijven historische rapportage kan vervormen, en dat dit een bewuste modelleerkeuze is — een volledig antwoord hoort thuis in het hoofdstuk over dimensioneel modelleren, maar het besef dat de keuze bestaat is hier al relevant.

Relevante Documentatie

Samenvatting

Een data warehouse is geen "grotere database" — het is een fundamenteel andere ontwerpkeuze, gebouwd voor OLAP (weinig, zware, lezende queries) in plaats van OLTP (veel, lichte, schrijvende transacties). Columnar storage en MPP zijn de twee technische pijlers die dat mogelijk maken: kolomsgewijze opslag zodat queries alleen de benodigde kolommen lezen (met flinke compressiewinst als bonus), en parallelle verwerking over meerdere nodes zodat schaal een kwestie van meer rekenkracht wordt in plaats van query-herontwerp. De klassieke staging/warehouse/marts-architectuur is functioneel de voorloper van bronze/silver/gold uit Medallion Architecture — dezelfde verantwoordelijkheidsscheiding, andere naam. Met deze fundamenten begrijp je niet alleen wat je warehouse doet, maar ook waarom — een noodzakelijke basis voordat we in de volgende hoofdstukken dieper ingaan op hoe je die data modelleert.

Hulp nodig bij het bouwen van een modern dataplatform?

DataPartner365 helpt organisaties met Microsoft Fabric, Snowflake, Databricks, dbt, Azure, data-architectuur en CI/CD.

Neem contact op met DataPartner365