Motoare
Alegerea unui motor
Toate cele trei motoare stochează aceleași date: un ID UUIDv7 plus un tablou de date, dar le organizează diferit pe disc. Această organizare determină la ce se pricepe fiecare motor:
| DocStore | SegmentedLog | Queue | |
|---|---|---|---|
| Pe disc | un fișier JSONC per înregistrare, partiționat după ultimele caractere UUID | segmente doar pentru adăugare, cu linii JSON precedate de lungime și sumă de control, plus un manifest | un jurnal de evenimente doar pentru adăugare |
| Scriere | înlocuire atomică de fișier per înregistrare | adăugări cu sume de control; suprascrierile și ștergerile adaugă intrări noi, recuperate prin compact() |
adăugare cu sumă de control per eveniment din coadă |
| Citire | get() după ID, interogări indexate |
scanări cu cursor și pe interval de timp, get() prin indexul de stare |
claim() în ordinea adăugării în coadă |
| Punct forte | citiri punctuale și căutări indexate pentru colecții de orice dimensiune | fluxuri intensive în adăugări, cu până la aproximativ 1M de înregistrări | distribuirea persistentă a sarcinilor între procese |
| Punct slab | scanări ale câmpurilor fără index pentru colecții de peste aproximativ 10k înregistrări | rescrieri frecvente ale unei singure înregistrări (versiunile vechi ocupă spațiu până la compactare) | nu este un depozit general de înregistrări; payload-urile sunt eliminate la finalizarea unei sarcini |
DocStore și SegmentedLog folosesc aceeași interogare fluentă API: aceeași succesiune where() de apeluri funcționează pe ambele motoare, diferind doar execuția. DocStore poate răspunde folosind indexurile secundare, în timp ce SegmentedLog filtrează la scanarea segmentelor. Queue se bazează pe preluarea sarcinilor, nu pe interogări: lucrătorii claim() următoarea sarcină în loc să filtreze sarcinile.
Reguli orientative:
- Înregistrări pe care le preluați, actualizați sau ștergeți individual și le interogați după câmp:
DocStore. Adăugați indexuri pentru câmpurile după care filtrați. - Evenimente sau versiuni pe care, în principal, le adăugați și le citiți în ordine sau într-o fereastră de timp:
SegmentedLog. - Sarcini transmise de producători către procese lucrătoare:
Queue. Preluările și finalizările sunt sigure în mai multe procese, iarclaimMany()/completeMany()distribuie costurile de blocare și fsync. - Îmbinări, căutări sau raportări în orice colecție: adăugați o oglindă SQL. Fișierele rămân sursa canonică; oglinda este o proiecție reconstruibilă în SQLite sau MySQL.
Motoarele se pot combina: partajează doar directorul rădăcină, astfel încât o aplicație poate
păstra conținutul într-un DocStore, istoricul de audit într-un SegmentedLog și
sarcinile de fundal într-un Queue, sub aceeași rădăcină de stocare.
DocStore
DocStore este potrivit pentru citiri punctuale și scanări simple ale înregistrărilor persistente. Scrie
un fișier JSONC pentru fiecare înregistrare:
$store = new Storh\DocStore($root, 'articles');
$record = $store->put(['slug' => 'hello', 'published' => true]);
$same = $store->get($record->id());
Fișierele de înregistrare sunt partiționate în funcție de ultimele caractere UUID, astfel încât rafalele de scriere UUIDv7 să se distribuie între directoare:
articles/data/2d/018bcfe5-6800-7000-8000-2d5dba6274da.jsonc
SegmentedLog
SegmentedLog este potrivit pentru înregistrări adăugate frecvent, citiri cu cursor și citiri pe intervale de timp.
Înregistrările sunt scrise în segmentul activ, câte o linie per înregistrare în formatul
<byte-length>\t<xxh32>\t<json> (consultați Atomicitate). Fișierele segmentelor au extensia .ndjson, dar prefixul cu lungimea și suma de control înseamnă că instrumentele NDJSON simple nu le pot citi; folosiți stream() sau constructorul de interogări pentru a citi înregistrările. Segmentele complete devin segmente sigilate, cu indexuri sparse.
$events = new Storh\SegmentedLog($root, 'events');
$events->put(['type' => 'user.created']);
$events->compact();
Queue
Queue este destinat cozilor persistente cu debit mare. Operațiile enqueue, claim, complete, requeue și purge scriu evenimente doar pentru adăugare în queue.log; starea cozii este reluată în memorie la deschiderea cozii. Metodele pentru loturi distribuie costul blocării și al flush-ului între mai multe sarcini. Jurnalul crește cu fiecare eveniment; stats() raportează dimensiunea curentă în octeți.
$queue = new Storh\Queue($root, 'mail');
$queue->enqueue(['to' => 'person@example.test']);
$job = $queue->claim();
Persistență și concurență
storh publică scrierile de fișiere prin redenumire atomică în directorul țintă.
La redeschidere și repair() elimină fișierele temporare abandonate al căror proces proprietar s-a închis,
și lasă în pace fișierele temporare ale proceselor de scriere active. Marcajele scriitorilor fac verificarea constantă ca timp, astfel încât deschiderea unei stocări parcurge colecția doar după o cădere a procesului.
SegmentedLog și Queue scriu linii de jurnal protejate prin lungime și sumă de control. La redeschidere
sau reparare, cozile incomplete sunt trunchiate până la ultimul eveniment confirmat, iar starea din memorie este reconstruită din conținutul persistent al jurnalului. Scrierile în jurnal și în coadă sunt serializate cu blocări ale sistemului de fișiere.
Modificările DocStore sunt serializate cu o blocare de scriere la nivel de colecție, astfel încât
fișierele de înregistrări și indexurile secundare se actualizează ca o singură unitate de consistență între procese. Scrierile simultane către ID-uri distincte sunt sigure, cu aceleași presupuneri privind redenumirea atomică a sistemului de fișiere. Scrierile simultane către același ID sunt câștigate de ultima redenumire.
storh golește buffer-ele și execută fsync pentru descriptorii de fișier înainte de redenumire sau finalizarea adăugării și încearcă să execute fsync pentru directoarele părinte după redenumiri atomice. Persistența la pierderea alimentării depinde în continuare de respectarea acestor operații de către sistemul de fișiere și opțiunile de montare subiacente.