
# Машини

## Избор на машина

И трите машини съхраняват едно и също: ID от UUIDv7 и масив с данни, но го разполагат по различен начин на диска. Това определя за какво е подходяща всяка машина:

|            | DocStore | SegmentedLog | Queue |
| ---------- | -------- | ------------ | ----- |
| На диска    | по един файл JSONC за запис, с разпределяне по опашните части на UUID | сегменти само за добавяне с редове във формат JSON, предшествани от дължината и контролната сума, плюс манифест | един журнал само за добавяне на събития |
| Записване    | атомарна подмяна на файл за всеки запис | добавяне с контролна сума; презаписванията и изтриванията добавят нови записи, а мястото се освобождава чрез `compact()` | добавяне с контролна сума за всяко събитие в опашката |
| Четене    | `get()` по ID, заявки с индекси | четене с курсор и сканиране по времеви диапазон, `get()` чрез индекса на състоянието | `claim()` в реда на добавяне в опашката |
| Подходящо за | директно четене и търсене по индекс при всякакъв размер на колекцията | потоци с интензивно добавяне до около 1M записа | надеждно разпределяне на задачи между процеси |
| Слабо място  | сканиране на полета без индекс при над около 10k записа | често пренаписване на отделни записи (старите версии заемат място до компактирането) | не е универсално хранилище на записи; полезните данни се изтриват след завършване на задачата |

`DocStore` и `SegmentedLog` използват еднакъв верижен интерфейс за [заявки API](https://storh.inline0.dev/bg/docs/query-builder): една и съща верига от `where()` работи и с двете машини; различава се само изпълнението. `DocStore` може да отговори чрез вторичните си индекси, а `SegmentedLog` филтрира при сканиране на сегментите. `Queue` се основава на резервиране, а не на заявки: работниците `claim()` следващата задача, вместо да филтрират задачите.

Практически насоки:

- Записи, които извличате, актуализирате или изтривате поотделно и търсите по поле:
  `DocStore`. Добавете [индекси](https://storh.inline0.dev/bg/docs/indexes) за полетата, по които филтрирате.
- Събития или версии, които предимно добавяте и четете обратно по ред или в определен времеви интервал: `SegmentedLog`.
- Задачи, предавани от производители към работни процеси: `Queue`. Заявяването и приключването са безопасни при множество процеси, а `claimMany()`/`completeMany()` разпределят разходите за заключване и fsync.
- Свързвания, търсене или отчети върху произволна колекция: добавете огледало [SQL](https://storh.inline0.dev/bg/docs/sql-mirror). Файловете остават канонични; огледалото е възстановима проекция в SQLite или MySQL.

Машините се комбинират: споделят само основната директория, така че едно приложение може да съхранява съдържание в `DocStore`, журнал за одит в `SegmentedLog` и фонови задачи в `Queue` под един и същ корен за съхранение.

## DocStore

`DocStore` е предназначен за директно четене и прости сканирания на трайни записи. Той записва по един файл JSONC за всеки запис:

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

Файловете със записи се разпределят по последните знаци на UUID, така че пиковите записи от UUIDv7 се разпределят в различни директории:

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

## SegmentedLog

`SegmentedLog` е предназначен за записи с интензивно добавяне, четене с курсор и четене по времеви диапазон. Записите се добавят към активния сегмент, по един ред на запис във формат `<byte-length>\t<xxh32>\t<json>` (вижте [Атомарност](https://storh.inline0.dev/bg/docs/atomicity)). Файловете на сегментите използват разширение `.ndjson`, но префиксът с дължината и контролната сума означава, че обикновените инструменти за NDJSON не могат да ги четат; използвайте `stream()` или конструктора на заявки, за да прочетете записите. Пълните сегменти се запечатват и получават разредени индекси.

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

## Опашка

`Queue` е предназначен за трайни опашки с висока пропускателна способност. Enqueue, claim, complete, requeue и purge записват събития само за добавяне в `queue.log`; състоянието на опашката се възпроизвежда в паметта при отваряне. Пакетните методи разпределят разходите за заключване и изчистване между много задачи. Журналът расте с всяко събитие; `stats()` показва текущия му размер в байтове.

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

## Трайност и едновременен достъп

storh публикува файловите записи чрез атомарно преименуване в целевата директория. При повторно отваряне и `repair()` се премахват изоставените временни файлове, чийто собствен процес е приключил, като временните файлове на активните записващи процеси се запазват. Маркерите на записващите процеси правят проверката с постоянно време, така че колекцията се сканира при отваряне на хранилището само след срив.

`SegmentedLog` и `Queue` записват редове в журнала със защитена дължина и контролна сума. При повторно отваряне или поправка прекъснатият край се отрязва до последното потвърдено събитие, а състоянието в паметта се възстановява от трайното съдържание на журнала. Записите в журнала и опашката се сериализират чрез заключвания на файловата система.

Промените в `DocStore` се сериализират със заключване за запис на ниво колекция, така че файловете със записи и вторичните индекси се актуализират като една граница на съгласуваност между процесите. Едновременните записи с различни ID-та са безопасни при същите допускания за атомарно преименуване във файловата система. При едновременни записи към едно и също ID печели последното преименуване.

storh изчиства буферите и извиква fsync за файловите дескриптори преди завършване на преименуване или добавяне и се опитва да извика fsync за родителските директории след атомарни преименувания. Устойчивостта при загуба на захранване все пак зависи от това дали файловата система и опциите за монтиране спазват тези синхронизации.
