<!-- Archivering -->
<!-- canonical: https://yres.eu/nl/wiki/concepten/archivering -->
# Archivering

**Archivering** verplaatst oudere rijen uit het history-schema (`HIS`/`ODS`) naar **Parquet-bestanden in de Azure Data Lake**. Zo blijft de actieve datawarehouse-database klein en goedkoop, terwijl de oudere historie bewaard blijft als kolomgeoriënteerde bestanden — en via een automatisch onderhouden **union-view** gewoon meequeryt met de live tabel.

Archivering werkt in drie stappen per tabel, binnen één run van de `Dynamic Archiving Workflow YRES`:

1. **Kopiëren** — de rijen die aan de archiveringsregel voldoen gaan als Parquet naar het `archive/`-pad in de Data Lake.
2. **Verifiëren & opschonen (purge)** — `[LoadManagement].[spArchivePurge]` telt de rijen die *nu* aan de regel voldoen en verwijdert ze **alleen bij een exacte match** met het aantal gekopieerde rijen. Wijkt de telling af (er is tussendoor geladen), dan wordt er **niets** verwijderd en kun je de run veilig opnieuw draaien.
3. **Union-view verversen** — `[LoadManagement].[spArchiveMaintainView]` genereert per tabel de view `[<HIS-schema>].[<Target>_IncArchive]` die de live tabel en de gearchiveerde Parquet-bestanden als één geheel toont.

:::tip Eerst proefdraaien, dan pas opschonen
De purge staat standaard **uit** (instelling `ArchivingPurgeEnabled = 0`). In die stand kopieert de workflow alleen naar de Data Lake en wordt er niets verwijderd — ideaal om de configuratie en de Parquet-uitvoer te controleren. Zet daarna `ArchivingPurgeEnabled` op `1` om echt op te schonen — dit is de enige schakelaar van de purge. (`AllowDeletesFromDB` speelt hier bewust geen rol: die instelling gaat over het droppen van objecten, niet over het verwijderen van data.)
:::

## Twee archiveringsmodi per tabel

Archivering is **opt-in per tabel** en kent twee modi, ingesteld op `LoadManagement.UsedTables` (via `spMaintainTable`):

| Modus | Wat wordt gearchiveerd | Typisch gebruik |
|---|---|---|
| **`CLOSED`** | Alleen **afgesloten SCD2-versies**: rijen met `isCurrent = 0` waarvan de `ETL_EndDate` ouder is dan de bewaartermijn. De actuele rij blijft altijd in de database. | Historie-opschoning zonder functioneel effect: de actuele stand blijft compleet. |
| **`BUSINESS`** | **Echte data** op basis van een **datumkolom in de dataset** (de `ArchivingColumn`), bv. facturen ouder dan 10 jaar — inclusief actuele rijen. | Wettelijke/functionele retentie: "alles ouder dan X jaar mag het datawarehouse uit". |

De configuratiekolommen:

| Kolom (`UsedTables`) | Betekenis |
|---|---|
| `ArchivingMode` | `NULL` = uit, `CLOSED` of `BUSINESS`. |
| `ArchivingColumn` | Alleen bij `BUSINESS`: de datasetkolom met de businessdatum (moet in de Dictionary bestaan). |
| `ArchivingRetention` + `ArchivingRetentionUnit` | De bewaartermijn: een aantal `YEAR` / `MONTH` / `DAY` (bv. `10` + `YEAR`). |
| `ArchivingClause` | Geavanceerd: een vrije WHERE-clausule die de modus **overstemt** — voor uitzonderingsgevallen zoals een Unix-timestampkolom. Bij `BUSINESS` mag zo'n clausule alleen datakolommen gebruiken (geen `ETL_*`/`isCurrent`). |

De configuratie wordt **direct bij het opslaan gevalideerd** (`spMaintainTable`): een onbekende modus,
`BUSINESS` zonder datumkolom of clausule, of een ontbrekende bewaartermijn levert meteen een duidelijke
foutmelding op — in plaats van dat de archivering stilletjes uit blijft staan.

Uit die configuratie bouwt de function **`[LoadManagement].[fxArchivingPredicate]`** één archiveringsconditie, met de grensdatum als **vaste literal** — zodat de kopieer- en opschoonstap binnen één run gegarandeerd exact dezelfde regel gebruiken. Het resultaat verschijnt als **`ArchivingScript`** (`FROM <HIS-schema>.<Target> WHERE <conditie>`) op de contractview `[LoadManagement].[vwExtractor]`; de workflow pakt alleen tabellen waar dit script gevuld is.

:::note Gearchiveerde data komt niet terug (BUSINESS)
Bij `BUSINESS`-archivering kan een verwijderde rij nog in het bronsysteem bestaan. Daarom **blokkeert de laadmachine** (`spHIS_InsertAndUpdate`) bij deze modus alle binnenkomende rijen die in de "gearchiveerde ruimte" vallen (businessdatum ouder dan de grens): ze bereiken `HIS` nooit meer, ook niet via een FULL- of IMAGE-load. Het aantal geblokkeerde rijen wordt per load gelogd (`Blocked archived-space rows in page` in de monitoring). Tip: zet het `LoadFilter` van zo'n tabel gelijk aan de archiveringsgrens, dan haalt de bron-extractie die oude data ook niet meer op. `CLOSED` heeft deze blokkering niet nodig: de actuele rij blijft immers gewoon in de database staan.

Deze garantie blijft ook overeind als je de configuratie later **verandert of uitzet** — zie het
[purge-geheugen](#purge-geheugen) hieronder.
:::

## Wanneer draait archivering?

Archivering is een **aparte workflow** (`Dynamic Archiving Workflow YRES`), los van de normale load. Hij draait wanneer je hem expliciet start — handmatig of via een eigen trigger — met dezelfde scope- en tierparameters als de gewone load:

| Parameter | Standaard | Betekenis |
|---|---|---|
| `Source` / `Schema` / `Table` | `ALL` | Beperk de run tot één bron, schema of tabel. |
| `RunningTier` | `Current` | Schaal de database tijdelijk op tijdens de run. |
| `RevertToTier` | `Previous` | Tier waarnaar na afloop wordt teruggeschaald. |

De `ForEach` over de tabellen draait parallel (`batchCount: 3`); de pipeline zelf heeft `concurrency: 1` — er draait nooit meer dan één archiveer-workflow tegelijk.

## Hoe het werkt (op hoofdlijnen)

```
Handmatige run / trigger  (Source, Schema, Table, RunningTier, RevertToTier)
  → (optioneel) Set DB Tier          → schaal de database tijdelijk op
  → Get tables (Lookup)              → SELECT … FROM [LoadManagement].[vwExtractor]
                                        WHERE [ArchivingScript] IS NOT NULL
  → ForEach per tabel (parallel):
        Copy data                    → 'SELECT * ' + ArchivingScript
                                        → Parquet op archive/… (AzureDataLakeStorage_ARCHIVE)
        Purge archived rows          → [LoadManagement].[spArchivePurge]
                                        telt opnieuw; verwijdert alleen bij exacte match,
                                        gefaseerd (batches), gegate door ArchivingPurgeEnabled
        Maintain archive view        → [LoadManagement].[spArchiveMaintainView]
                                        ververst de <Target>_IncArchive-unionview
  → (optioneel) Set DB Tier Back     → schaal de database terug
```

Net als bij een gewone load is ADF de generieke uitvoerder en zit de logica in SQL: het `ArchivingScript` begint bewust met `FROM`, zodat er zowel een `SELECT *` (kopiëren) als een `DELETE` (opschonen) vóór geplakt kan worden — beide draaien daardoor op **exact dezelfde** rijenselectie.

## Wat er in de Data Lake landt

De Copy-stap schrijft het resultaat als **Parquet** naar een eigen archiefpad (dataset `AzureDataLakeStorage_ARCHIVE`), gescheiden van de gewone Data Lake-loads:

```
archive / <Source> / <TargetSchema> / <Target> / <jaar> / <maand> / <Target>-<timestamp>.parquet
```

De geëxporteerde rijen behouden alle SCD2-frameworkkolommen (`KeyHash`, `RowHash`, `ETL_Date`, `ETL_EndDate`, `isCurrent` en de `RowID`), zodat het archief dezelfde structuur heeft als de bron in `HIS`.

## Live + archief als één geheel: de `_IncArchive`-views

Na elke geslaagde kopie ververst `spArchiveMaintainView` per tabel de view **`[<HIS-schema>].[<Target>_IncArchive>`**: een `UNION ALL` van de live tabel en de gearchiveerde Parquet-bestanden, gelezen met **Azure SQL data virtualization** (`OPENROWSET` over een external data source op de Data Lake). Afnemers die ook de gearchiveerde historie nodig hebben, bevragen simpelweg deze view in plaats van de tabel.

- De view **dedupliceert** op de interne `RowID` met voorrang voor de live rij — een proefrun in copy-only-modus of een herstart kan dezelfde rijen immers twee keer in het archief zetten.
- De kolomlijst en datatypes worden bij elke archiveringsrun **opnieuw gegenereerd** uit de live tabel, dus kolomwijzigingen volgen vanzelf.
- View-onderhoud kan archivering **nooit blokkeren**: lukt het niet (bv. rechten nog niet ingericht), dan wordt dat gelogd en gaat de archivering gewoon door. De view verschijnt automatisch bij de eerstvolgende run nadat het probleem is opgelost.

:::caution Rechten voor de union-views
De views lezen het Data Lake rechtstreeks vanuit SQL. Daarvoor moet eenmalig per omgeving zijn ingericht: een **system-assigned managed identity op de logical SQL-server**, **Storage Blob Data Reader** voor die identity op de Data Lake-container, en de instelling **`ArchiveLakeLocation`** (`adls://<container>@<account>.dfs.core.windows.net`). Zolang dat niet gebeurd is, werkt archiveren zelf gewoon — alleen de views worden overgeslagen (met een melding in de monitoring). Data virtualization is een preview-feature van Azure SQL Database.
:::

## Purge-geheugen: consistent, ook na configuratiewijzigingen {#purge-geheugen}

Elke geslaagde, geverifieerde purge wordt vastgelegd in **`[LoadManagement].[ArchiveLog]`**: één rij per
opschoning, met exact de conditie waarmee de rijen verwijderd zijn. De laadmachine past bij elke load
**alle onthouden condities** toe, bovenop de actuele archiveringsregel.

Dat maakt het gedrag voorspelbaar in situaties die voorheen om oplettendheid vroegen:

- **Configuratie gewijzigd** (langere bewaartermijn, andere kolom) — de rijen die onder de oude regel
  zijn opgeschoond, blijven geblokkeerd en sluipen niet terug in `HIS`.
- **Archivering uitgezet** — al opgeschoonde data blijft opgeschoond; het archief in de Data Lake en de
  `_IncArchive`-view blijven gewoon leesbaar.
- **Bewust terughalen** — wil je opgeschoonde rijen wél opnieuw laden, verwijder dan de
  `ArchiveLog`-rijen van die tabel; de eerstvolgende load neemt ze weer mee (het Parquet-archief blijft
  daarnaast altijd beschikbaar).

Het geheugen blijft compact: een nieuwe purge op dezelfde kolom vervangt oudere, ruimere regels, zodat
er per tabel maar een handvol rijen staat.

## De instellingen

| Instelling (`[Config].[Settings]`) | Standaard | Wat het regelt |
|---|---|---|
| **`ArchivingPurgeEnabled`** | `0` (Nee) | Hoofdschakelaar — en enige gate — van de opschoonstap. `0` = alleen kopiëren (proefdraaien), `1` = na geverifieerde kopie ook verwijderen uit `HIS`. De bestaande instelling `AllowDeletesFromDB` speelt bewust géén rol bij de purge: die gaat over het droppen van objecten, niet over dataverwijdering. |
| **`ArchiveLakeLocation`** | leeg | `adls://…`-locatie van de Data Lake voor de union-views; wordt door provisioning gevuld. Leeg = views worden overgeslagen. |

De **[health checks](https://yres.eu/nl/wiki/referentie/monitoring-logging)** (`vwYresChecks`, groep 7) bewaken de configuratie: `BUSINESS` zonder datumkolom, een `ArchivingColumn` die niet in de Dictionary bestaat, een ontbrekende bewaartermijn, een clausule op `ETL_`-kolommen en een purge die aanstaat terwijl `ArchiveLakeLocation` leeg is (check 7.14 — het archief is dan niet vanuit SQL leesbaar), worden allemaal gesignaleerd. Ook het purge-geheugen wordt bewaakt: check **7.15** waarschuwt als een onthouden blokkering verwijst naar een kolom die niet meer in de selectie zit (elke load zou daarop stuklopen), en check **7.16** laat zien wélke tabellen nog een actief purge-geheugen hebben terwijl de huidige configuratie geen blokkering meer instelt — handig om te begrijpen waarom opgeschoonde rijen niet terugkomen, en waar je moet zijn als je dat wél wilt.

:::note Vervallen: settings-gestuurde standaard-archivering
Oudere versies bevatten een alternatieve opzet (`vwArchivingExtractor` met de instellingen `DefaultArchivingDate`/`DefaultArchivingLoadtypes`) die automatisch alle DELTA-tabellen zou archiveren. Sinds v1.56 is archivering bewust een expliciete keuze per tabel; de deploy ruimt de oude view en instellingen zelf op.
:::

## Monitoring

Archiveringsruns verschijnen in de gewone monitoringschermen: de stappen per tabel loggen als `Process = 'load'` (zichtbaar in `vwLoads`/`vwMonitor`, met de HIS-tabel als target) en de workflowstappen als `Process = 'Workflow'` (zichtbaar in `vwWorkflow`). Daarnaast schrijven de archiveringsprocedures **detailstappen** met `Process = 'Archiving'`: de verificatietellingen (gekopieerd vs. nu aanwezig), het aantal verwijderde rijen, overgeslagen purges (schakelaar uit) en het view-onderhoud. Mislukt de verificatie, dan faalt de run zichtbaar met de reden in de log — en is opnieuw draaien altijd veilig, want er is dan niets verwijderd.

## Verschil met de load types

Archivering staat **los van** de [load types](https://yres.eu/nl/wiki/concepten/load-types). Een load type bepaalt wat er bij het *laden* met de history-tabel gebeurt; archivering bepaalt wat er met *oude* rijen gebeurt. Let in het bijzonder op:

- **OVERWRITE** wist de history bij elke load — daar valt weinig te archiveren.
- **FULL**, **DELTA**, **IMAGE**, **RELOAD** en de andere types bouwen wél SCD2-historie op; juist die tabellen profiteren van `CLOSED`-archivering.
- Tabellen uit **bestandsbronnen** doen (nog) niet mee met archivering.

Zie ook [Load types](https://yres.eu/nl/wiki/concepten/load-types), [Historie & SCD2](https://yres.eu/nl/wiki/concepten/historie-scd2), de [begrippenlijst](https://yres.eu/nl/wiki/glossary) en [Views & pipelines](https://yres.eu/nl/wiki/setup/views-pipelines).
