Sharding
DocStore schrijft één bestand per record. Zonder sharding zou een grote collectie alle bestanden in één map opstapelen, waardoor mapbewerkingen trager worden naarmate het aantal items groeit. storh verdeelt recordbestanden daarom op basis van twee tekens van de record-ID over buckets:
pages/data/2d/018bcfe5-6800-7000-8000-2d5dba6274da.jsonc
De bucket wordt bepaald aan de hand van het begin van de laatste groep van UUID (tekens 24 en 25), die willekeurige entropie bevat. Twee hexadecimale tekens leveren 256 buckets op, dus een collectie met 100k records heeft gemiddeld minder dan 400 bestanden per map.
Waarom de staart en niet de kop
UUIDv7-ID's beginnen met een tijdstempel van 48 bits in milliseconden, dus ID's die kort na elkaar worden aangemaakt, delen hun eerste tekens. Sharding op basis van het voorvoegsel zou elke schrijfuitbarsting naar dezelfde drukke map sturen en de buckets pas laten rouleren naarmate de tijd verstrijkt. De laatste bytes zijn per record willekeurig, dus de schrijfbewerkingen worden vanaf het eerste record gelijkmatig verdeeld.
Sharding is onzichtbaar voor leesbewerkingen en queries: get() berekent de bucket op basis van de ID, en stream() retourneert records in alle buckets gesorteerd op ID, zodat cursorpaginering zich gedraagt alsof de collectie één geordende lijst is.
Op schaal
Het aantal buckets groeit nooit: twee hexadecimale tekens staan altijd voor 256 mappen, ongeacht de grootte van de collectie. Wat wel groeit, is het aantal bestanden per bucket:
| Records | Bestanden per bucket |
|---|---|
| 10k | ~40 |
| 100k | ~390 |
| 1M | ~3,900 |
| 10M | ~39,000 |
Moderne bestandssystemen indexeren mapitems, dus een paar duizend bestanden per bucket houdt opzoeken op ID snel; 1M records is goed te doen voor get() en geïndexeerde queries. Een store openen kost altijd evenveel tijd, ongeacht de grootte van de collectie: verweesde tijdelijke bestanden worden alleen opgeruimd als een achtergebleven schrijversmarkering aangeeft dat een eerder proces tijdens het schrijven is gecrasht. Bij grootschalig gebruik groeien volledige-collectiebewerkingen wel lineair: niet-geïndexeerde scans, reindex() en verify() doorlopen elk alle recordbestanden, dus behandel ze als batchtaken. Bij meer dan ongeveer 10M records per collectie gaan het aantal records per bucket en het totale inodegebruik meetellen: splits collecties op of gebruik het gesegmenteerde log voor data die in scans wordt verwerkt; dat houdt het aantal bestanden van nature laag.
De andere engines
SegmentedLog en Queue gebruiken geen sharding, omdat ze niet één bestand per record aanmaken. Het log voegt gegevens toe aan segmentbestanden onder segments/, met een limiet van max_segment_bytes (standaard 1 MiB), en de wachtrij voegt events toe aan één queue.log. Zo blijft het aantal bestanden van nature laag.