
# Partiționare

`DocStore` scrie câte un fișier pentru fiecare înregistrare. Fără partiționare, o colecție mare ar acumula toate fișierele într-un singur director, iar operațiile asupra directorului s-ar degrada pe măsură ce crește numărul de intrări. Prin urmare, storh distribuie fișierele în bucket-uri, folosind două caractere din id-ul înregistrării:

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

Bucket-ul este determinat de începutul grupului final al id-ului UUID (caracterele 24 și 25), care conține entropie aleatorie. Două caractere hexazecimale oferă 256 bucket-uri, astfel că o colecție de 100k de înregistrări are în medie sub 400 fișiere per director.

## De ce ultimele caractere, nu primele

Id-urile UUIDv7 încep cu un marcaj temporal pe 48 de biți, exprimat în milisecunde, așa că id-urile create în momente apropiate au aceleași caractere inițiale. Partiționarea după prefix ar trimite fiecare val de scrieri în același director suprasolicitat și ar schimba bucket-urile doar odată cu trecerea timpului. Octeții de la final sunt aleatorii pentru fiecare înregistrare, astfel că scrierile se distribuie uniform încă de la prima înregistrare.

Partiționarea este invizibilă pentru citiri și interogări: `get()` calculează bucket-ul pornind de la id, iar `stream()` returnează înregistrările sortate după id în toate bucket-urile, astfel că paginarea cu cursoare se comportă ca și cum colecția ar fi o singură listă ordonată.

## La scară mare

Numărul de bucket-uri nu crește niciodată: două caractere hexazecimale înseamnă 256 de directoare, indiferent de dimensiunea colecției. Crește numărul de fișiere din fiecare bucket:

| Înregistrări | Fișiere per bucket |
| ------- | ---------------- |
| 10k     | ~40              |
| 100k    | ~390             |
| 1M      | ~3,900           |
| 10M     | ~39,000          |

Sistemele de fișiere moderne indexează intrările din directoare, astfel că păstrarea a câteva mii de fișiere per bucket menține rapiditatea căutărilor directe; 1M de înregistrări este o dimensiune confortabilă pentru `get()` și interogări indexate. Deschiderea unei stocări durează constant, indiferent de dimensiunea colecției: curățarea fișierelor temporare orfane are loc doar când un marcaj de scriere rămas de la un proces oprit indică faptul că procesul anterior s-a blocat în timpul scrierii. Operațiile pe întreaga colecție care cresc liniar la scară mare sunt scanările fără index, `reindex()` și `verify()`, fiecare parcurgând toate fișierele înregistrărilor, așa că tratați-le ca sarcini în lot. Peste aproximativ 10M de înregistrări per colecție, încep să conteze numărul de fișiere per bucket și utilizarea totală a inodurilor: împărțiți colecțiile sau folosiți jurnalul segmentat pentru datele potrivite scanărilor, care menține numărul de fișiere redus prin construcție.

## Celelalte motoare

`SegmentedLog` și `Queue` nu folosesc partiționarea deoarece nu creează câte un fișier per înregistrare. Jurnalul adaugă date în fișiere segmentate din `segments/`, limitate de `max_segment_bytes` (1 MiB în mod implicit), iar coada adaugă evenimente într-un singur `queue.log`. Prin construcție, numărul de fișiere rămâne redus.
