
# Движки

## Выбор движка

Все три движка хранят одни и те же данные: идентификатор UUIDv7 и массив данных, но размещают их на диске по-разному. От способа размещения зависят сильные стороны каждого движка:

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

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

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

- Записи, которые вы получаете, обновляете или удаляете по отдельности и запрашиваете по полю:
  `DocStore`. Добавьте [индексы](https://storh.inline0.dev/ru/docs/indexes) для полей, по которым выполняете фильтрацию.
- События или версии, которые вы в основном добавляете и затем читаете по порядку или в заданном временном окне:
  `SegmentedLog`.
- Задачи, передаваемые от производителей рабочим процессам: `Queue`. Захват и завершение задач безопасны при работе нескольких процессов, а `claimMany()`/`completeMany()` позволяют распределить затраты на блокировки и fsync.
- Объединения, поиск или отчеты по любой коллекции: добавьте зеркало [SQL](https://storh.inline0.dev/ru/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/ru/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 для родительских каталогов. Надежность хранения при отключении питания по-прежнему зависит от поддержки этих операций файловой системой и параметрами монтирования.
