# Partitionering

## Vad det är

Partitionering, eller tabellpartitionering, lagrar raderna i en logisk tabell i flera vanliga
tabeller, där var och en håller de rader vars nyckel faller inom just dess gränser, medan läsningar
och skrivningar fortsätter att använda det enda namn de alltid har använt. PostgreSQL, som den här
sidan följer, kallar sin inbyggda form deklarativ partitionering: tabellen deklareras som
partitionerad, med en metod och en partitionsnyckel, och varje partition skapas som en egen tabell.

Själva den partitionerade tabellen håller inga rader. En insättning dirigeras till den partition
vars gränser passar nyckeln, och en uppdatering som ändrar nyckeln flyttar raden till den partition
där den nu hör hemma. Partitionerna delar föräldertabellens kolumner men kan ha egna index, egna
lagringsinställningar och ett eget tabellutrymme, och en partition kan i sin tur partitioneras igen.

Det finns tre metoder. Intervallpartitionering ger varje partition ett spann av värden, oftast
datum; den nedre gränsen ingår och den övre gör det inte, så med spannen 1–10 och 10–20 hör värdet
10 till det andra. Listpartitionering räknar upp vilka värden varje partition tar emot, till exempel
en uppsättning landskoder. Hashpartitionering ger varje partition en modulus och en rest, och en rad
hamnar där hashvärdet av dess nyckel lämnar just den resten, vilket sprider raderna jämnt när det
saknas ett naturligt intervall att skära efter. En standardpartition kan fånga upp de värden som
inte passar någon annan, men bara vid intervall- och listpartitionering.

Partitionering sker inuti en och samma databasserver. Att dela upp data över separata servrar, var
och en med sin egen kopia av databasschemat, är sharding, och det är ett betydligt större steg. SQL
Server och BigQuery har egna former av partitionering, med egen syntax, och idéerna nedan gäller
även där kommandona skiljer sig.

## Varför det spelar roll

De flesta stora tabeller växer i ena änden. Ordrar, mätvärden, loggar och händelser kommer in varje
dag, ligger kvar i månader eller år, och de flesta databasfrågor gäller den senaste delen. Utan
partitionering arbetar varje fråga mot en enda tabell och en enda uppsättning index som hela tiden
blir större.

Med en partitionsnyckel som passar frågorna kan planeraren bevisa att de flesta partitioner inte kan
innehålla någon matchande rad och lämna dem helt utanför planen. PostgreSQL kallar det partition
pruning. En rapport om förra veckan läser förra veckans partition och inget annat. Dokumentationen
lägger till en mindre uppenbar vinst: eftersom partitionerna tar över det jobb som de övre nivåerna
i ett indexträd annars gör, är det mer sannolikt att de mest använda delarna av indexen ryms i
minnet.

Störst förändring blir det när gammal data ska bort. Att radera en månad av rader ur en enda tabell
är en lång `DELETE` som lämnar döda rader efter sig som `VACUUM` måste städa upp. Med en partition
per månad blir samma gallring en enda `DROP TABLE` eller en enda `DETACH PARTITION`, något som
dokumentationen beskriver som betydligt snabbare än en massoperation och som helt slipper
merkostnaden för `VACUUM`. En frånkopplad partition är fortfarande en vanlig tabell, så den kan
säkerhetskopieras eller sammanfattas innan den tas bort, och partitioner som sällan läses kan ligga
på billigare och långsammare lagring.

Vinsten har ett pris, och dokumentationen säger rakt ut när den lönar sig. Varje partition lägger
till arbete i planeringen, och fördelarna är normalt värda det bara när tabellen annars skulle vara
mycket stor, med tumregeln att tabellen ska vara större än databasserverns fysiska minne.
Partitionering lämnar också en sak orörd: en enskild rad som många transaktioner slåss om är
fortfarande en rad i en partition, bakom ett lås.

## Så ser det ut i praktiken

Partitionering är här alltid ett steg bland flera, bredvid index, omskrivna frågor och
cachning, och aldrig hela svaret.

En anteckning om att skala en databas innan den shardas har ett avsnitt om att
[partitionera tabellerna runt den heta raden](/sv/notes/optimise-before-you-shard/). Där
partitioneras tabellen `orders` per månad på `created_at`, med primärnyckeln `(id, created_at)`
eftersom nyckeln måste innehålla partitionskolumnen. En fråga som anger ett datumintervall läser
bara partitionerna inom det, den här månadens index förblir små nog att ligga i minnet, och
gallringen blir en enda `DETACH PARTITION` i stället för en `DELETE` av miljontals rader.
Anteckningen är också tydlig med gränsen: partitionering gör inte den omstridda raden snabbare, och
en fråga som inte anger nyckeln läser varje partition.

Ett prestandaprojekt i PostgreSQL för en applikation med användarkonton, tenants, geografiska data
och daglig elförbrukning
[delade upp eltabellen i tidsintervall](/sv/portfolio/accelerated-postgresql-performance-by-10x-via-strategic-indexing-15/)
per dag och månad och använde hashpartitionering för de geografiska data. Det skedde tillsammans med
GiST-index, sammansatta index och omskrivna frågor, och det samlade resultatet var 10 gånger bättre
svarstider och 40 % lägre resursanvändning.

En tredje sida beskriver
[mer än 100 databaser i PostgreSQL, SQL Server och BigQuery](/sv/portfolio/architected-created-and-managed-100-postgresql-ms-sql-12/),
till stor del med geografiska data och tidsseriedata. Tidsseriepartitionering var en del av
schemadesignen i PostgreSQL och SQL Server, och i BigQuery användes tidspartitionerade och klustrade
tabeller. Tillsammans med spatiala index, materialiserade vyer och cachning minskade svarstiderna
för de data med 40–60 %.

## Var det går fel

Partitionering flyttar en del av arbetet från frågetid till designtid, och de flesta problem kommer
från ett designbeslut som fattades utan frågorna framför sig.

**Insättningar börjar misslyckas den första dagen i en ny månad.** Det finns ingen partition som
täcker det nya intervallet, och PostgreSQL vägrar en insättning i föräldertabellen som inte hör till
någon befintlig partition. Skapa partitioner i förväg, från ett schemalagt jobb som genererar
DDL-satserna, som dokumentationen föreslår, och larma när den nyaste partitionen närmar sig sitt
slut:

```sql
CREATE TABLE orders_2026_11
  PARTITION OF orders
  FOR VALUES FROM ('2026-11-01')
              TO ('2026-12-01');
```

En standardpartition stoppar felen, men då hamnar rader för varje otäckt månad tillsammans i den
enda tabellen, och att koppla in en ny partition senare skannar standardpartitionen under ett
`ACCESS EXCLUSIVE`-lås, om inte en `CHECK`-begränsning på standardpartitionen redan utesluter det
nya intervallet. Se en standardpartition som innehåller rader som en varning.

**Frågor blir långsammare efter partitioneringen än före.** De vanliga frågorna filtrerar inte på
partitionsnyckeln, så var och en läser varje partition och dess index. En andra orsak är antalet
partitioner: enligt dokumentationen klarar planeraren upp till några tusen bra, förutsatt att
typiska frågor sållar bort alla utom ett fåtal, och planeringstid och minne växer bortom det. Välj
nyckeln utifrån de `WHERE`-villkor som applikationen redan skriver, kontrollera med `EXPLAIN` att
bara de förväntade partitionerna dyker upp och håll antalet i proportion. En partition per kund är
en typisk fälla, eftersom antalet växer med verksamheten; dokumentationen föreslår i stället
hashpartitionering med ett rimligt antal partitioner.

**En primärnyckel eller unikhetsbegränsning går inte att skapa.** På en partitionerad tabell måste
en unik nyckel eller primärnyckel innehålla varje kolumn i partitionsnyckeln, och partitionsnyckeln
får inte innehålla uttryck eller funktionsanrop. Indexet i varje partition kan bara upprätthålla
unikhet inom den partitionen. Lägg till nyckelkolumnen i begränsningen, så som anteckningens tabell
`orders` gör med `(id, created_at)`.

**Att bygga ett index på föräldertabellen blockerar skrivningar länge.** En vanlig `CREATE INDEX` på
föräldertabellen stänger ute skrivningar tills varje partition har indexerats, och alternativet
`CONCURRENTLY` fungerar per partition snarare än på föräldern. Skapa indexet med `ON ONLY` på
föräldertabellen, vilket lämnar det markerat som ogiltigt, bygg indexet på varje partition med
`CONCURRENTLY` och koppla in vart och ett med `ALTER INDEX ... ATTACH PARTITION`. När indexet för
varje partition är inkopplat blir föräldertabellens index giltigt av sig självt.

**En befintlig stor tabell går inte att ställa om på plats.** PostgreSQL gör inte om en vanlig
tabell till en partitionerad. Skapa den nya partitionerade tabellen och koppla in den gamla tabellen
som en av dess partitioner. Lägg innan dess till en `CHECK`-begränsning som motsvarar partitionens
gränser, så att inkopplingen slipper skanna hela tabellen medan den håller ett
`ACCESS EXCLUSIVE`-lås på den.

## Läs mer

- [Table Partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) — kapitlet i
  dokumentationen för PostgreSQL: de tre metoderna, pruning, underhåll, begränsningar och bästa
  praxis.
- [CREATE TABLE](https://www.postgresql.org/docs/current/sql-createtable.html) — `PARTITION BY`,
  `PARTITION OF`, partitionsgränser och standardpartitionen.
- [ALTER TABLE](https://www.postgresql.org/docs/current/sql-altertable.html) — `ATTACH PARTITION`
  och `DETACH PARTITION`, och vad var och en låser.
- [CREATE INDEX](https://www.postgresql.org/docs/current/sql-createindex.html) — index på
  partitionerade tabeller, `ON ONLY` och bygge med `CONCURRENTLY`.

<https://engineer.company/sv/glossary/partitioning/>
