Particionálás
DocStore rekordonként egy fájlt ír. Particionálás nélkül egy nagy gyűjtemény összes fájlja egy könyvtárba kerülne, a könyvtárműveletek pedig lelassulnának, ahogy nő a bejegyzések száma. Ezért az storh a rekordfájlokat a rekordazonosító két karaktere alapján csoportosítja:
pages/data/2d/018bcfe5-6800-7000-8000-2d5dba6274da.jsonc
A könyvtár a UUID utolsó csoportjának elejéről származik (a 24. és 25. karakterből), amely véletlenszerű entrópiát hordoz. Két hexadecimális karakterrel 256 könyvtár képezhető, így egy 100 ezres gyűjteményben könyvtáranként átlagosan 400 fájl alatt marad a fájlok száma.
Miért a farok, és miért nem az eleje?
A UUIDv7 azonosítók 48 bites, milliszekundumos időbélyeggel kezdődnek, ezért a közel egymáshoz létrehozott azonosítók kezdő karakterei megegyeznek. Az előtag szerinti particionálás minden írási hullámot ugyanabba a túlterhelt könyvtárba irányítana, és csak az idő múlásával váltana könyvtárat. A záró bájtok rekordonként véletlenszerűek, így az írások már az első rekordtól egyenletesen oszlanak el.
A particionálás nem látható az olvasások és lekérdezések számára: az get() az azonosítóból számítja ki a könyvtárat, az stream() pedig az összes könyvtár rekordjait azonosító szerint rendezve adja vissza, így a kurzoros lapozás úgy működik, mintha a gyűjtemény egyetlen rendezett lista lenne.
Nagy méretekben
A könyvtárak száma nem nő: két hexadecimális karakterrel 256 könyvtár adódik bármilyen méretű gyűjteménynél. A könyvtárankénti fájlok száma viszont nő:
| Rekordok | Fájlok könyvtáranként |
|---|---|
| 10k | ~40 |
| 100k | ~390 |
| 1M | ~3,900 |
| 10M | ~39,000 |
A modern fájlrendszerek indexelik a könyvtárbejegyzéseket, ezért könyvtáranként néhány ezer fájl mellett gyorsak maradnak a közvetlen keresések; az 1M rekord kényelmesen kezelhető a get() és indexelt lekérdezések számára. Az adattár megnyitása állandó idejű a gyűjtemény méretétől függetlenül: az árva ideiglenes fájlok keresése csak akkor fut le, ha egy elhalt írójelző azt mutatja, hogy egy korábbi folyamat írás közben összeomlott. Nagy méretekben az egész gyűjteményre kiterjedő műveletek nőnek lineárisan: az index nélküli keresések, a reindex() és a verify() mindegyike végigjárja az összes rekordfájlt, ezért kötegelt feladatként kezelje őket. Gyűjteményenként nagyjából 10M rekord fölött a könyvtárankénti fájlszám és az összes inode mennyisége már számít: bontsa fel a gyűjteményeket, vagy használja a szegmentált naplót a keresésközpontú adatokhoz, amely kialakításánál fogva alacsonyan tartja a fájlok számát.
A többi motor
Az SegmentedLog és az Queue nem particionál, mert nem hoz létre rekordonként egy fájlt. A napló a szegmensfájlokhoz fűz hozzá az segments/ alatt, a max_segment_bytes korlátozza a méretüket (alapértelmezés szerint 1 MiB), a várólista pedig egyetlen queue.log fájlhoz fűzi hozzá az eseményeket. A fájlok száma eleve alacsony marad.