Storh
Dateibasiertes Speichern von Datensätzen für PHP: JSONC-Dokumente, segmentierte Append-only-Logs und logbasierte Warteschlangen mit UUIDv7-IDs.
composer require storh/storhNeueste Version v0.5.2 MIT PHP 8.2+
$pages = $docs
->query()
->where('kind')->eq('page')
->orderBy('title')
->limit(20)
->get();In der Praxis
Der Schnellstart in zwei Teilen: zuerst ein Dokument im Speicherstamm, dann ein Logeintrag und ein Warteschlangenauftrag daneben.
Ein Stamm, drei Engines
StorageRoot::resolve() normalisiert ein Basisverzeichnis und einen Namensraum zu einem Stammverzeichnis. Bei der ersten Verwendung erstellt jede Engine darin ihr eigenes Verzeichnis.
Jede Engine gibt StorageRecord-Objekte mit id() und data() zurück. Die Warteschlange hängt jeden Abruf und Abschluss an ihr Ereignislog an.
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-Dokumentspeicher
Eine JSONC-Datei pro Datensatz, UUIDv7-IDs, Aufteilung anhand der ersten zwei Zeichen der letzten Gruppe von UUID, atomarer Austausch und Filter für Felder beim Streamen.
Segmentiertes Log
Append-only-Segmente aus JSON-Zeilen mit vorangestellter Länge und Prüfsumme, dünne Indizes, Cursor- und Zeitbereichsabfragen, Absturzwiederherstellung und Komprimierung.
Log-Warteschlange
Aufträge durchlaufen die Zustände Ausstehend, In Bearbeitung und Erledigt; dafür werden kompakte, nur angehängte Warteschlangenereignisse verwendet.
UUIDv7-Datensätze
Sortierbare, zeitlich adressierbare IDs für stabile Cursor, begrenzte Scans und speicherpfadfreundliches Sharding.
Atomare Schreibvorgänge
Schreibvorgänge in temporäre Dateien und atomare Umbenennungen halten Datensätze intakt. Nach einem Absturz des Schreibers bereinigt das nächste Öffnen dessen verwaiste temporäre Dateien.
Wiederherstellung unvollständiger Schreibvorgänge
Beim Lesen von Segmenten werden Längen- und Prüfsummenfelder überprüft; die Wiederherstellung kürzt unvollständige Schlusszeilen.
Dateibasiert
Kein Datenbankserver, kein Framework-Adapter, keine Composer-Laufzeitpakete; du übergibst ein Verzeichnis.
Liest JSONC
Datensatzdateien dürfen Kommentare und nachgestellte Kommas enthalten. storh schreibt kompakte JSONC-Einzeiler JSON; Kommentare bleiben daher bis zum nächsten Schreibvorgang erhalten.
Abfrage-Builder
Fluent-Abfragen wie where('status')->in([...]) mit DocStore und SegmentedLog; DocStore verwendet vorhandene sekundäre Indizes.
Sekundäre Indizes
Gleichheits-, eindeutige und Bereichsindizes werden als Dateien neben der Sammlung gespeichert, aus den Datensätzen abgeleitet und können jederzeit neu erstellt werden.
Schemas
Optionale Sammlungsschemas überprüfen jeden Schreibvorgang und legen fest, welche Felder indiziert werden.
SQL Mirror
Übertrage Sammlungen in SQLite oder MySQL für Joins, Suche und Berichte, während die Dateien maßgeblich bleiben. ext-pdo oder ext-mysqli wird nur bei Verwendung benötigt.
Warum Storh
Ein Verzeichnis statt eines Servers
Du übergibst ein Basisverzeichnis, in das storh schreibt. Benötigt werden PHP 8.2 und ext-json, aber weder Composer-Laufzeitpakete noch ein Framework-Adapter.
Eine Datensatzform, drei Speicherlayouts
Dokumente, Logs und Warteschlangen speichern alle eine UUIDv7-ID und ein Datenarray. Wähle das Speicherlayout passend zur Arbeitslast und lege alle drei unter einem Stammverzeichnis ab.
Prüfungen zum Ausführen
verify() vergleicht Indizes und Logstatus mit einem vollständigen Dateiscan. repair() stellt beschädigte Datensätze unter Quarantäne und kürzt unvollständige Logenden; CLI gibt JSON für cron-Aufträge und Zustandsprüfungen aus.
Benchmarks samt Konfiguration
Das README veröffentlicht Medianwerte für 50,000 Datensätze pro Engine auf einem Apple M1 Pro mit PHP 8.5, zum Beispiel 5.6 µs pro Einzelabfrage. Bei jedem Schreibvorgang dieser Läufe wird fsync ausgeführt; die Schreibraten hängen daher vom Dateisystem ab.
Im Vergleich zu
storh ist dateibasierter Speicher statt einer relationalen Abfrage-Engine. Diese Tools übernehmen daher einige Aufgaben, die storh auslässt.
| Werkzeug | Gut geeignet für | Unterschiede zu Storh |
|---|---|---|
| SQLite | Gut geeignet fürEine eigenständige SQL-Engine in einer einzigen Datei mit serialisierbaren ACID-Transaktionen, die auch bei einem Programmabsturz oder Stromausfall bestehen bleiben. Die Dokumentation beschreibt sie als Ersatz für improvisierte Datendateien. | Unterschiede zu Storhstorh speichert einen Datensatz pro JSON-Datei; der Kern benötigt keinen PDO-Treiber. Transaktionen über mehrere Datensätze gibt es nicht, und laut Dokumentation gehört diese Aufgabe in eine echte Datenbank. Sammlungen lassen sich für Joins und Berichte in SQLite übertragen, während die Dateien maßgeblich bleiben. |
| MySQL | Gut geeignet fürEine relationale Datenbank, die von einem multithreadfähigen SQL-Server bereitgestellt wird, mit Clientprogrammen und Bibliotheken, Verwaltungstools und einer breiten Auswahl an APIs. | Unterschiede zu Storhstorh läuft innerhalb des PHP-Prozesses und greift auf ein Verzeichnis zu; ein Serverbetrieb ist daher nicht nötig. Für Joins, Suche und Berichte spiegelt es Sammlungen in MySQL, auch über ein vorhandenes mysqli-Handle. Die Dateien bleiben die maßgebliche Datenquelle. |
| SleekDB | Gut geeignet fürEine Flat-File-Dokumentdatenbank in PHP ohne Abhängigkeiten von Drittanbietern, die schemalose Daten als einfache JSON-Dateien speichert. Ihre Abfragesprache API umfasst Joins, Textsuche, group by und having sowie Filter für verschachtelte Eigenschaften. | Unterschiede zu Storhstorh kombiniert seinen Dokumentspeicher mit einem segmentierten Append-only-Log und einer logbasierten Warteschlange unter einem Stammverzeichnis und führt vor der Bestätigung jedes Schreibvorgangs fsync aus. Joins gehören nicht zu storh; dafür überträgt es Sammlungen in SQLite oder MySQL. |
Fragen und Antworten
Kurze Antworten aus der Storh-Dokumentation.
Ja. DocStore serialisiert Änderungen mit einer Schreibsperre auf Sammlungsebene; gleichzeitige Schreibvorgänge für dieselbe ID folgen dem Prinzip: Die letzte Umbenennung gewinnt. Beim Abruf eines Auftrags hält die Warteschlange die Sperre, sodass zwei Worker nicht denselben ausstehenden Auftrag abrufen.
Ja. Beim Lesen sind Kommentare und abschließende Kommas erlaubt. Der nächste put() für diese ID schreibt die Datei jedoch als kompaktes JSON neu und entfernt sie. Im TRUST-Cache-Modus werden Änderungen an Dateien außerhalb von storh nicht erkannt.
Einzelabfragen per ID bleiben schnell, solange das Dateisystem die Dateianzahl bewältigt. Nicht indizierte Feldscans in DocStore werden ab etwa 10k Datensätzen langsamer; lege deshalb Indizes für die gefilterten Felder an. Scans und Bereichsabfragen im segmentierten Log funktionieren problemlos bis etwa 1M Datensätze.
Schreibvorgänge werden mit fsync dauerhaft gespeichert und durch atomare Umbenennung veröffentlicht. Beim erneuten Öffnen eines Logs oder einer Warteschlange werden unvollständige Enden bis zum letzten bestätigten Ereignis gekürzt. Die Dauerhaftigkeit bei Stromausfällen hängt weiterhin davon ab, ob Dateisystem und Einhängeoptionen diese Synchronisierungen unterstützen.
Nein. Übertrage Sammlungen mit SqlMirror in SQLite oder MySQL, um Joins, Suche und Berichte auszuführen. Atomare Schreibvorgänge über mehrere Datensätze bleiben Aufgabe einer echten Datenbank als maßgeblichem System.
Nein. Die Warteschlange ist kein allgemeiner Datenspeicher; die Nutzdaten eines Auftrags werden nach dessen Abschluss verworfen.
Starte mit dem Schnellstart und verwende anschließend „Eine Engine auswählen“, um DocStore, SegmentedLog oder Queue passend zu deiner Arbeitslast zu wählen.