
# Рушії

## Вибір рушія

Усі три рушії зберігають одне й те саме: ідентифікатор UUIDv7 та масив даних,
але записують їх на диск по-різному. Від цього залежить, для чого найкраще підходить кожен рушій:

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

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

Практичні правила:

- Записи, які ви отримуєте, оновлюєте або видаляєте окремо та шукаєте за полем:
  `DocStore`. Додавайте [індекси](https://storh.inline0.dev/uk/docs/indexes) для полів, за якими виконуєте фільтрацію.
- Події або версії, які здебільшого додаєте й читаєте в тому самому порядку або за часовим
  інтервалом: `SegmentedLog`.
- Завдання, які передаються від виробників процесам-обробникам: `Queue`. Отримання та
  завершення завдань безпечні для кількох процесів, а `claimMany()`/`completeMany()`
  дають змогу розподілити витрати на блокування та fsync між багатьма завданнями.
- Об’єднання, пошук або звітування за всією колекцією: додайте
  дзеркальну копію [SQL](https://storh.inline0.dev/uk/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/uk/docs/atomicity)). Файли
сегментів мають розширення `.ndjson`, але через префікси довжини й контрольної суми звичайні інструменти NDJSON не можуть їх читати; читайте записи за допомогою `stream()` або
конструктора запитів. Заповнені сегменти стають закритими сегментами з розрідженими індексами.

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

## Черга

`Queue` призначений для довговічних черг завдань із високою пропускною здатністю. Додавання в чергу, отримання, завершення,
повторне додавання та очищення записують події лише для додавання в `queue.log`; під час відкриття черги її стан
відтворюється в пам’яті. Пакетні методи розподіляють витрати на блокування та скидання між багатьма завданнями. Журнал зростає з кожною подією; `stats()`
повідомляє його поточний розмір у байтах.

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

## Довговічність і паралельність

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

`SegmentedLog` і `Queue` записують рядки журналу, захищені довжиною та контрольною сумою. Під час повторного відкриття
або виправлення пошкоджені кінцеві дані обрізаються до останньої зафіксованої події, а стан у пам’яті
перебудовується зі вмісту довговічного журналу. Записи в журнал і чергу серіалізуються за допомогою блокувань файлової системи.

Операції `DocStore` серіалізуються блокуванням запису на рівні колекції, тому
файли записів і вторинні індекси оновлюються як єдина межа узгодженості між
процесами. Паралельне записування різних ідентифікаторів записів безпечне за тих самих
умов атомарного перейменування у файловій системі. У разі паралельного запису з тим самим ідентифікатором перемагає останнє перейменування.

storh скидає буфери та виконує fsync для дескрипторів файлів перед перейменуванням або завершенням додавання,
а після атомарного перейменування намагається виконати fsync батьківських каталогів. Стійкість до втрати живлення
все одно залежить від того, чи файлову систему та параметри монтування налаштовано на виконання цих операцій синхронізації.
