
# Dzinēji

## Dzinēja izvēle

Visi trīs dzinēji glabā vienu un to pašu: UUIDv7 ID un datu masīvu, taču diskā tie izkārto datus atšķirīgi. Šis izkārtojums nosaka, kam katrs dzinējs ir piemērots:

|            | DocStore | SegmentedLog | Queue |
| ---------- | -------- | ------------ | ----- |
| Diskā    | viens JSONC fails katram ierakstam, sadalīts mapēs pēc UUID astes | tikai papildināmi segmenti ar garuma un kontrolsummas prefiksu JSON rindām, kā arī manifests | viens tikai papildināms notikumu žurnāls |
| Rakstīšana    | atomiska faila aizstāšana katram ierakstam | papildinājumi ar kontrolsummu; pārrakstīšana un dzēšana pievieno jaunus ierakstus, kurus vēlāk atbrīvo `compact()` | katra rindas notikuma papildināšana ar kontrolsummu |
| Lasīšana    | `get()` pēc ID, indeksēti vaicājumi | kursora un laika diapazona skenēšana, `get()`, izmantojot stāvokļa indeksu | `claim()` ievietošanas secībā |
| Piemērotākais lietojums | atsevišķu ierakstu nolasīšana un indeksēti meklējumi jebkura lieluma kolekcijā | papildināšanai intensīvas plūsmas līdz aptuveni 1M ierakstu | noturīga uzdevumu nodošana starp procesiem |
| Ierobežojumi  | neindeksētu lauku skenēšana, ja ir vairāk nekā aptuveni 10k ierakstu | bieža atsevišķu ierakstu pārrakstīšana (vecās versijas aizņem vietu līdz saspiešanai) | nav vispārīga ierakstu krātuve; dati tiek dzēsti pēc uzdevuma pabeigšanas |

`DocStore` un `SegmentedLog` izmanto vienu un to pašu plūstošo [vaicājuma API](https://storh.inline0.dev/lv/docs/query-builder): viena un tā pati `where()` ķēde darbojas abos dzinējos, un atšķiras tikai izpilde. `DocStore` var atbildēt no sekundārajiem indeksiem, bet `SegmentedLog` filtrē, skenējot segmentus. `Queue` balstās uz pieprasījumiem, un tajā nav vaicājumu: darbinieki `claim()` nākamo uzdevumu, nevis filtrē uzdevumus.

Īkšķa likumi:

- Ieraksti, ko izgūstat, atjaunināt vai dzēšat atsevišķi un vaicājat pēc lauka:
  `DocStore`. Pievienojiet [indeksus](https://storh.inline0.dev/lv/docs/indexes) laukiem, pēc kuriem filtrējat.
- Notikumi vai versijas, ko galvenokārt pievienojat un lasāt secīgi vai noteiktā laika posmā: `SegmentedLog`.
- Uzdevumi, ko ražotāji nodod darbinieku procesiem: `Queue`. Pieprasījumi un pabeigšana ir droši vairākiem procesiem, bet `claimMany()`/`completeMany()` ļauj sadalīt slēdzeņu un fsync izmaksas.
- Savienojumi, meklēšana vai atskaites par jebkuru kolekciju: pievienojiet [SQL spoguli](https://storh.inline0.dev/lv/docs/sql-mirror). Faili paliek kanoniski; spogulis ir atjaunojama projekcija SQLite vai MySQL.

Dzinēji ir kombinējami: tiem kopīga ir tikai saknes direktorija, tāpēc viena lietotne vienā krātuves saknē var glabāt saturu `DocStore`, audita žurnālu `SegmentedLog` un fona uzdevumus `Queue`.

## DocStore

`DocStore` paredzēts atsevišķu noturīgu ierakstu nolasīšanai un vienkāršai skenēšanai. Tas katram ierakstam raksta atsevišķu JSONC failu:

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

Ierakstu faili tiek sadalīti mapēs pēc pēdējām UUID rakstzīmēm, lai UUIDv7 rakstīšanas slodze sadalītos pa direktorijām:

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

## SegmentedLog

`SegmentedLog` paredzēts ierakstiem, ko bieži papildina, kursora nolasīšanai un laika diapazonu nolasīšanai. Ieraksti tiek rakstīti aktīvajā segmentā — katrs ieraksts atsevišķā rindā formā `<byte-length>\t<xxh32>\t<json>` (skatiet [Atomiškums](https://storh.inline0.dev/lv/docs/atomicity)). Segmentu failiem ir paplašinājums `.ndjson`, taču garuma un kontrolsummas prefiksa dēļ parastie NDJSON rīki tos nevar nolasīt; izmantojiet `stream()` vai vaicājumu veidotāju, lai nolasītu ierakstus. Pilnie segmenti tiek noslēgti un aizstāti ar aizzīmogotiem segmentiem, kuriem ir reti indeksi.

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

## Rinda

`Queue` paredzēts augstas veiktspējas noturīgām darbu rindām. Ievietošana rindā, pieprasīšana, pabeigšana, atkārtota ievietošana un notīrīšana ieraksta tikai papildināmus notikumus `queue.log`; rindas stāvoklis tiek atskaņots atmiņā, to atverot. Pakešu metodes sadala slēdzeņu un iztukšošanas izmaksas starp daudziem uzdevumiem. Žurnāls pieaug ar katru notikumu; `stats()` ziņo par tā pašreizējo izmēru baitos.

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

## Izturība un vienlaicīga piekļuve

storh publicē failu ierakstus, veicot atomisku pārdēvēšanu mērķa direktorijā. Atkārtota atvēršana un `repair()` noņem pamestos pagaidu failus, kuru īpašnieka process vairs nedarbojas, bet atstāj neskartus aktīvo rakstītāju pagaidu failus. Rakstītāju marķieri ļauj šo pārbaudi veikt nemainīgā laikā, tāpēc pēc avārijas kolekcija tiek pārskatīta, atverot krātuvi.

`SegmentedLog` un `Queue` raksta žurnāla rindas, kuru garumu un kontrolsummu pārbauda. Atkārtoti atverot vai labojot, nepilnīgās beigu rindas tiek saīsinātas līdz pēdējam apstiprinātajam notikumam, un stāvoklis atmiņā tiek atjaunots no noturīgā žurnāla satura. Žurnāla un rindas ierakstīšana tiek serializēta ar failu sistēmas slēdzenēm.

`DocStore` izmaiņas tiek serializētas ar kolekcijas līmeņa rakstīšanas slēdzeni, tāpēc ierakstu faili un sekundārie indeksi tiek atjaunināti vienas saskaņotības robežas ietvaros vairākos procesos. Vienlaicīga rakstīšana atšķirīgos ierakstu ID ir droša, ja tiek ievēroti tie paši pieņēmumi par failu sistēmas atomisko pārdēvēšanu. Vienlaicīgas rakstīšanas gadījumā vienam ID uzvar pēdējā pārdēvēšana.

storh iztukšo un sinhronizē failu deskriptorus pirms pārdēvēšanas vai papildināšanas pabeigšanas un pēc atomiskas pārdēvēšanas mēģina sinhronizēt vecākdirektorijas. Izturība pret strāvas zudumu joprojām ir atkarīga no tā, vai pamatā esošā failu sistēma un montēšanas opcijas ievēro šīs sinhronizācijas.
