Hoppa till innehållet
AI-grafen
E· Universitetdatahantering· ca 60 min· utvecklande· verifierad 2026-09-20

Datalager och format: Parquet, Arrow

Kunna välja lagringsformat för stora dataset och förstå kolumnlagring.

Förkunskaper

Intuition

Radlagring (CSV, JSON) sparar en rad i taget:

id,namn,alder,poang
1,Ada,15,82
2,Bo,16,95

Kolumnlagring (Parquet, ORC) sparar en kolumn i taget:

id:    1, 2, 3, 4, ...
namn:  Ada, Bo, Cim, ...
alder: 15, 16, 15, ...

Tre konsekvenser av den skillnaden:

  1. Läs bara det du behöver. SELECT avg(alder) läser en kolumn i stället för hela filen. På breda tabeller är det tio- till hundrafaldig skillnad.
  2. Bättre komprimering. Värden i samma kolumn liknar varandra — samma typ, ofta liknande storlek, ofta upprepningar. Kolumnvis komprimering ger typiskt 5–10× mot CSV.
  3. Sämre för enskilda rader. Ska du läsa eller ändra rad 4 711 måste alla kolumner hämtas. Kolumnformat är byggda för analys, inte för transaktioner.

Tumregel: CSV för utbyte med människor, Parquet för allt annat som är större än några megabyte.

Formellt

Formaten och vad de är till för:

FormatTypBra påDåligt på
CSVrad, textläsbart, går överalltingen typning, stort, långsamt
JSON/JSONLrad, textnästlade strukturer, strömningännu större, långsamt
Parquetkolumn, binäranalys, komprimering, schemainte människoläsbart
Arrowkolumn, i minnetnollkopiering mellan verktyginte ett lagringsformat
Delta / IcebergParquet + transaktionsloggversionering, ACID, tidsresamer infrastruktur

Arrow är inte ett filformat utan en minnesrepresentation. Poängen är att pandas, Polars, DuckDB och Spark kan dela samma minnesblock utan att serialisera — det tar bort en av de största kostnaderna i dataarbete.

Parquets tre lager av smarthet:

MekanismVad den gör
Kolumnpruningläser bara efterfrågade kolumner
Radgruppsstatistikmin/max per block → hoppa över block som inte kan matcha
Dictionary encodingupprepade strängar lagras som heltal + ordlista

Den andra kallas predicate pushdown och är skälet till att en filtrerad fråga mot en stor Parquet-fil ofta går på bråkdelen av tiden.

Partitionering delar datan i kataloger efter en kolumn:

data/ar=2026/manad=09/dag=20/del-0.parquet

En fråga om september 2026 rör då bara de filerna. Men partitionera inte för fint — tusentals små filer gör allt långsammare (många öppningar, dålig komprimering). Riktmärket är filer på 128 MB–1 GB.

Komprimering:

KodekStorlekHastighet
snappystörresnabbast — standardvalet
zstdmindrenästan lika snabb; ofta bäst i dag
gzipminstlångsam

Kod

import numpy as np, pandas as pd, time
from pathlib import Path

rng = np.random.default_rng(0)
n = 500_000
df = pd.DataFrame({
    "id": np.arange(n),
    "stad": rng.choice(["Malmö", "Lund", "Göteborg", "Umeå"], n),   # få unika → dictionary
    "alder": rng.integers(15, 80, n),
    "poang": rng.normal(70, 12, n).round(2),
    "datum": pd.to_datetime("2026-01-01") + pd.to_timedelta(rng.integers(0, 365, n), "D"),
})

ut = Path("/tmp/format"); ut.mkdir(exist_ok=True)
df.to_csv(ut / "d.csv", index=False)
df.to_parquet(ut / "d.snappy.parquet", compression="snappy", index=False)
df.to_parquet(ut / "d.zstd.parquet", compression="zstd", index=False)

for f in sorted(ut.glob("d.*")):
    print(f"{f.name:<20} {f.stat().st_size / 1024**2:>7.2f} MB")

def tid(fn):
    t = time.perf_counter(); fn(); return round((time.perf_counter() - t) * 1000)

print("läs allt, csv    ", tid(lambda: pd.read_csv(ut / "d.csv")), "ms")
print("läs allt, parquet", tid(lambda: pd.read_parquet(ut / "d.zstd.parquet")), "ms")
print("läs EN kolumn    ",
      tid(lambda: pd.read_parquet(ut / "d.zstd.parquet", columns=["alder"])), "ms")
#  ↑ den sista är dramatiskt snabbare: bara en kolumn läses från disk

# Partitionering
df["ar"] = df["datum"].dt.year
df["manad"] = df["datum"].dt.month
df.to_parquet(ut / "part", partition_cols=["ar", "manad"], index=False)

# En fråga om en månad rör bara den katalogen
del = pd.read_parquet(ut / "part", filters=[("manad", "==", 9)])
print(len(del), "rader i september")

# Predicate pushdown med radgruppsstatistik
hog = pd.read_parquet(ut / "d.zstd.parquet", filters=[("poang", ">", 95)])
print(len(hog), "rader med poäng > 95 — block som inte kan matcha hoppas över")

Kör koden själv: skillnaden mellan «läs allt» och «läs en kolumn» är den enskilt tydligaste demonstrationen av varför kolumnlagring finns.

Behärskning innebär

  • Väljer format efter användning
  • Förklarar kolumnlagringens fördelar
  • Partitionerar och komprimerar medvetet

Logga in för att göra övningarna och bygga upp din behärskning.

Källor

Alla källor och licenser