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:
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.