Přejít k obsahu

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ů a claimMany()/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:

PHP
$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:

Text
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.

PHP
$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.

PHP
$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í.