
# Sharding

`DocStore` skriver én fil pr. post. Uden sharding ville en stor samling
samle alle filer i én mappe, og mappehandlinger bliver langsommere, når
antallet af poster vokser. storh opdeler derfor postfiler i buckets efter postens id med to tegn:

```text
pages/data/2d/018bcfe5-6800-7000-8000-2d5dba6274da.jsonc
```

Bucketen tages fra starten af det sidste segment i UUID (tegn 24
og 25), som består af tilfældig entropi. To hexadecimale tegn giver 256 buckets, så en
samling med 100k poster har i gennemsnit under 400 filer pr. mappe.

## Hvorfor halen og ikke hovedet

UUIDv7-id'er begynder med et tidsstempel på 48 bit i millisekunder, så id'er, der oprettes tæt
på hinanden, deler de første tegn. Sharding efter præfiks ville sende hver
skrivebølge til den samme overbelastede mappe og kun skifte bucket, som tiden går.
Bytes i halen er tilfældige pr. post, så skrivninger fordeles jævnt fra den
første post.

Sharding er usynligt for læsninger og forespørgsler: `get()` beregner bucketen ud fra
id'et, og `stream()` returnerer poster sorteret efter id på tværs af alle buckets, så
markørbaseret sideinddeling fungerer, som om samlingen var én ordnet liste.

## I stor skala

Antallet af buckets vokser aldrig: to hexadecimale tegn giver 256 mapper uanset
samlingens størrelse. Det, der vokser, er antallet af filer pr. bucket:

| Poster | Filer pr. bucket |
| ------- | ---------------- |
| 10k     | ~40              |
| 100k    | ~390             |
| 1M      | ~3,900           |
| 10M     | ~39,000          |

Moderne filsystemer indekserer mappeposter, så nogle få tusinde filer pr.
bucket holder direkte opslag hurtige; 1M poster fungerer fint med `get()` og
indekserede forespørgsler. Åbning af et lager tager konstant tid uanset samlingens
størrelse: oprydningen af forældreløse midlertidige filer kører kun, når en markør for en død skriveproces viser,
at en tidligere proces gik ned midt i en skrivning. Det, der vokser lineært i stor skala, er
operationer på hele samlingen: scanninger uden indeks, `reindex()` og `verify()`
gennemgår hver postfil, så betragt dem som batchjob. Når en samling når omkring 10M
poster, begynder antallet pr. bucket og det samlede inodeforbrug at
have betydning: opdel samlingerne, eller brug den segmenterede log til data, der kræver scanninger,
som holder filantallet lavt fra starten.

## De andre motorer

`SegmentedLog` og `Queue` bruger ikke sharding, fordi de ikke opretter én fil
pr. post. Loggen føjer data til segmentfiler under `segments/`, begrænset af
`max_segment_bytes` (1 MiB som standard), og køen føjer hændelser til én
`queue.log`. Filantallet forbliver lavt fra starten.
