
# Enginy

## Výběr enginu

Všechny tři enginy ukládají totéž: ID UUIDv7 a pole dat, ale na disku je organizují různě. Od toho se odvíjí, k čemu se každý engine hodí:

|            | DocStore | SegmentedLog | Queue |
| ---------- | -------- | ------------ | ----- |
| Na disku    | jeden soubor JSONC na záznam, rozdělený podle koncových znaků UUID | segmenty s přidáváním na konec, řádky JSON uvozenými délkou a kontrolním součtem, plus manifest | jeden log událostí pouze s přidáváním na konec |
| Zápis    | atomická náhrada souboru pro každý záznam | zápisy s kontrolním součtem; přepisy a mazání přidávají nové položky, prostor se uvolní kompakcí `compact()` | zápis s kontrolním součtem pro každou událost fronty |
| Čtení    | `get()` podle ID, dotazy s indexem | procházení kurzorem a časovým rozsahem, `get()` přes index stavu | `claim()` v pořadí zařazení do fronty |
| Nejvhodnější použití | jednotlivá čtení a vyhledávání podle indexu v kolekcích libovolné velikosti | proudy s převahou přidávání, přibližně do 1M záznamů | trvalé předávání práce mezi procesy |
| Slabina  | skenování polí bez indexu u kolekcí s více než přibližně 10k záznamy | časté přepisování jednotlivých záznamů (staré verze zabírají místo až do kompakce) | nejde o obecné úložiště záznamů; data se po dokončení úlohy zahodí |

`DocStore` a `SegmentedLog` používají stejné plynulé rozhraní [dotazů API](https://storh.inline0.dev/cs/docs/query-builder): stejný řetězec volání `where()` funguje v obou enginech, liší se jen provedení. `DocStore` může odpověď získat ze sekundárních indexů, zatímco `SegmentedLog` filtruje při procházení segmentů. `Queue` pracuje na základě nárokování, dotazy nepodporuje: pracovníci `claim()` další úlohu místo filtrování úloh.

Orientační pravidla:

- Záznamy, které jednotlivě načítáte, aktualizujete nebo mažete a vyhledáváte podle polí: `DocStore`. Přidejte [indexy](https://storh.inline0.dev/cs/docs/indexes) pro pole, podle kterých filtrujete.
- Události nebo verze, které převážně přidáváte a čtete zpět v pořadí nebo v časovém rozmezí: `SegmentedLog`.
- Úlohy předávané od producentů pracovním procesům: `Queue`. Nárokování a dokončování jsou bezpečné pro více procesů a `claimMany()`/`completeMany()` rozkládají náklady na zámky a fsync.
- Spojování, vyhledávání nebo vytváření sestav nad libovolnou kolekcí: přidejte [SQL zrcadlo](https://storh.inline0.dev/cs/docs/sql-mirror). Soubory zůstávají kanonickým zdrojem; zrcadlo je projekce, kterou lze znovu vytvořit v SQLite nebo MySQL.

Enginy lze kombinovat: sdílejí pouze kořenový adresář, takže jedna aplikace může mít obsah v `DocStore`, protokol auditu v `SegmentedLog` a práci na pozadí v `Queue` pod stejným kořenem úložiště.

## DocStore

`DocStore` slouží k jednotlivému čtení a jednoduchému procházení trvalých záznamů. Pro každý záznam zapisuje jeden soubor JSONC:

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

Soubory záznamů jsou rozdělené do podadresářů podle posledních znaků UUID, takže se nárazové zápisy UUIDv7 rozloží mezi adresáře:

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

## SegmentedLog

`SegmentedLog` se hodí pro záznamy, ke kterým se často přidává, čtení pomocí kurzoru a čtení podle časového rozsahu. Záznamy se zapisují do aktivního segmentu, jeden na řádek ve formátu `<byte-length>\t<xxh32>\t<json>` (viz [Atomicita](https://storh.inline0.dev/cs/docs/atomicity)). Soubory segmentů mají příponu `.ndjson`, ale kvůli prefixu s délkou a kontrolním součtem je běžné nástroje NDJSON nedokážou číst; záznamy načtěte pomocí `stream()` nebo tvůrce dotazů. Po zaplnění se segmenty uzavřou a opatří řídkými indexy.

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

## Queue

`Queue` slouží pro trvalé fronty úloh s vysokou propustností. Zařazení, nárokování, dokončení, vrácení do fronty a vyprázdnění zapisují události pouze na konec souboru `queue.log`; stav fronty se při jejím otevření načte přehráním událostí do paměti. Hromadné metody rozkládají náklady na zámky a zápis na disk mezi mnoho úloh. Log roste s každou událostí; `stats()` uvádí jeho aktuální velikost v bajtech.

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

## Trvalost a souběžnost

storh zveřejňuje zápisy souborů pomocí atomického přejmenování v cílovém adresáři. Při opětovném otevření a `repair()` se odstraňují opuštěné dočasné soubory, jejichž vlastnický proces už neběží, zatímco dočasné soubory aktivních zapisujících procesů zůstávají nedotčené. Značky zapisujících umožňují tuto kontrolu provést v konstantním čase, takže se při otevření úložiště prochází kolekce jen po pádu procesu.

`SegmentedLog` a `Queue` zapisují řádky logu chráněné délkou a kontrolním součtem. Při opětovném otevření nebo opravě se poškozený konec zkrátí na poslední potvrzenou událost a stav v paměti se znovu vytvoří z trvalého obsahu logu. Zápisy do logu a fronty se serializují pomocí zámků souborového systému.

Změny v `DocStore` se serializují pomocí zámku pro zápis na úrovni kolekce, takže se soubory záznamů a sekundární indexy aktualizují společně v rámci jedné hranice konzistence napříč procesy. Souběžné zápisy různých ID záznamů jsou bezpečné za stejných předpokladů o atomickém přejmenování v souborovém systému. Při souběžných zápisech stejného ID platí poslední přejmenování.

storh před dokončením přejmenování nebo připojení zapíše vyrovnávací paměti a zavolá fsync pro popisovače souborů; po atomickém přejmenování se také pokusí zavolat fsync pro nadřazené adresáře. Trvalost při výpadku napájení stále závisí na tom, zda podkladový souborový systém a možnosti připojení tato synchronizační volání respektují.
