Kapittel 8 · 8C · Forelesning 16

Recovery — livet etter krasj

Notater kap. 18. Loggens jobb: garantere atomicity (alt-eller-ingenting) og durability (varig lagring) uansett hva som skjer — strømbrudd, at operativsystemet dreper databaseprosessen, eller en transaksjon som aborteres på grunn av deadlock.

01 · Mål

Hva skal recovery levere?

Recovery manager har bare ett oppdrag: gjenopprette databasen til en konsistent tilstand etter en feil — og det med to ACID-bokstaver i bagasjen, A og D.

  • Atomicity — alle uavsluttede transaksjoner ved krasj skal rulles tilbake (undo).
  • Durability — alle committed transaksjoner ved krasj skal være på disken etterpå (redo om nødvendig).

I tillegg må recovery-prosessen være idempotent (gir samme resultat uansett hvor mange ganger den kjøres): hvis maskinen krasjer igjen midt under recovery, skal det å starte recovery på nytt fortsatt gi riktig resultat. Det er kompenserende loggposter (CLR — Compensating Log Records, seksjon 06 nedenfor) som gjør dette mulig.

Tre typer feil

  1. Transaksjons-abort under normal drift — manglende data, brukervalg, deadlock.
  2. System-krasj — strømbrudd, OS-kill, software-feil. RAM forsvinner, disk overlever.
  3. Mediefeil — disken selv er ødelagt. Trenger backup + redo. (Utenfor pensum.)
Sjekk · Recovery-mål
Hvilke ACID-egenskaper er recovery managerens ansvar?
AI og C
BBare A
CA og D
DA, C og D
A og D — Atomicity og Durability. Atomicity oppnås ved undo (rull tilbake løsere) og Durability ved redo (sørg for at vinneres endringer er på disk). Isolation er lock managerens jobb (kapittel 8B), og Consistency er brukerens ansvar via integritetskrav.
02 · Vinnere og tapere

Hvilke transaksjoner var egentlig ferdige?

Når strømmen gikk, var noen transaksjoner committed (vi vet det fordi commit-loggposten — en oppføring i loggen som markerer at transaksjonen committet — var på disk), andre var midt i. De første kalles winners, de andre losers.

Tid → CRASH T1 commit ✓ T2 commit ✓ T3 commit ✓ T4 løs — undo T5 løs — undo winner (committed) loser (in-flight)
T1, T2, T3 fikk commit-loggrecord på disk før krasj — de er winners. T4 og T5 hadde ikke det — løsere som må rulles tilbake.

Etter krasj må recovery-prosessen:

  1. Finne ut hvem som var winner og hvem som var loser (Analysis-fasen).
  2. Sørge for at alle winners' endringer er på disk (Redo-fasen).
  3. Rulle tilbake alle losers' delvise endringer (Undo-fasen).
Sjekk · Vinnere
En transaksjon har skrevet en commit-loggpost til log-bufferen (et RAM-område der nye loggposter samles før de skrives til disk), men maskinen krasjer før loggbufferen blir tømt til disk. Er den winner eller loser?
AWinner — den prøvde å committe
BLoser — committen var ikke på stabil lagring (disk) ved krasjet
CDen blir spart ved at vi henter loggen fra RAM
DAvhenger av isolation level
Loser. RAM forsvinner ved krasj. Vi vet kun om transaksjoner som har commit-record på disk. Det er nettopp derfor regelen om at log-bufferen må flushes (skrives ut) til disk før commit returneres til klienten finnes — for å sikre durability. Vi går inn på dette i §05 (WAL).
03 · Buffer-policy

Force og steal

Hvilken algoritme et DBMS trenger avhenger av to designvalg om buffer-poolen (RAM-området der databasen mellomlagrer datasider fra disk — kap. 6):

  • Force: Tvinger systemet at alle data-sider skrives til disk ved commit?
    • Ja (force) — durability lett, men dyr commit (mange random writes).
    • Nei (no-force) — billig commit, men trenger redo etter krasj.
  • Steal: Kan en plass i bufferpoolen «stjeles» (skrives til disk) fra en uavsluttet transaksjon?
    • Ja (steal) — fleksibel buffer-bruk, men trenger undo for halvskrevne sider.
    • Nei (no-steal) — uavsluttede sider holdes i buffer til commit. Begrenset.
No-stealSteal
ForceShadowing (ingen logg)Bare undo-logg
No-forceBare redo-loggUndo + redo (ARIES)

ARIES (Algorithms for Recovery and Isolation Exploiting Semantics — den klassiske recovery-algoritmen, fra IBM på 90-tallet) sitter i steal/no-force-hjørnet: maksimal fleksibilitet for buffer manager, men krever både undo- og redo-logging. Det er prisen for ytelse i moderne SQL-databaser.

Update-in-place vs shadowing

De tre andre cellene gir alternative recoveryteknikker pensum nevner kort: Undo/no-redo (force + steal), No-undo/redo (no-force + no-steal), og Shadowing (force + no-steal — uten logg, lager kopier av data og bytter pekere ved commit, for eksempel Copy-On-Write-strukturer som lager ny kopi før endring i stedet for å skrive over). Hovedskillet er update-in-place (ARIES — endrer datablokken direkte) versus shadowing (lager nye kopier).

Sjekk · Force/Steal
Hvorfor unngår de fleste databaser force-policyen ved commit?
ADet bryter med ACID
BDet er dyrt — datablokkene kan være spredt utover disken og krever mange random writes
CDet krever shadowing
DDet er ikke mulig på SSD
Random writes er dyrt. Force betyr at alle berørte datablokker må skrives til disk ved commit — og det kan være mange små, spredte sider. Loggen er sekvensiell og rask å skrive. No-force lar datablokkene flushes asynkront i bakgrunnen, mot at vi må kunne redoe etter krasj.
04 · Loggen

Append-only kronologi

Loggen er en sekvensiell, append-only (bare tilføy, aldri overskriv) fil på en egen disk. Hver loggpost har en stadig voksende LSN (Log Sequence Number — et unikt løpenummer).

Generisk log-record

(LSN, TransID, PrevLSN, OpType, PageID, Offset, BeforeImage, AfterImage)
  • LSN — unik, voksende. Også brukt som PageLSN på datasiden.
  • PrevLSN — peker bakover til forrige log-record fra samme transaksjon. Lager en lenket kjede per Tx.
  • OpType — update / insert / delete / commit / abort / CLR / checkpoint.
  • BeforeImage / AfterImage — hva som lå der før og etter (brukes til hhv. undo og redo).

PageLSN — koblingen mellom logg og data

Hver dataside har et felt PageLSN som inneholder LSN-en til siste log-record som ble brukt på siden. Det er den sjekken redo-fasen bruker for å avgjøre om en endring allerede er reflektert på siden.

Log buffer (RAM) LSN 100 LSN 101 LSN 102 LSN 103 LSN 104 FlushedLSN = 102 Log på disk: LSN 0..102 Dataside i buffer PageID: A PageLSN: 102 Innhold: … Database-disk
FlushedLSN = nyeste log-record på disk. PageLSN på dataside = LSN av siste endring på siden. Ved å sammenlikne kan vi vite om en endring trenger redo.
Sjekk · Loggpost-felt
Hva er PrevLSN-feltet i en loggpost brukt til?
AIdentifisere forrige sjekkpunkt
BLenke loggposter for samme transaksjon bakover — brukes ved abort/undo
CPeke til forrige loggpost på disken
DHolde rede på neste LSN som skal skrives
Riktig. PrevLSN lager en kjede gjennom alle loggposter fra samme transaksjon. Ved abort starter vi på LastLSN i transaksjonstabellen og følger PrevLSN bakover for å undoe alle endringene transaksjonen har gjort. Lenken er essensiell — uten den måtte undo skanne hele loggen.
Sjekk · PageLSN
Hva betyr det at en datasides PageLSN = 250?
ASiden ble flushet til disk på det tidspunkt LSN 250 ble skrevet
BDen siste loggposten som er anvendt på siden, hadde LSN 250
CSide-id-en er 250
DDet er 250 endringer ventende på siden
Siste anvendte LSN. PageLSN er stempelet «hvor langt har vi anvendt logg på denne siden». Redo bruker det til å hoppe over endringer som allerede er reflektert: hvis log-record har LSN L og PageLSN ≥ L, er endringen allerede der. (A) blander sammen med FlushedLSN (siste loggpost på disk), som er en separat verdi for selve loggen. (C) og (D) misforstår hva LSN er.
05 · WAL

Write-ahead logging

Hele recovery-arkitekturen hviler på én regel:

WAL-regelen: Før en endret dataside skrives til disk, må alle loggposter som beskriver endringen være på disk først.

Konkret: før vi skriver dirty side P med PageLSN = L til disk, må FlushedLSN ≥ L. Hvis ikke, kunne et krasj rett etter side-skrivingen latt oss stå med en delvis oppdatert side på disk uten noen log-record som forteller hvordan vi skulle rulle den tilbake — atomicity brutt.

WAL har egentlig to deler:

  1. Undo-WAL — log først, data etterpå (ovenfor). Sikrer atomicity.
  2. Force-log-at-commit — ved COMMIT, tving log-bufferen til disk før suksess returneres til klient. Sikrer durability.

Log-buffer

100: w(A, …) ✓ disk
101: w(B, …) ✓ disk
102: w(C, …) ⚡ ny

Buffer-pool

side A · PageLSN=100
side B · PageLSN=101
side C · PageLSN=102 (dirty!)

Disk

log: 0..101
side A · på disk
side B · på disk
side C · gammel versjon

Side C kan ikke skrives til disk før log-record 102 er på disk (FlushedLSN ≥ 102).

Sjekk · WAL
Hvorfor er WAL-regelen «logg først, data etterpå» — ikke omvendt?
ALoggen er kortere enn dataen
BHvis vi krasjer, må vi kunne rulle siden tilbake — det krever before-image fra loggen
CDisken bruker mindre energi på sekvensielle skrivinger
DSQL-standarden krever det
Atomicity-argumentet. Hvis siden gikk på disk uten loggen, og vi krasjet, ville vi sittet med halvskrevne sider på disken og ingen måte å rulle dem tilbake. Loggen ligge på disk først, slik at vi alltid har before-image om vi trenger å undo.
06 · CLR

Compensating Log Records

Tenk: under undo for en aborted transaksjon krasjer maskinen. Når den starter på nytt — hvor langt har vi kommet i å rulle tilbake? Skal vi rulle tilbake samme record igjen?

ARIES sin eleganse: når vi undoer en loggpost, skriver vi en ny loggpost som beskriver «jeg har angret den endringen». Den heter en CLR (Compensating Log Record — kompenserende loggpost). PageLSN på siden oppdateres til CLR-ens LSN.

Egenskaper:

  • CLR-er kan redoes hvis krasj skjer under undo, men undoes aldri.
  • Undo-prosessen følger PrevLSN i den opprinnelige (non-CLR) loggposten for å finne neste loggpost å angre.
  • CLR-en lar undo bli en roll-forward-operasjon (vi går framover i loggen i stedet for bakover) — andre operasjoner kan fortsette på samme blokk.

Resultat: idempotent recovery. Du kan krasje, restarte, krasje igjen — slutt-tilstanden er den samme.

Sjekk · CLR
Kan en CLR (Compensating Log Record) bli undone?
AJa — som en hvilken som helst loggpost
BNei — CLR-er er redo-only. Det er nettopp det som gjør recovery idempotent.
CBare hvis transaksjonen aborterer
DBare i strict 2PL
Nei. CLR-er undoes aldri — kun redoes hvis krasj skjer under recovery. Hvis CLR-er kunne undoes, ville en serie krasj kunne sette databasen i en uendelig løkke av undo/undo-undo. Ved at de er redo-only og oppdaterer PageLSN på siden, vet vi alltid hvor langt undo har kommet.
07 · Datastrukturer

Det ARIES holder rede på

To tabeller i RAM. De kopieres periodisk til loggen via et sjekkpunkt — en loggpost som lagrer et øyeblikksbilde av TT og DPT, slik at recovery slipper å lese loggen fra start og kan begynne der. (Full mekanikk i §10.)

Transaction Table (TT)

Én rad per aktiv transaksjon. Inneholder:

  • TransID
  • Status — active / committed / aborted
  • LastLSN — siste loggpost skrevet av denne Tx (start på undo-kjeden)

Dirty Page Table (DPT)

Én rad per dirty side i buffer (modifisert, men ikke skrevet til disk). Inneholder:

  • PageID
  • RecLSN — LSN av første loggpost som gjorde siden dirty etter siste flush. Det er det tidligste punktet redo må gå tilbake til for denne siden.

Hvorfor skiller vi RecLSN fra PageLSN? Begge gjelder samme side, men ulike formål: RecLSN sier hvor langt tilbake redo må starte; PageLSN sier hvor langt redo allerede er kommet.

Sjekk · Datastrukturer
Hva skjer med en sides oppføring i DPT når siden flushes til disk?
ARecLSN settes lik PageLSN
BOppføringen fjernes — siden er ikke lenger dirty
COppføringen merkes "clean" men beholdes
DIngenting endrer seg
Fjernes. DPT inneholder kun sider som har endringer i buffer som ikke er på disk. Når flushen er ferdig, er bufferen og disken synkronisert, og siden tas ut. Hvis den senere skrives på igjen, kommer den tilbake i DPT med en ny RecLSN.
08 · ARIES

Tre faser ved restart

Etter krasj går recovery-prosessen gjennom nøyaktig tre faser, alltid i denne rekkefølgen. Klikk «Spill av» for å se rekkefølgen:

FASE 1

Analysis

Skann logg fra siste sjekkpunkt og fremover. Bygg opp Transaction Table og Dirty Page Table slik de var ved krasjet (TT/DPT-snapshotet i sjekkpunktet er startpunktet — se §10). Identifiser winners (committed) og losers (in-flight).

FASE 2

Redo

Start ved minste RecLSN i DPT. Skann fremover. For hver loggpost: utfør den om PageLSN < LSN. Repeating history (vi gjenspiller all historikk) — alle endringer (også fra losere!) gjenspilles.

FASE 3

Undo

baklengs gjennom loggpostene til losers (via PrevLSN). For hver, skriv en CLR og angre endringen. Når alle losers er rullet helt tilbake — ferdig.

Hver fase tar ca. 1.4s i animasjonen.
Snikende detalj — repeating history

Redo gjenspiller alt, også endringer gjort av løsere. Hvorfor? Fordi PageLSN-sjekken bruker LSN-sammenlikning, og fordi det forenkler logikken: når vi når undo-fasen, er databasen i nøyaktig samme tilstand som ved krasjet (inkludert halvgjorte endringer fra losere). Så undo må bare reverse log-recordene, uansett hva som har skjedd.

Restart-algoritmen i pseudokode

Et sjekkpunkt skrives som to loggposter — begin_checkpoint og end_checkpoint (med TT/DPT-snapshotet) — og en log anchor (master record på disk) peker til siste fullførte begin_checkpoint. Detaljer i §10.

function RESTART():
    # ── ANALYSIS ──
    finn siste begin_checkpoint i loggen via log anchor
    TT, DPT ← snapshot fra end_checkpoint
    for hver log-record fra begin_checkpoint og fremover:
        if record er Update / Insert / Delete:
            if Tx ikke i TT: legg til (status=active, lastLSN=LSN)
            else: oppdater Tx.lastLSN = LSN
            if PageID ikke i DPT: legg til (RecLSN=LSN)
        else if record er Commit:
            TT[Tx].status = committed
        else if record er Abort:
            TT[Tx].status = aborted
    losers ← {Tx i TT der status ≠ committed}

    # ── REDO ──
    startLSN ← min(RecLSN over alle sider i DPT)
    for hver log-record fra startLSN og fremover:
        if PageID ikke i DPT: skip
        if LSN < DPT[PageID].RecLSN: skip
        les inn siden; if PageLSN ≥ LSN: skip
        utfør operasjonen; sett PageLSN = LSN

    # ── UNDO ──
    for hver loser Tx:
        L ← Tx.lastLSN
        while L ≠ 0:
            record ← logg[L]
            if record er CLR:
                # CLR-er undoes aldri — hopp via PrevLSN av non-CLR-en
                # som CLR-en kompenserte for
                L ← PrevLSN av non-CLR-en CLR-en peker på
            else:
                angre operasjonen; skriv CLR
                # PageLSN på berørt side = CLR-ens LSN
                L ← record.PrevLSN
    skriv "end_recovery" til logg

CLR-er er redo-only. Ved krasj under undo: ny restart finner siste CLR fra loseren og fortsetter via PrevLSN av den loggposten CLR-en kompenserte for — derfor blir aldri samme operasjon angret to ganger.

Bla gjennom kortene under for å sjekke at du forstår fasene og hvorfor de er nødvendige.

Tastatur: forrige · neste · Enter/Space snu kort
1 / 0
ARIES · faserekkefølge Lett

Hvorfor må Analysis kjøres før Redo?

  • A Det må den ikke — rekkefølgen kan byttes
  • B Redo trenger DPT for å vite hvor langt tilbake i loggen den må starte (minste RecLSN)
  • C Analysis bruker mindre minne enn Redo
  • D Analysis sletter loggen etter at den er ferdig

Riktig: B — DPT trengs først.

Analysis rekonstruerer DPT og TT slik de var ved krasjet. Redo bruker DPT til å starte ved minste RecLSN — uten det måtte Redo skanne hele loggen fra start. Undo trenger TT for å vite hvilke transaksjoner som er løsere. Faserekkefølgen Analysis → Redo → Undo er fast.

ARIES · repeating history Middels

Redo-fasen gjenspiller også endringer fra transaksjoner som aldri committet (losere). Hvorfor?

  • A Det er en bug — Redo burde hoppet over losere
  • B For å forenkle logikken: etter Redo er DB i nøyaktig krasj-tilstand, så Undo kan reverse log-recordene direkte
  • C For å spare minne under recovery
  • D For at lock manager skal kunne rekonstruere låstabellen

Riktig: B — repeating history.

Ved å gjenspille alt (også fra losere), bringes databasen tilbake til nøyaktig den tilstanden den hadde ved krasjet. Da kan Undo bare reverse log-recordene én etter én, uten å bekymre seg for hva som faktisk er på disk eller ikke.

PageLSN-sjekken (if PageLSN ≥ LSN: skip) sørger for at vi ikke gjør samme operasjon to ganger om en endring allerede er reflektert på siden.

ARIES · Undo via PrevLSN Middels

Under Undo, hvordan finner ARIES neste loggpost å angre for en loser-transaksjon?

  • A Skanner loggen sekvensielt fra slutten til en match på TransID
  • B Bruker PrevLSN-feltet til å hoppe direkte bakover langs Tx-ens kjede
  • C Slår opp i Dirty Page Table
  • D Følger PageLSN på berørte sider

Riktig: B — PrevLSN-kjeden.

Hver loggpost peker bakover til forrige loggpost fra samme transaksjon via PrevLSN. Undo starter på Tx.LastLSN i TT og hopper direkte langs kjeden — O(antall log-records for Tx-en), ikke O(hele loggen).

Sekvensiell skanning ville vært katastrofalt for loggen, som typisk er gigabytes stor.

ARIES · krasj under undo Vanskelig

Restart kjører, halvveis i Undo-fasen krasjer maskinen igjen. Hva skjer ved neste restart?

  • A Samme operasjon angres en gang til — derfor er det ikke trygt
  • B Recovery er idempotent: CLR-er sier hva som er angret, og prosessen fortsetter trygt videre
  • C Loggen må startes på nytt fra begin_checkpoint før forrige restart
  • D Databasen er ødelagt og må gjenopprettes fra backup

Riktig: B — idempotent.

Hver gang Undo angrer en operasjon skrives en CLR med PageLSN på siden oppdatert til CLR-ens LSN. Etter krasjen finner Analysis disse CLR-ene, Redo gjenspiller dem (slik at PageLSN-er stemmer), og Undo fortsetter via PrevLSN av den loggposten CLR-en kompenserte for — aldri den samme operasjonen to ganger.

Du kan krasje, restarte, krasje, restarte — slutt-tilstanden er den samme.

ARIES · winners Lett

Hva må gjøres med en transaksjon som er committed men hvis dirty sider aldri kom seg ut til disk før krasjet?

  • A Ingenting — committed = på disk per definisjon
  • B Redo gjenspiller alle endringene (Durability)
  • C Undo angrer endringene siden tilstanden er usikker
  • D Transaksjonen aborteres ved restart

Riktig: B — Redo.

WAL-regelen + force-log-at-commit garanterer at loggposter er på disk ved commit, men dataside-skrivinger kan henge i buffer. Redo leser loggen og påfører endringene på sidene — det gir Durability.

(A) er en vanlig misforståelse: «committed» betyr at loggen er på disk, ikke at dataene har nådd dit ennå.

09 · Eksempel

Analysis i praksis

La A, B, C være data-sider. Etter krasj finner vi følgende logg:

LSNPrevLSNTxOpTypePage
1010T1UpdateC
1020T2UpdateB
103101T1Commit
104Begin_ckpt
105End_ckpt (TT, DPT)
1060T3UpdateA
107102T2UpdateC
108107T2Commit

(ckpt = checkpoint, sjekkpunkt — se §10.)

Ved sjekkpunkt (LSN 105) hadde TT og DPT følgende:

TT @ ckpt

TxLastLSNStatus
T1103Commit
T2102Active

DPT @ ckpt

PageRecLSN
C101
B102

Etter Analysis

Vi skanner forover fra LSN 106:

  • LSN 106 (T3, Update A) — T3 er ny i TT, status active, lastLSN=106. A er ny i DPT, RecLSN=106.
  • LSN 107 (T2, Update C) — T2 sin lastLSN oppdateres til 107. C eksisterer allerede i DPT med RecLSN=101 — vi oppdaterer ikke RecLSN (vi vil ha eldste!).
  • LSN 108 (T2, Commit) — T2 sin status blir committed.

TT etter analysis

TxLastLSNStatus
T1103Commit
T2108Commit
T3106Active → undo!

DPT etter analysis

PageRecLSN
C101
B102
A106

T3 er den eneste loseren — den må undoes. Redo starter ved minste RecLSN i DPT = 101.

Redo-test

For hver log-record (101..108) sjekker vi i denne rekkefølgen:

  1. Hopp over hvis PageID ikke er i DPT.
  2. Hopp over hvis log-record sin LSN < sidens RecLSN.
  3. Les inn siden fra disk. Hopp over hvis sidens PageLSN ≥ log-record sin LSN.
  4. Ellers: redo. Skriv AfterImage til siden. Sett PageLSN = log-record sin LSN.

Anta at A, B, C på disk har PageLSN = 106, 102, 101 ved start av redo:

LSNPageTestResultat
101CPageLSN(C)=101 ≥ 101skip
102BPageLSN(B)=102 ≥ 102skip
106APageLSN(A)=106 ≥ 106skip
107CPageLSN(C)=101 < 107REDO — skriv AfterImage, PageLSN(C):=107

Undo

T3 sin LastLSN er 106. Vi går baklengs (PrevLSN=0, så bare denne ene), skriver en CLR for LSN 106 og angrer endringen på A.

Tenk selv · Eksempel
Hvorfor blir RecLSN for C ikke oppdatert når vi behandler LSN 107 i analysis?
RecLSN er eldste log-record som har gjort siden dirty siden siste flush. Hvis vi oppdaterte RecLSN til 107, ville redo ikke startet tilbake fra 101 — og endringen i LSN 101 kunne mistet sin redo om PageLSN(C) på disk var lavere. Vi må holde på den eldste for å være sikre på korrekt redo.

Større eksempel — undo-kjede med CLR-er

Tenk deg samme strukturen, men nå har vi to losere som har gjort flere endringer hver. Loggen ved krasj-tidspunktet:

LSNPrevLSNTxOpTypePage
2000T4UpdateP1
2100T5UpdateP2
220200T4UpdateP3
230210T5UpdateP1
240220T4UpdateP2
250240T4Commit
260230T5UpdateP3
⚡ KRASJ ⚡

Etter analysis: T4 er winner (commit på LSN 250), T5 er loser (ingen commit). T5 sin lastLSN = 260.

Undo-fasen, steg for steg

Vi følger PrevLSN-kjeden bakover fra T5.lastLSN, skriver en CLR for hver angring og oppdaterer PageLSN. Hver CLR peker tilbake til den non-CLR-en den kompenserer for — det er den recorden vi følger PrevLSN av hvis vi krasjer:

StegAngrer LSNPageSkriver CLRNeste i kjeden
1260P3LSN 270 (CLR for 260)230 (= PrevLSN av 260)
2230P1LSN 280 (CLR for 230)210 (= PrevLSN av 230)
3210P2LSN 290 (CLR for 210)0 (= PrevLSN av 210 ⇒ ferdig)
4LSN 300: end_recovery

Loggen etter recovery inneholder altså både de opprinnelige loggpostene og CLR-ene 270, 280, 290 + sluttmarkøren 300. Sidene P1, P2, P3 har nå PageLSN = 280, 290, 270 — pekende på CLR-ene, ikke de angrede recordene.

Hva om vi krasjer under undo?

Anta krasj rett etter at vi skrev CLR 270 (som angrer 260) og siden P3 ble flushed til disk. Vi starter recovery på nytt:

  1. Analysis ser nå LSN 270 som siste record fra T5. T5 har fortsatt status «active» (ingen commit funnet), så T5 er fortsatt loser.
  2. Redo kjøres på vanlig vis. Når den treffer LSN 270 (CLR), redo-er den om PageLSN(P3) < 270 — men siden ble flushed, så PageLSN(P3) = 270 ⇒ skip. Ingen dobbel-undo.
  3. Undo starter på T5.lastLSN = 270 (CLR-en, ikke 260!). Siden CLR-er aldri undoes, hopper vi forbi 270 og finner non-CLR-en CLR-en peker på (LSN 260). Derfra følger vi PrevLSN(260) = 230. Resten av kjeden: angrer 230, så 210, ferdig. Vi har ikke forsøkt å angre 260 to ganger.

Det er nøyaktig dette CLR-mekanismen er bygd for: idempotens. Krasj når som helst — neste restart finner siste CLR for hver loser og fortsetter undo via PrevLSN av den loggposten CLR-en kompenserte for, og produserer samme slutt-tilstand.

Vanlig feil

Studenter tror CLR-er kan undoes som vanlige loggposter. Det kan de ikke. CLR-er er redo-only — i undo-fasen hopper vi over dem og bruker PrevLSN av non-CLR-en de kompenserer for. Hvis CLR-er kunne undoes, ville vi havnet i en uendelig løkke ved gjentatt krasj.

Sjekk · Undo-kjede
Hva er PageLSN på P1 etter at undo-fasen er ferdig i det større eksempelet over?
A200 — den siste committed endringen
B230 — den siste endringen før undo
C280 — CLR-en som angret 230
D0 — siden er nullstilt
280. Hver gang vi skriver en CLR, oppdateres PageLSN på den berørte siden til CLR-ens LSN. Det er nettopp denne mekanismen som gjør at vi vet «vi har allerede angret denne endringen» selv etter krasj. Verdien siden er som etter T4 sin endring (LSN 200), men PageLSN-feltet peker på CLR-en.
10 · Sjekkpunkter

Begrens hvor langt tilbake recovery må gå

Uten sjekkpunkter må Analysis lese hele loggen fra start hver gang vi krasjer. Et sjekkpunkt er en periodisk «her var vi» — og recovery kan starte derfra.

Stop-the-world checkpoint (gammelt)

  1. Stopp alle nye operasjoner.
  2. Vent til alle aktive transaksjoner er ferdige (eller i hvert fall stille).
  3. Flush alle dirty sider til disk.
  4. Skriv «checkpoint»-record i loggen.
  5. Slipp alle løs igjen.

Korrekt, men lammer systemet i sekunder — uakseptabelt for OLTP-trafikk.

Fuzzy checkpoint (ARIES)

  1. Skriv begin_checkpoint-loggpost.
  2. Skriv en end_checkpoint-loggpost som inneholder et øyeblikksbilde (snapshot) av TT og DPT akkurat nå.
  3. Oppdater log anchor (et lite, fast sted på disk — også kalt master record) til å peke på begin_checkpoint.
  4. Bakgrunnsprosess flusher dirty sider uten å blokkere drift.

Recovery starter ved begin_checkpoint; den vet hva TT/DPT så ut da, og leser videre forover for å bygge nåværende tilstand.

Hvorfor to records og ikke bare én?

Mens vi skriver TT og DPT (som kan være store), kjører resten av databasen videre — nye transaksjoner kommer til, andre committer. Snapshotet i end_checkpoint reflekterer altså tilstanden ved et punkt et sted mellom begin og end. Recovery må derfor begynne ved begin_checkpoint for å garantert fange alle endringer som ikke er reflektert i snapshotet.

Hva om vi krasjer mellom begin og end?

Log anchor pekes først oppdatert etter at end_checkpoint er på disk. Krasj mellom begin og end ⇒ log anchor peker fortsatt på forrige sjekkpunkt. Det fuzzy-sjekkpunktet vi var i ferd med å skrive eksisterer som «ufullstendig» i loggen, men recovery ignorerer det og bruker det forrige. Litt mer logg å skanne, men korrekt.

Vanlig feil

Studenter forveksler fuzzy med «inkonsistent». Fuzzy betyr at TT/DPT-snapshotet i end_checkpoint er tatt mens systemet kjørte — det er ikke et perfekt øyeblikksbilde, men recovery-algoritmen er designet for å rekonstruere riktig tilstand fra begin_checkpoint og fremover. Stop-the-world ville gitt et perfekt snapshot, men til prisen av lammelse.

Penultimate checkpointing (nest siste sjekkpunkt)

En annen variant pensum nevner: skriv data ved sjekkpunkting, men bruk nest siste sjekkpunkt som startpunkt for recovery. Garanterer at all data referert i loggen før det punktet er på disk, så Analysis/REDO trenger ikke gå lenger tilbake. Ikke ARIES — ARIES bruker fuzzy checkpoint og DPT i stedet.

Trade-off: hyppige sjekkpunkter ⇒ kort recovery, mer normaldrift-overhead. I praksis kjører kommersielle systemer fuzzy checkpoint hvert 5–10 minutt eller hver N-te MB logg.

Sjekk · Sjekkpunkter
Hva er hovedfordelen med fuzzy checkpoint over stop-the-world?
AKortere recovery-tid
BSjekkpunktet kan tas uten å blokkere pågående transaksjoner
CMindre logg-volum
DMindre disk-bruk
Ingen blokkering. Begge typer gir kort recovery — det er hovedhensikten med alle sjekkpunkter. Forskjellen er at fuzzy lar databasen fortsette å betjene transaksjoner mens sjekkpunktet skrives, så systemet aldri stopper synlig for brukeren.
Sjekk · Krasj under sjekkpunkt
Systemet krasjer etter at begin_checkpoint er på disk, men før end_checkpoint er det. Hva gjør recovery ved restart?
ARecovery feiler — sjekkpunktet er korrupt og må slettes manuelt
BRecovery rekonstruerer TT/DPT fra det halvferdige sjekkpunktet
CRecovery ignorerer det halvferdige sjekkpunktet og bruker forrige fullførte sjekkpunkt
DRecovery starter fra begynnelsen av hele loggen
Forrige sjekkpunkt. Log anchor (master record) oppdateres først etter at end_checkpoint er flushet til disk. Krasj før det ⇒ log anchor peker fortsatt på forrige fullførte sjekkpunkt. Recovery skanner litt mer logg, men slutt-tilstanden er fortsatt korrekt. (B) ville vært feil — uten end_checkpoint er TT/DPT-snapshotet aldri skrevet. (D) ville fungert, men er unødvendig.
11 · Oppsummering

Kort oppsummert

  • Recovery garanterer Atomicity (undo) og Durability (redo).
  • Winners = committed før krasj. Losers = in-flight ved krasj.
  • ARIES = steal/no-force ⇒ trenger både undo- og redo-logging.
  • WAL-regelen: log-records på disk før dataside. Force-log-at-commit: synkron logg ved commit.
  • CLR gjør undo idempotent. PageLSN på siden følger med.
  • Datastrukturer: Transaction Table (aktive Tx) og Dirty Page Table (sider med endringer i buffer).
  • ARIES tre faser: Analysis → Redo → Undo. Redo «repeating history» — også fra løsere.
  • Fuzzy checkpoint: tar snapshot av TT/DPT uten å blokkere drift.

Du har nå gått gjennom hele kapittel 8. Last igjen oversikten, eller hopp tilbake til alle kapitlene.