
# Silniki

## Wybór silnika

Wszystkie trzy silniki przechowują to samo: identyfikator UUIDv7 i tablicę danych, ale zapisują je na dysku w różny sposób. Od tego układu zależy, do czego najlepiej nadaje się każdy silnik:

|            | DocStore | SegmentedLog | Queue |
| ---------- | -------- | ------------ | ----- |
| Na dysku    | jeden plik JSONC na rekord, partycjonowany według końcowych znaków UUID | segmenty tylko do dopisywania z wierszami JSON poprzedzonymi długością i sumą kontrolną oraz manifest | jeden dziennik zdarzeń tylko do dopisywania |
| Zapis      | atomowa podmiana pliku dla każdego rekordu | dopisywanie z sumą kontrolną; nadpisania i usunięcia dopisują nowe wpisy, odzyskiwane przez `compact()` | dopisywanie z sumą kontrolną dla każdego zdarzenia kolejki |
| Odczyt     | `get()` po ID, zapytania indeksowane | skanowanie kursorem i zakresu czasu, `get()` przez indeks stanu | `claim()` w kolejności dodania do kolejki |
| Najlepsze zastosowanie | odczyty pojedynczych rekordów i wyszukiwanie indeksowane przy dowolnym rozmiarze kolekcji | strumienie z dużą liczbą dopisań, do około 1 mln rekordów | trwałe przekazywanie pracy między procesami |
| Słaba strona | skanowanie pól bez indeksu przy ponad około 10 tys. rekordów | częste przepisywanie pojedynczych rekordów (stare wersje zajmują miejsce do czasu kompaktowania) | nie jest ogólnym magazynem rekordów; ładunki są usuwane po zakończeniu zadania |

`DocStore` i `SegmentedLog` współdzielą ten sam płynny mechanizm [zapytania API](https://storh.inline0.dev/pl/docs/query-builder): ten sam łańcuch `where()` działa w obu silnikach, różni się tylko wykonanie. `DocStore` może korzystać z indeksów pomocniczych, a `SegmentedLog` filtruje podczas skanowania segmentów.
`Queue` opiera się na przejmowaniu zadań, a nie na zapytaniach: procesy robocze `claim()` następne zadanie zamiast filtrować zadania.

Praktyczne wskazówki:

- Rekordy pobierane, aktualizowane lub usuwane pojedynczo i wyszukiwane według pola:
  `DocStore`. Dodaj [indeksy](https://storh.inline0.dev/pl/docs/indexes) dla filtrowanych pól.
- Zdarzenia lub wersje, które głównie dopisujesz, a potem odczytujesz w kolejności albo według przedziału czasu: `SegmentedLog`.
- Zadania przekazywane przez producentów procesom roboczym: `Queue`. Przejmowanie zadań i ich kończenie są bezpieczne w wielu procesach, a `claimMany()`/`completeMany()` rozkładają koszt blokad i fsync na wiele operacji.
- Łączenia, wyszukiwanie lub raportowanie na dowolnej kolekcji: dodaj lustrzaną kopię [SQL](https://storh.inline0.dev/pl/docs/sql-mirror). Pliki pozostają kanonicznym źródłem danych; kopia jest projekcją, którą można odbudować w SQLite lub MySQL.

Silniki można łączyć: współdzielą tylko katalog główny, więc jedna aplikacja może przechowywać treści w `DocStore`, ścieżkę audytu w `SegmentedLog`, a pracę w tle w `Queue`, korzystając z tego samego katalogu głównego magazynu.

## DocStore

`DocStore` służy do odczytów pojedynczych rekordów i prostego skanowania trwałych danych. Zapisuje jeden plik JSONC na rekord:

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

Pliki rekordów są partycjonowane według ostatnich znaków UUID, dzięki czemu serie zapisów UUIDv7 rozkładają się między katalogi:

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

## SegmentedLog

`SegmentedLog` służy do rekordów intensywnie dopisywanych oraz odczytów kursorem i według zakresu czasu.
Rekordy są zapisywane w aktywnym segmencie, po jednym wierszu na rekord, w formacie `<byte-length>\t<xxh32>\t<json>` (zob. [Atomowość](https://storh.inline0.dev/pl/docs/atomicity)). Pliki segmentów mają rozszerzenie `.ndjson`, ale prefiks z długością i sumą kontrolną sprawia, że zwykłe narzędzia NDJSON nie mogą ich odczytać; odczytuj rekordy za pomocą `stream()` lub kreatora zapytań. Pełne segmenty przechodzą w zamknięte segmenty z rzadkimi indeksami.

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

## Queue

`Queue` służy do trwałych kolejek zadań o wysokiej przepustowości. Operacje enqueue, claim, complete, requeue i purge zapisują zdarzenia tylko do dopisywania w `queue.log`; stan kolejki jest odtwarzany w pamięci przy jej otwarciu. Metody wsadowe rozkładają koszt blokad i opróżniania bufora na wiele zadań. Log rośnie wraz z każdym zdarzeniem; `stats()` podaje jego bieżący rozmiar w bajtach.

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

## Trwałość i współbieżność

storh publikuje zapisy plików przez atomową zmianę nazwy w katalogu docelowym. Ponowne otwarcie i `repair()` usuwają porzucone pliki tymczasowe, których proces właściciela już nie działa, pozostawiając pliki tymczasowe aktywnych zapisujących bez zmian. Znaczniki zapisującego sprawiają, że sprawdzenie ma stały koszt, więc przegląd kolekcji podczas otwierania magazynu odbywa się tylko po awarii.

`SegmentedLog` i `Queue` zapisują wiersze logu zabezpieczone długością i sumą kontrolną. Ponowne otwarcie lub naprawa obcina uszkodzoną końcówkę do ostatniego zatwierdzonego zdarzenia i odbudowuje stan w pamięci na podstawie trwałej zawartości logu. Zapisy logu i kolejki są serializowane za pomocą blokad systemu plików.

Zmiany w `DocStore` są serializowane za pomocą blokady zapisu na poziomie kolekcji, dzięki czemu pliki rekordów i indeksy pomocnicze aktualizują się jako jedna granica spójności między procesami. Równoczesne zapisy różnych identyfikatorów rekordów są bezpieczne przy tych samych założeniach dotyczących atomowej zmiany nazwy w systemie plików. Przy równoczesnych zapisach tego samego ID wygrywa ostatnia zmiana nazwy.

storh opróżnia bufory i wykonuje fsync na uchwytach plików przed zmianą nazwy lub zakończeniem dopisywania, a po atomowych zmianach nazw próbuje wykonać fsync na katalogach nadrzędnych. Trwałość przy utracie zasilania nadal zależy od tego, czy bazowy system plików i opcje montowania respektują te operacje synchronizacji.
