Перейти до вмісту

Рушії

Вибір рушія

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

DocStore SegmentedLog Queue
На диску один файл JSONC на запис, шардований за кінцевими символами UUID сегменти лише для додавання з рядками JSON, перед якими вказано довжину й контрольну суму, а також маніфест один журнал подій лише для додавання
Записування атомарна заміна файлу для кожного запису додавання із контрольними сумами; перезаписування та видалення додають нові записи, а звільнене місце повертається під час compact() додавання із контрольною сумою для кожної події черги
Читання get() за ідентифікатором, запити за індексом сканування курсором і за часовим діапазоном, 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 на запис:

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> (див. Атомарність). Файли сегментів мають розширення .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 батьківських каталогів. Стійкість до втрати живлення все одно залежить від того, чи файлову систему та параметри монтування налаштовано на виконання цих операцій синхронізації.