Motori
Scegliere un motore
Tutti e tre i motori memorizzano le stesse informazioni: un ID UUIDv7 e un array di dati, ma le dispongono diversamente sul disco. È questa disposizione a determinare i punti di forza di ciascun motore:
| DocStore | SegmentedLog | Queue | |
|---|---|---|---|
| Su disco | un file JSONC per record, suddiviso in partizioni UUID-tail | segmenti append-only di righe JSON precedute da lunghezza e checksum, più un manifest | un log di eventi append-only |
| Scrittura | sostituzione atomica del file per record | aggiunte con checksum; le sovrascritture e le eliminazioni aggiungono nuove voci, recuperate con compact() |
aggiunta con checksum per ogni evento della coda |
| Lettura | get() per ID, query indicizzate |
scansioni con cursore e per intervallo di tempo, get() tramite l'indice di stato |
claim() nell'ordine di accodamento |
| Punto di forza | letture puntuali e ricerche indicizzate per raccolte di qualsiasi dimensione | flussi con molte aggiunte fino a circa 1M di record | distribuzione affidabile del lavoro tra processi |
| Punto debole | scansioni di campi senza indice oltre circa 10k record | riscritture frequenti di singoli record (le versioni precedenti occupano spazio fino alla compattazione) | non è un archivio generico di record; i payload vengono eliminati al completamento di un job |
DocStore e SegmentedLog condividono la stessa query API fluida: la stessa catena where() viene eseguita su entrambi i motori e cambia solo l'esecuzione. DocStore può rispondere usando gli indici secondari, mentre SegmentedLog filtra durante la scansione dei segmenti. Queue si basa sull'acquisizione e non consente query: i worker claim() il job successivo invece di filtrare i job.
Regole pratiche:
- Record che recuperate, aggiornate o eliminate singolarmente e interrogate per campo:
DocStore. Aggiungete indici per i campi su cui applicate filtri. - Eventi o versioni che per lo più aggiungete e rileggete in ordine o in una
finestra temporale:
SegmentedLog. - Processi affidati dai produttori ai processi worker:
Queue. Le richieste e i completamenti sono sicuri tra più processi, mentreclaimMany()/completeMany()ammortizzano il costo di lock e fsync. - Join, ricerca o report su qualsiasi raccolta: aggiungete un mirror SQL. I file restano canonici; il mirror è una proiezione ricostruibile in SQLite o MySQL.
I motori si combinano: condividono solo la directory radice, quindi un'applicazione può
conservare i contenuti in un DocStore, il registro di audit in un SegmentedLog e
le attività in background in un Queue, tutti sotto la stessa radice di archiviazione.
DocStore
DocStore serve per letture puntuali e scansioni semplici di record persistenti. Scrive
un file JSONC per ogni record:
$store = new Storh\DocStore($root, 'articles');
$record = $store->put(['slug' => 'hello', 'published' => true]);
$same = $store->get($record->id());
I file dei record sono distribuiti in partizioni in base agli ultimi UUID caratteri, così i picchi di scrittura di UUIDv7 si distribuiscono tra le directory:
articles/data/2d/018bcfe5-6800-7000-8000-2d5dba6274da.jsonc
SegmentedLog
SegmentedLog serve per record con molte aggiunte, letture tramite cursore e letture per intervallo temporale.
I record vengono scritti nel segmento attivo, una riga per record nel formato
<byte-length>\t<xxh32>\t<json> (vedere Atomicità). I
file di segmento usano l'estensione .ndjson, ma il prefisso con lunghezza e checksum
impedisce ai normali strumenti NDJSON di leggerli; rileggete i record con stream() o
con il generatore di query. I segmenti completi diventano segmenti sigillati con indici sparsi.
$events = new Storh\SegmentedLog($root, 'events');
$events->put(['type' => 'user.created']);
$events->compact();
Coda
Queue serve per code di lavoro persistenti ad alto throughput. Accodamento, richiesta, completamento,
riaccodamento e svuotamento scrivono eventi in append-only su queue.log; lo stato della coda viene
ricostruito in memoria all'apertura della coda. I metodi batch ammortizzano il costo di lock e
flush su molti processi. Il log cresce a ogni evento; stats()
riporta la dimensione attuale in byte.
$queue = new Storh\Queue($root, 'mail');
$queue->enqueue(['to' => 'person@example.test']);
$job = $queue->claim();
Persistenza e concorrenza
storh pubblica le scritture su file tramite rinomina atomica all'interno della directory di destinazione.
Alla riapertura, repair() rimuove i file temporanei abbandonati il cui processo proprietario è terminato,
lasciando intatti quelli dei processi di scrittura attivi. I marcatori dei writer rendono il controllo
costante, quindi l'apertura di un archivio esegue la scansione della raccolta solo dopo un arresto anomalo.
SegmentedLog e Queue scrivono righe di log protette da lunghezza e checksum. Alla riapertura
oppure durante la riparazione, le code troncate vengono riportate all'ultimo evento confermato e lo stato
in memoria viene ricostruito dai contenuti persistenti del log. Le scritture su log e coda sono serializzate con
lock del filesystem.
Le modifiche di DocStore sono serializzate con un lock di scrittura a livello di raccolta, così
l'aggiornamento dei file dei record e degli indici secondari costituisce un unico confine di coerenza tra
processi. Le scritture simultanee su ID di record distinti sono sicure con le stesse
ipotesi sulla rinomina atomica del filesystem. In caso di scritture simultanee sullo stesso ID, prevale l'ultima rinomina.
storh esegue il flush e fsync degli handle di file prima del completamento della rinomina o dell'aggiunta e prova a eseguire fsync sulle directory padre dopo le rinomine atomiche. La persistenza in caso di interruzione improvvisa dell'alimentazione dipende comunque dal fatto che il filesystem sottostante e le opzioni di mount rispettino queste operazioni di sincronizzazione.