Storh
Хранилище записей на основе файлов для PHP: документы JSONC, сегментированные журналы только для добавления и очереди на основе журналов с идентификаторами UUIDv7.
composer require storh/storhПоследний выпуск v0.5.2 MIT PHP 8.2+
$pages = $docs
->query()
->where('kind')->eq('page')
->orderBy('title')
->limit(20)
->get();На практике
Краткое руководство состоит из двух частей: документ в корневом каталоге хранилища, затем запись журнала и задание очереди рядом с ним.
Один корневой каталог, три движка
StorageRoot::resolve() нормализует базовый каталог и пространство имен в корневой каталог, а при первом использовании каждый движок создает в нем собственный каталог.
Каждый движок возвращает объекты StorageRecord с полями id() и data(). Очередь добавляет каждое получение задания и его завершение в журнал событий.
use Storh\DocStore;
use Storh\Queue;
use Storh\RecordQuery;
use Storh\SegmentedLog;
use Storh\StorageRoot;
$root = StorageRoot::resolve(__DIR__ . '/var/storh', 'demo');
$docs = new DocStore($root, 'pages');
$home = $docs->put([
'kind' => 'page',
'title' => 'Home',
]);
$loaded = $docs->get($home->id());$log = new SegmentedLog($root, 'events');
$log->put([
'type' => 'page.saved',
'pageId' => $home->id(),
]);
foreach ($log->stream(RecordQuery::all()->after($home->id())->limit(50)) as $record) {
// Process records lazily.
}
$queue = new Queue($root, 'jobs');
$jobId = $queue->enqueue(['task' => 'render', 'pageId' => $home->id()]);
$job = $queue->claim();
if (null !== $job) {
$queue->complete($job->id());
}Хранилище документов JSONC
Один файл JSONC на запись, идентификаторы UUIDv7, шардирование по двум символам последней группы UUID, атомарная замена и потоковая фильтрация полей.
Сегментированный журнал
Сегменты только для добавления со строками JSON, снабженными префиксами длины и контрольной суммы, разреженными индексами, чтением по курсору и временному диапазону, восстановлением после сбоя и уплотнением.
Очередь на основе журнала
Задания переходят между состояниями «ожидает», «обрабатывается» и «выполнено» с помощью компактных событий очереди, доступных только для добавления.
Записи UUIDv7
Сортируемые идентификаторы с привязкой ко времени для стабильных курсоров, сканирования в заданных границах и путей хранения, удобных для шардирования.
Атомарная запись
Запись во временный файл и атомарное переименование сохраняют целостность записей. После сбоя процесса записи при следующем открытии удаляются оставшиеся временные файлы.
Восстановление после неполной записи
При чтении сегментов проверяются поля длины и контрольной суммы; при восстановлении обрезаются неполные строки в конце.
Сначала файлы
Без сервера баз данных, адаптера фреймворка и пакетов среды выполнения Composer; вызывающий код передает каталог.
Чтение JSONC
В файлах записей могут быть комментарии и завершающие запятые. При записи storh сохраняет компактный однострочный JSON, поэтому комментарии остаются до следующей записи.
Построитель запросов
Цепочки запросов, например where('status')->in([...]), доступны в DocStore и SegmentedLog; DocStore использует вторичные индексы, если они есть.
Вторичные индексы
Индексы равенства, уникальности и диапазона хранятся в файлах рядом с коллекцией, формируются на основе записей и могут быть перестроены в любой момент.
Схемы
Необязательные схемы коллекций проверяют каждую запись и определяют, для каких полей создаются индексы.
Зеркало SQL
Передавайте коллекции в SQLite или MySQL для соединений, поиска и отчетности, сохраняя файлы основным источником данных. Требуется ext-pdo или ext-mysqli только при использовании.
Почему Storh
Каталог вместо сервера
Вы передаете базовый каталог, и storh записывает данные в него. Требуются PHP 8.2 и ext-json; пакеты среды выполнения Composer и адаптер фреймворка не нужны.
Одна структура записи, три способа хранения
Документы, журналы и очереди хранят идентификатор UUIDv7 и массив данных. Выберите способ хранения на диске под свою задачу и разместите все три движка в одном корневом каталоге.
Проверки, которые можно выполнить
verify() сравнивает индексы и состояние журнала с полным сканированием файлов. repair() помещает поврежденные записи в карантин и обрезает поврежденные концы журналов, а CLI выводит JSON для заданий cron и проверок работоспособности.
Тесты производительности с описанием условий
В README публикуются медианные показатели для 50,000 записей каждого движка на Apple M1 Pro с PHP 8.5, например 5.6 µs на чтение по ключу. Перед каждым завершением записи в этих тестах выполняется fsync, поэтому скорость записи зависит от файловой системы.
Сравнение с
storh использует файловое хранилище, а не реляционный движок запросов, поэтому эти инструменты выполняют некоторые задачи, которые он не охватывает.
| Инструмент | Подходит для | Отличия от Storh |
|---|---|---|
| SQLite | Подходит дляАвтономный движок SQL в одном файле с сериализуемыми транзакциями ACID, сохраняющими атомарность при сбое программы или отключении питания. В документации он представлен как замена самодельным файлам данных. | Отличия от Storhstorh хранит каждую запись в отдельном файле JSON, а для его работы не нужен драйвер PDO. Многозаписных транзакций нет; документация рекомендует использовать для этого полноценную базу данных. Коллекции можно передавать в SQLite для соединений и отчетности, сохраняя файлы основным источником данных. |
| MySQL | Подходит дляРеляционная база данных, предоставляемая многопоточным сервером SQL, с клиентскими программами и библиотеками, административными инструментами и широким набором API. | Отличия от Storhstorh работает внутри процесса PHP и использует каталог, поэтому сервер не нужно обслуживать. Для соединений, поиска и отчетности коллекции передаются в MySQL, в том числе через существующий дескриптор mysqli; файлы остаются источником достоверных данных. |
| SleekDB | Подходит дляДокументная база данных на основе обычных файлов в PHP без сторонних зависимостей; данные без схемы хранятся в виде обычных файлов JSON. Язык запросов API поддерживает соединения, полнотекстовый поиск, группировку и фильтрацию по вложенным свойствам. | Отличия от Storhstorh объединяет хранилище документов с сегментированным журналом, доступным только для добавления, и очередью на его основе в одном корневом каталоге и выполняет fsync для каждой записи, прежде чем сообщить о ее сохранении. В storh нет соединений; для них коллекции передаются в SQLite или MySQL. |
Вопросы и ответы
Краткие ответы из документации Storh.
Да. DocStore сериализует изменения с помощью блокировки записи на уровне коллекции, а при одновременной записи с одним идентификатором побеждает последнее переименование. При получении заданий очередь удерживает блокировку, поэтому два обработчика не заберут одно и то же ожидающее задание.
Да. При чтении допускаются комментарии и завершающие запятые, но следующая запись put() с этим идентификатором перезаписывает файл в компактном формате JSON и удаляет их. В режиме кэширования TRUST изменения файлов вне storh не обнаруживаются.
Чтение по идентификатору остается быстрым, пока файловая система справляется с числом файлов. Сканирование полей DocStore без индексов замедляется при объеме свыше примерно 10k записей, поэтому создавайте индексы для полей, по которым выполняете фильтрацию. Сканирование сегментированного журнала и чтение диапазонов хорошо работают примерно до 1M записей.
Записи синхронизируются с диском через fsync и публикуются атомарным переименованием; при повторном открытии журнала или очереди поврежденные концы обрезаются до последнего зафиксированного события. Сохранность данных при отключении питания по-прежнему зависит от того, соблюдают ли эти операции синхронизации файловая система и параметры монтирования.
Нет. Передавайте коллекции в SQLite или MySQL с помощью SqlMirror для соединений, поиска и отчетности. Для атомарной записи нескольких записей по-прежнему нужна полноценная база данных в качестве основного источника данных.
Нет. Queue — это не универсальное хранилище записей; после выполнения задания его полезная нагрузка удаляется.
Начните с краткого руководства, а затем в разделе «Выбор движка» подберите DocStore, SegmentedLog или Queue под свою задачу.