
# Engines

## Eine Engine auswählen

Alle drei Engines speichern dieselben Daten: eine UUIDv7-ID und ein Daten-Array. Auf der Festplatte legen sie sie jedoch unterschiedlich ab. Von diesem Layout hängt ab, wofür sich die jeweilige Engine eignet:

|            | DocStore | SegmentedLog | Queue |
| ---------- | -------- | ------------ | ----- |
| Auf der Festplatte | eine JSONC-Datei pro Datensatz, nach UUID-Endzeichen aufgeteilt | Append-only-Segmente mit Längen- und Prüfsummenpräfixen in JSON-Zeilen sowie ein Manifest | ein einzelnes Append-only-Ereignislog |
| Schreiben | atomarer Dateiaustausch pro Datensatz | Prüfsummen-geschützte Anhänge; Überschreibungen und Löschungen fügen neue Einträge hinzu, die durch `compact()` freigegeben werden | Prüfsummen-geschütztes Anhängen für jedes Queue-Ereignis |
| Lesen | `get()` nach ID, indizierte Abfragen | Cursor- und Zeitbereichsscans, `get()` über den Zustandsindex | `claim()` in der Reihenfolge der Einreihung |
| Besonders geeignet für | Einzelabrufe und indizierte Suchen bei Sammlungen jeder Größe | Append-lastige Streams mit bis zu etwa 1 Mio. Datensätzen | dauerhafte Arbeitsverteilung zwischen Prozessen |
| Schwäche | Scans nicht indizierter Felder ab etwa 10.000 Datensätzen | häufige Umschreibungen einzelner Datensätze (alte Versionen belegen bis zur Komprimierung Speicherplatz) | kein allgemeiner Datensatz-Store; Payloads werden nach Abschluss eines Jobs verworfen |

`DocStore` und `SegmentedLog` verwenden dieselbe fließende [Query-API](https://storh.inline0.dev/de/docs/query-builder): Dieselbe `where()`-Kette läuft auf beiden Engines; nur die Ausführung unterscheidet sich. `DocStore` kann Ergebnisse aus seinen sekundären Indizes liefern, während `SegmentedLog` beim Scannen der Segmente filtert. `Queue` basiert auf Claims und lässt sich nicht abfragen: Worker `claim()` den nächsten Job, statt Jobs zu filtern.

Faustregeln:

- Datensätze, die du einzeln abrufst, aktualisierst oder löschst und nach Feldern abfragst: `DocStore`. Füge [Indizes](https://storh.inline0.dev/de/docs/indexes) für die Felder hinzu, nach denen du filterst.
- Ereignisse oder Versionen, die du meist anhängst und der Reihe nach oder nach Zeitfenster abrufst: `SegmentedLog`.
- Jobs, die von Erzeugern an Worker-Prozesse übergeben werden: `Queue`. Claims und Abschlüsse sind prozessübergreifend sicher, und `claimMany()`/`completeMany()` verteilen die Kosten für Sperren und fsync auf mehrere Vorgänge.
- Joins, Suche oder Berichte über beliebige Sammlungen: Füge einen [SQL-Mirror](https://storh.inline0.dev/de/docs/sql-mirror) hinzu. Die Dateien bleiben maßgeblich; der Mirror ist eine wiederherstellbare Projektion in SQLite oder MySQL.

Die Engines lassen sich kombinieren: Sie teilen nur das Stammverzeichnis. Eine Anwendung kann daher Inhalte in einem `DocStore`, einen Prüfpfad in einem `SegmentedLog` und Hintergrundaufgaben in einer `Queue` unter demselben Speicherstammverzeichnis halten.

## DocStore

`DocStore` eignet sich für Einzelabrufe und einfache Scans dauerhafter Datensätze. Es schreibt eine JSONC-Datei pro Datensatz:

```php
$store = new Storh\DocStore($root, 'articles');
$record = $store->put(['slug' => 'hello', 'published' => true]);
$same = $store->get($record->id());
```

Die Datensatzdateien werden anhand der letzten UUID Zeichen in Unterverzeichnisse aufgeteilt, damit sich Schreibspitzen mit UUIDv7 über mehrere Verzeichnisse verteilen:

```text
articles/data/2d/018bcfe5-6800-7000-8000-2d5dba6274da.jsonc
```

## SegmentedLog

`SegmentedLog` eignet sich für Append-lastige Datensätze, Cursor-Lesevorgänge und Zeitbereichsabfragen. Datensätze werden in das aktive Segment geschrieben, eine Zeile pro Datensatz im Format `<byte-length>\t<xxh32>\t<json>` (siehe [Atomarität](https://storh.inline0.dev/de/docs/atomicity)). Die Segmentdateien verwenden die Erweiterung `.ndjson`, aber wegen des Längen- und Prüfsummenpräfixes können gewöhnliche NDJSON-Tools sie nicht lesen. Rufe Datensätze mit `stream()` oder dem Query-Builder ab. Volle Segmente werden zu versiegelten Segmenten mit Sparse-Indizes.

```php
$events = new Storh\SegmentedLog($root, 'events');
$events->put(['type' => 'user.created']);
$events->compact();
```

## Queue

`Queue` eignet sich für dauerhafte Work-Queues mit hohem Durchsatz. enqueue, claim, complete, requeue und purge schreiben Append-only-Ereignisse in `queue.log`. Beim Öffnen der Queue wird ihr Status durch Wiedergabe des Logs in den Arbeitsspeicher geladen. Batch-Methoden verteilen die Kosten für Sperren und Flush auf viele Jobs. Das Log wächst mit jedem Ereignis; `stats()` gibt seine aktuelle Größe in Byte aus.

```php
$queue = new Storh\Queue($root, 'mail');
$queue->enqueue(['to' => 'person@example.test']);
$job = $queue->claim();
```

## Haltbarkeit und Nebenläufigkeit

storh veröffentlicht Dateischreibvorgänge durch ein atomares Umbenennen im Zielverzeichnis. Beim erneuten Öffnen und bei `repair()` werden verwaiste temporäre Dateien entfernt, deren Besitzerprozess beendet wurde. Temporäre Dateien aktiver Schreibprozesse bleiben erhalten. Schreibmarkierungen ermöglichen diese Prüfung in konstanter Zeit, sodass eine Sammlung beim Öffnen nur nach einem Absturz durchsucht wird.

`SegmentedLog` und `Queue` schreiben längen- und checksummegesicherte Logzeilen. Beim erneuten Öffnen oder Reparieren werden unvollständige Enden bis zum letzten bestätigten Ereignis entfernt und der Speicherzustand aus den dauerhaft gespeicherten Logdaten neu aufgebaut. Schreibvorgänge in Logs und Warteschlangen werden durch Dateisystemsperren serialisiert.

`DocStore`-Änderungen werden durch eine Schreibsperre auf Sammlungsebene serialisiert, sodass Datensatzdateien und Sekundärindizes prozessübergreifend gemeinsam aktualisiert werden. Gleichzeitige Schreibvorgänge für unterschiedliche Datensatz-IDs sind unter denselben Annahmen für atomare Umbenennungen im Dateisystem sicher. Bei gleichzeitigen Schreibvorgängen für dieselbe ID gewinnt die letzte Umbenennung.

storh leert Dateipuffer und synchronisiert Dateihandles mit fsync, bevor Umbenennungen oder Anhängevorgänge abgeschlossen werden, und versucht, nach atomaren Umbenennungen auch übergeordnete Verzeichnisse mit fsync zu synchronisieren. Die Dauerhaftigkeit bei Stromausfällen hängt weiterhin davon ab, ob das zugrunde liegende Dateisystem und die Einhängeoptionen diese Synchronisierungen unterstützen.
