Hopp til innhold
↑ ↓ navigerEnter åpneEsc lukk
Teknisk

Fra 61 til 38 millisekunder: slik bygde vi det nye cache-laget i Oppslag

Oppslag serverer 1,1 millioner virksomheter med median svartid på 38 ms. Her er arkitekturen, hva vi prøvde som ikke virket, og hvorfor vi cacher per felt og ikke per objekt.

Kristoffer Bakke · Plattformsjef 11 min lesing

I juli rullet vi ut et nytt cache-lag for Oppslag. Median svartid gikk fra 61 til 38 ms og p99 fra 240 til 110 ms, uten at ferskheten ble dårligere. Dette er historien om hvordan, inkludert to blindveier.

Utgangspunktet

Oppslag speiler flere offentlige registre i en Postgres-database på omtrent 90 GB. Et kall til GET /v1/virksomheter/{orgnr} uten utvidelser traff tre tabeller; med utvid=roller,regnskap traff det sju. Det er ikke mye, men ved 400 kall i sekundet på en database som også tar imot 15 endringer i sekundet fra inntaket, ble låsekonflikter og buffer-utkasting synlige i halen.

Vi hadde en enkel cache foran: hele svaret per (orgnr, utvid, versjon) i Redis med 15 minutters levetid. Treffraten var 71 prosent. Problemet var de 29 prosentene, pluss at hver registerendring ugyldiggjorde alle kombinasjoner av utvid for selskapet.

Blindvei 1: lengre levetid

Første forsøk var å øke levetiden til 60 minutter. Treffraten gikk til 84 prosent, p99 til 190 ms. Men da begynte kunder å se endringer senere enn 15 minutter etter kilden, som er et løfte vi gir. Vi rullet tilbake etter to dager. Lærdom: ferskhet er et produktkrav, ikke en teknisk innstilling.

Blindvei 2: lesereplika

Andre forsøk var å sende alle lesekall til en replika og la primæren bare ta inntak. Det hjalp på låsekonflikter, men replikaforsinkelsen varierte mellom 0 og 8 sekunder under inntakstopper, og vi fikk tilfeller der et selskap var oppdatert i en webhook men ikke i det påfølgende GET-kallet. Kunder som gjør GET etter webhook (som vi anbefaler) fikk gamle data. Det var verre enn problemet vi prøvde å løse.

Det som virket: cache per felt, ugyldiggjør per felt

Innsikten var at et virksomhetsobjekt egentlig er fem uavhengige deler som endres i forskjellig takt:

DelEndres
Grunndata (navn, adresse, form, status)Sjelden, men når det skjer er det viktig
RollerNoen ganger i året
RegnskapÉn gang i året
RettighetshavereSjelden
KonsernSjelden

Den nye cachen lagrer hver del for seg, med nøkkel (orgnr, del, versjon). Et kall med utvid=roller henter to nøkler og setter dem sammen. Når inntaket får en rolleendring, ugyldiggjøres bare (orgnr, roller, *). Grunndata og regnskap for samme selskap ligger urørt i cachen.

Levetiden er nå 24 timer, fordi ugyldiggjøring skjer ved endring, ikke ved tid. Treffraten er 96 prosent. De 4 prosentene er hovedsakelig selskaper ingen har slått opp det siste døgnet, pluss de første kallene etter en endring.

Inntaket skriver cachen

Den andre endringen var at inntaket, som uansett vet nøyaktig hva som endret seg, skriver den nye verdien rett inn i cachen i stedet for bare å slette. Et selskap som nettopp fikk ny daglig leder har altså ferske roller i cachen før første kunde spør. Det fjernet «første kall etter endring»-halen nesten helt, og det er grunnen til at p99 falt mer enn medianen.

Det krever at inntaket og API-et er enige om serialiseringen, som de er fordi de deler samme kodebase og samme versjonslag. Versjonen (2026-09-01 og så videre) er en del av cache-nøkkelen, så flere versjoner kan leve side om side.

Søk er annerledes

Søk (?sok=) cacher vi ikke per spørring; det er for mange varianter. I stedet ligger søkeindeksen i minnet på hver API-node, bygget fra et øyeblikksbilde som oppdateres hvert 5. minutt pluss en liten delta-indeks for endringer siden. Søk er 22 ms median, og vi er fornøyde med det foreløpig.

Tall etter tre måneder

FørEtter
Median svartid61 ms38 ms
p99240 ms110 ms
Treffrate cache71 %96 %
Databaselast (lesing)100 %11 %
Ferskhet (kilde → Spire, p99)14 min9 min

Ferskheten ble faktisk bedre, fordi inntaket ikke lenger konkurrerer med lesing om databasen.

Hva vi gjør videre

Regnskapstall for alle fem år er det største objektet vi cacher, og det hentes sjelden i sin helhet. Vi vurderer å dele det per år. Og søkeindeksen skal bli mer tolerant for skrivefeil i stedsnavn, som er den vanligste klagen vi får.

Spørsmål om arkitekturen? Ta kontakt, eller søk på driftsingeniør hvis du vil jobbe med den.

Første kall på under fem minutter

Opprett en gratis konto, hent en testnøkkel og gjør ditt første oppslag i sandkassen. Ingen kredittkort, ingen binding.