Enginy
Výběr enginu
Všechny tři enginy ukládají totéž: ID UUIDv7 a pole dat, ale na disku je organizují různě. Od toho se odvíjí, k čemu se každý engine hodí:
| DocStore | SegmentedLog | Queue | |
|---|---|---|---|
| Na disku | jeden soubor JSONC na záznam, rozdělený podle koncových znaků UUID | segmenty s přidáváním na konec, řádky JSON uvozenými délkou a kontrolním součtem, plus manifest | jeden log událostí pouze s přidáváním na konec |
| Zápis | atomická náhrada souboru pro každý záznam | zápisy s kontrolním součtem; přepisy a mazání přidávají nové položky, prostor se uvolní kompakcí compact() |
zápis s kontrolním součtem pro každou událost fronty |
| Čtení | get() podle ID, dotazy s indexem |
procházení kurzorem a časovým rozsahem, get() přes index stavu |
claim() v pořadí zařazení do fronty |
| Nejvhodnější použití | jednotlivá čtení a vyhledávání podle indexu v kolekcích libovolné velikosti | proudy s převahou přidávání, přibližně do 1M záznamů | trvalé předávání práce mezi procesy |
| Slabina | skenování polí bez indexu u kolekcí s více než přibližně 10k záznamy | časté přepisování jednotlivých záznamů (staré verze zabírají místo až do kompakce) | nejde o obecné úložiště záznamů; data se po dokončení úlohy zahodí |
DocStore a SegmentedLog používají stejné plynulé rozhraní dotazů API: stejný řetězec volání where() funguje v obou enginech, liší se jen provedení. DocStore může odpověď získat ze sekundárních indexů, zatímco SegmentedLog filtruje při procházení segmentů. Queue pracuje na základě nárokování, dotazy nepodporuje: pracovníci claim() další úlohu místo filtrování úloh.
Orientační pravidla:
- Záznamy, které jednotlivě načítáte, aktualizujete nebo mažete a vyhledáváte podle polí:
DocStore. Přidejte indexy pro pole, podle kterých filtrujete. - Události nebo verze, které převážně přidáváte a čtete zpět v pořadí nebo v časovém rozmezí:
SegmentedLog. - Úlohy předávané od producentů pracovním procesům:
Queue. Nárokování a dokončování jsou bezpečné pro více procesů aclaimMany()/completeMany()rozkládají náklady na zámky a fsync. - Spojování, vyhledávání nebo vytváření sestav nad libovolnou kolekcí: přidejte SQL zrcadlo. Soubory zůstávají kanonickým zdrojem; zrcadlo je projekce, kterou lze znovu vytvořit v SQLite nebo MySQL.
Enginy lze kombinovat: sdílejí pouze kořenový adresář, takže jedna aplikace může mít obsah v DocStore, protokol auditu v SegmentedLog a práci na pozadí v Queue pod stejným kořenem úložiště.
DocStore
DocStore slouží k jednotlivému čtení a jednoduchému procházení trvalých záznamů. Pro každý záznam zapisuje jeden soubor JSONC:
$store = new Storh\DocStore($root, 'articles');
$record = $store->put(['slug' => 'hello', 'published' => true]);
$same = $store->get($record->id());
Soubory záznamů jsou rozdělené do podadresářů podle posledních znaků UUID, takže se nárazové zápisy UUIDv7 rozloží mezi adresáře:
articles/data/2d/018bcfe5-6800-7000-8000-2d5dba6274da.jsonc
SegmentedLog
SegmentedLog se hodí pro záznamy, ke kterým se často přidává, čtení pomocí kurzoru a čtení podle časového rozsahu. Záznamy se zapisují do aktivního segmentu, jeden na řádek ve formátu <byte-length>\t<xxh32>\t<json> (viz Atomicita). Soubory segmentů mají příponu .ndjson, ale kvůli prefixu s délkou a kontrolním součtem je běžné nástroje NDJSON nedokážou číst; záznamy načtěte pomocí stream() nebo tvůrce dotazů. Po zaplnění se segmenty uzavřou a opatří řídkými indexy.
$events = new Storh\SegmentedLog($root, 'events');
$events->put(['type' => 'user.created']);
$events->compact();
Queue
Queue slouží pro trvalé fronty úloh s vysokou propustností. Zařazení, nárokování, dokončení, vrácení do fronty a vyprázdnění zapisují události pouze na konec souboru queue.log; stav fronty se při jejím otevření načte přehráním událostí do paměti. Hromadné metody rozkládají náklady na zámky a zápis na disk mezi mnoho úloh. Log roste s každou událostí; stats() uvádí jeho aktuální velikost v bajtech.
$queue = new Storh\Queue($root, 'mail');
$queue->enqueue(['to' => 'person@example.test']);
$job = $queue->claim();
Trvalost a souběžnost
storh zveřejňuje zápisy souborů pomocí atomického přejmenování v cílovém adresáři. Při opětovném otevření a repair() se odstraňují opuštěné dočasné soubory, jejichž vlastnický proces už neběží, zatímco dočasné soubory aktivních zapisujících procesů zůstávají nedotčené. Značky zapisujících umožňují tuto kontrolu provést v konstantním čase, takže se při otevření úložiště prochází kolekce jen po pádu procesu.
SegmentedLog a Queue zapisují řádky logu chráněné délkou a kontrolním součtem. Při opětovném otevření nebo opravě se poškozený konec zkrátí na poslední potvrzenou událost a stav v paměti se znovu vytvoří z trvalého obsahu logu. Zápisy do logu a fronty se serializují pomocí zámků souborového systému.
Změny v DocStore se serializují pomocí zámku pro zápis na úrovni kolekce, takže se soubory záznamů a sekundární indexy aktualizují společně v rámci jedné hranice konzistence napříč procesy. Souběžné zápisy různých ID záznamů jsou bezpečné za stejných předpokladů o atomickém přejmenování v souborovém systému. Při souběžných zápisech stejného ID platí poslední přejmenování.
storh před dokončením přejmenování nebo připojení zapíše vyrovnávací paměti a zavolá fsync pro popisovače souborů; po atomickém přejmenování se také pokusí zavolat fsync pro nadřazené adresáře. Trvalost při výpadku napájení stále závisí na tom, zda podkladový souborový systém a možnosti připojení tato synchronizační volání respektují.