Машини
Избор на машина
И трите машини съхраняват едно и също: ID от UUIDv7 и масив с данни, но го разполагат по различен начин на диска. Това определя за какво е подходяща всяка машина:
| DocStore | SegmentedLog | Queue | |
|---|---|---|---|
| На диска | по един файл JSONC за запис, с разпределяне по опашните части на UUID | сегменти само за добавяне с редове във формат JSON, предшествани от дължината и контролната сума, плюс манифест | един журнал само за добавяне на събития |
| Записване | атомарна подмяна на файл за всеки запис | добавяне с контролна сума; презаписванията и изтриванията добавят нови записи, а мястото се освобождава чрез compact() |
добавяне с контролна сума за всяко събитие в опашката |
| Четене | get() по ID, заявки с индекси |
четене с курсор и сканиране по времеви диапазон, get() чрез индекса на състоянието |
claim() в реда на добавяне в опашката |
| Подходящо за | директно четене и търсене по индекс при всякакъв размер на колекцията | потоци с интензивно добавяне до около 1M записа | надеждно разпределяне на задачи между процеси |
| Слабо място | сканиране на полета без индекс при над около 10k записа | често пренаписване на отделни записи (старите версии заемат място до компактирането) | не е универсално хранилище на записи; полезните данни се изтриват след завършване на задачата |
DocStore и SegmentedLog използват еднакъв верижен интерфейс за заявки API: една и съща верига от where() работи и с двете машини; различава се само изпълнението. DocStore може да отговори чрез вторичните си индекси, а SegmentedLog филтрира при сканиране на сегментите. Queue се основава на резервиране, а не на заявки: работниците claim() следващата задача, вместо да филтрират задачите.
Практически насоки:
- Записи, които извличате, актуализирате или изтривате поотделно и търсите по поле:
DocStore. Добавете индекси за полетата, по които филтрирате. - Събития или версии, които предимно добавяте и четете обратно по ред или в определен времеви интервал:
SegmentedLog. - Задачи, предавани от производители към работни процеси:
Queue. Заявяването и приключването са безопасни при множество процеси, аclaimMany()/completeMany()разпределят разходите за заключване и fsync. - Свързвания, търсене или отчети върху произволна колекция: добавете огледало SQL. Файловете остават канонични; огледалото е възстановима проекция в SQLite или MySQL.
Машините се комбинират: споделят само основната директория, така че едно приложение може да съхранява съдържание в DocStore, журнал за одит в SegmentedLog и фонови задачи в Queue под един и същ корен за съхранение.
DocStore
DocStore е предназначен за директно четене и прости сканирания на трайни записи. Той записва по един файл JSONC за всеки запис:
$store = new Storh\DocStore($root, 'articles');
$record = $store->put(['slug' => 'hello', 'published' => true]);
$same = $store->get($record->id());
Файловете със записи се разпределят по последните знаци на UUID, така че пиковите записи от UUIDv7 се разпределят в различни директории:
articles/data/2d/018bcfe5-6800-7000-8000-2d5dba6274da.jsonc
SegmentedLog
SegmentedLog е предназначен за записи с интензивно добавяне, четене с курсор и четене по времеви диапазон. Записите се добавят към активния сегмент, по един ред на запис във формат <byte-length>\t<xxh32>\t<json> (вижте Атомарност). Файловете на сегментите използват разширение .ndjson, но префиксът с дължината и контролната сума означава, че обикновените инструменти за NDJSON не могат да ги четат; използвайте stream() или конструктора на заявки, за да прочетете записите. Пълните сегменти се запечатват и получават разредени индекси.
$events = new Storh\SegmentedLog($root, 'events');
$events->put(['type' => 'user.created']);
$events->compact();
Опашка
Queue е предназначен за трайни опашки с висока пропускателна способност. Enqueue, claim, complete, requeue и purge записват събития само за добавяне в queue.log; състоянието на опашката се възпроизвежда в паметта при отваряне. Пакетните методи разпределят разходите за заключване и изчистване между много задачи. Журналът расте с всяко събитие; stats() показва текущия му размер в байтове.
$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 за родителските директории след атомарни преименувания. Устойчивостта при загуба на захранване все пак зависи от това дали файловата система и опциите за монтиране спазват тези синхронизации.