
# Mehanizmi

## Izbira mehanizma

Vsi trije mehanizmi shranjujejo isto stvar: ID UUIDv7 in polje podatkov, vendar jo na disku razporedijo različno. Ta razporeditev določa, za kaj je posamezen mehanizem primeren:

|            | DocStore | SegmentedLog | Queue |
| ---------- | -------- | ------------ | ----- |
| Na disku    | ena datoteka JSONC za vsak zapis, razdeljena po zadnjih znakih UUID | segmenti, v katere se samo dodaja, z vrsticami JSON, pred katerimi sta dolžina in kontrolna vsota, ter manifest | en dnevnik dogodkov, v katerega se samo dodaja |
| Pisanje    | atomska zamenjava datoteke za vsak zapis | dodajanje s kontrolno vsoto; prepisi in brisanja dodajo nove vnose, prostor se sprosti s `compact()` | dodajanje s kontrolno vsoto za vsak dogodek čakalne vrste |
| Branje    | `get()` po ID-ju, poizvedbe z indeksi | pregledi s kazalcem in po časovnem razponu, `get()` prek indeksa stanja | `claim()` po vrstnem redu dodajanja |
| Najprimernejša uporaba | branje posameznih zapisov in iskanje z indeksi pri kateri koli velikosti zbirke | pretoki z veliko dodajanji in približno do 1M zapisov | trajno razpošiljanje opravil med procesi |
| Šibka točka  | pregledi neindeksiranih polj pri več kot približno 10k zapisih | pogosti prepisi posameznih zapisov (stare različice zasedajo prostor do strnjevanja) | ni splošna shramba zapisov; vsebina se po dokončanju opravila zavrže |

`DocStore` in `SegmentedLog` uporabljata isti tekoči [poizvedbe API](https://storh.inline0.dev/sl/docs/query-builder): ista veriga `where()` se izvaja v obeh mehanizmih, razlikuje pa se samo izvedba. `DocStore` lahko odgovori na podlagi sekundarnih indeksov, medtem ko `SegmentedLog` filtrira pri pregledu segmentov.
`Queue` temelji na prevzemanju, zato po njem ni mogoče poizvedovati: delavci `claim()` naslednje opravilo, namesto da bi opravila filtrirali.

Praktična pravila:

- Zapisi, ki jih posamično pridobivate, posodabljate ali brišete ter poizvedujete po poljih:
  `DocStore`. Dodajte [indekse](https://storh.inline0.dev/sl/docs/indexes) za polja, po katerih filtrirate.
- Dogodki ali različice, ki jih večinoma dodajate ter berete nazaj po vrstnem redu ali časovnem oknu: `SegmentedLog`.
- Opravila, ki jih producenti posredujejo procesom delavcev: `Queue`. Prevzemi in dokončanja so varni pri več procesih, metode `claimMany()`/`completeMany()` pa porazdelijo stroške zaklepanja in sinhronizacije z diskom.
- Združevanja, iskanje ali poročanje za katero koli zbirko: dodajte zrcalno kopijo [SQL](https://storh.inline0.dev/sl/docs/sql-mirror). Datoteke ostanejo kanonične; zrcalna kopija je projekcija, ki jo je mogoče znova zgraditi, v SQLite ali MySQL.

Mehanizmi se dopolnjujejo: delijo si samo korenski imenik, zato lahko aplikacija v istem korenu shrambe hrani vsebino v `DocStore`, revizijsko sled v `SegmentedLog` in opravila v ozadju v `Queue`.

## DocStore

`DocStore` je namenjen branju posameznih zapisov in preprostim pregledom trajnih zapisov. Za vsak zapis zapiše eno datoteko JSONC:

```php
$store = new Storh\DocStore($root, 'articles');
$record = $store->put(['slug' => 'hello', 'published' => true]);
$same = $store->get($record->id());
```

Datoteke zapisov so razdeljene po zadnjih znakih UUID, tako da se UUIDv7 obremenitve z zapisi porazdelijo med imenike:

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

## SegmentedLog

`SegmentedLog` je namenjen zapisom, ki jih pogosto dodajamo, branju s kazalcem in branju časovnih razponov. Zapisi se shranijo v aktivni segment, po ena vrstica za vsak zapis v obliki `<byte-length>\t<xxh32>\t<json>` (glejte [Atomičnost](https://storh.inline0.dev/sl/docs/atomicity)). Datoteke segmentov imajo pripono `.ndjson`, vendar zaradi predpone z dolžino in kontrolno vsoto običajna orodja NDJSON ne morejo brati njihove vsebine; zapise preberite nazaj z `stream()` ali z gradnikom poizvedb. Polni segmenti se zamenjajo z zapečatenimi segmenti z redkimi indeksi.

```php
$events = new Storh\SegmentedLog($root, 'events');
$events->put(['type' => 'user.created']);
$events->compact();
```

## Queue

`Queue` je namenjen trajnim čakalnim vrstam z visoko prepustnostjo. Dodajanje, prevzem, dokončanje, ponovno uvrščanje in čiščenje zapisujejo dogodke, ki se samo dodajajo, v `queue.log`; ob odprtju čakalne vrste se njeno stanje ponovno predvaja v pomnilnik. Paketne metode porazdelijo stroške zaklepanja in izpiranja med več opravil. Dnevnik se veča z vsakim dogodkom; `stats()` poroča o trenutni velikosti v bajtih.

```php
$queue = new Storh\Queue($root, 'mail');
$queue->enqueue(['to' => 'person@example.test']);
$job = $queue->claim();
```

## Trajnost in sočasnost

storh objavlja zapise datotek z atomsko preimenovanjem v ciljnem imeniku. Ob ponovnem odprtju in `repair()` se odstranijo opuščene začasne datoteke, katerih lastniški proces ne deluje več, aktivne začasne datoteke zapisovalcev pa ostanejo nedotaknjene. Oznake zapisovalcev omogočajo stalnočasovno preverjanje, zato se zbirka ob odprtju pregleda samo po sesutju.

`SegmentedLog` in `Queue` zapisujeta vrstice dnevnika z zaščito dolžine in kontrolne vsote. Ob ponovnem odprtju ali popravilu se odtrgani zaključki odrežejo do zadnjega potrjenega dogodka, stanje v pomnilniku pa se znova zgradi iz trajne vsebine dnevnika. Zapisi v dnevnik in čakalno vrsto so serializirani z zaklepi datotečnega sistema.

Spremembe `DocStore` se serializirajo z zaklepom za pisanje na ravni zbirke, zato se datoteke zapisov in sekundarni indeksi posodobijo kot ena meja doslednosti med procesi. Sočasno zapisovanje različnih ID-jev zapisov je varno ob enakih predpostavkah za atomsko preimenovanje datotek. Pri sočasnih zapisih istega ID-ja zmaga zadnje preimenovanje.

storh izprazni medpomnilnike in sinhronizira ročice datotek pred dokončanjem preimenovanja ali dodajanja ter poskuša sinhronizirati nadrejene imenike po atomskih preimenovanjih. Trajnost ob izpadu napajanja je še vedno odvisna od tega, ali osnovni datotečni sistem in možnosti priklopa upoštevajo te sinhronizacije.
