Zbavení FileStorage závislosti na sqlite

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Panda
Člen | 569
+
0
-

Zdravím všechny,
nedávno si na nette@conf.netlab.cz kdosi stěžoval, že je FileStorage v případě použití tagů a priorit natvrdo závislý na rozšíření sqlite. To by sice mělo být dostupné téměř všude, i tak ale takto tvrdá závislost nemusí být výhodná.

Proto jsem dneska udělal takový experiment – celý kód, který pracuje s sqlite žurnálem, jsem oddělil do třídy SqliteJournal, která implementuje nové rozhraní ICacheJournal. To nejen otevřelo cestu implementaci dalších „driverů“, například Sqlite3Journal, ale i možnost přidat podoporu pro tagy a priority do dalších storage driverů – například do MemcachedStorage.

Všechny změny jsem provedl na mém forku Nette na Githubu: jsmitka/nette. I když zdrojáky úspěšně prošly Nette testy, byl bych rád, kdyby se našli dobrovolníci ochotní mé úpravy otestovat. Předem díky.

Patrik Votoček
Člen | 2221
+
0
-

++

Panda
Člen | 569
+
0
-

kaja47 napsal(a):

Neměly by se v Sqlite3Journal stringy escapovat přes SQLite3::escapeString než přes sqlite_escape_string?

Měly, díky.

Vyki
Člen | 388
+
0
-

Opravdu dobrý počin. Nestálo by za to zakomponovat to rovnou do ofiko distribuce?

Panda
Člen | 569
+
0
-

Pokud se neobjeví zásadnější problémy, tak bych Davidovi poslal pull request.

jakubkulhan
Člen | 55
+
0
-

Trošku jsem si hrál a implementoval FileJournal. K dostání v repozitáři jakubkulhan/nette.

Je to hlavně inspirované bitcaskem, což je úložný engine pro (asi ne až tak známou) NoSQL databázi Riak.

V principu pracuje FileJournal následovně: jsou dva soubory – fj a fj.log, fj obsahuje seřazená data, aby byly možné rychlé výběry podle klíče (pro tagy), nebo pomocí rozsahu (pro prioritu). fj.log je append-only transakční log, do kterého se ukládají změny oproti fj. Když fj.log přesáhne jistou velikost, vytvoří se nový fj se změnami z fj.log, fj.log se vymaže a všechno začíná odznova.

Doufám, že jsem tam nenechal žádný palčivý bug a promyslel dobře všechny možné souběhové možnosti. Veškeré připomínky a patche vítány.

David Grudl
Nette Core | 8218
+
0
-

Panda napsal(a):

Proto jsem dneska udělal takový experiment – celý kód, který pracuje s sqlite žurnálem, jsem oddělil do třídy SqliteJournal

Dobrá práce, je to ve frameworku!

Panda
Člen | 569
+
0
-

Díky moc!

David Grudl
Nette Core | 8218
+
0
-

jakubkulhan napsal(a):

Trošku jsem si hrál a implementoval FileJournal. K dostání v repozitáři jakubkulhan/nette.

Tohle je zajímavé. Jen si říkám, že mít ve frameworku tři typy úložiště pro tagy je zbytečné. Asi by to chtělo udělat analýzu (rychlost, dostupnost) a podle toho jeden vybrat a ostatní dát do doplňků.

jakubkulhan
Člen | 55
+
0
-

David Grudl napsal(a):

Tohle je zajímavé. Jen si říkám, že mít ve frameworku tři typy úložiště pro tagy je zbytečné. Asi by to chtělo udělat analýzu (rychlost, dostupnost) a podle toho jeden vybrat a ostatní dát do doplňků.

Udělal jsem pár benchmarků (stroj – Intel Core2 Duo 2.33GHz, 2GiB RAM, HDD Seagate 320GB 7200RPM):

1000x write FileJournal
max         min         total       avg        stddev     var
0.27115107  0.00164890  8.59706783  0.00859707 0.36537707 42.50019671

1000x write SqliteJournal
max         min         total       avg        stddev     var
0.43490696  0.02485704  49.91854858  0.04991855 0.13166808 2.63765849

1000x write SqliteJournal (sqlite3 extenze)
max         min         total       avg        stddev     var
0.42714691  0.01956391  39.11326289  0.03911326 0.12882443 3.29362526

1x clean FileJournal
max         min         total       avg        stddev     var
0.41579294  0.41579294  0.41579294  0.41579294 0.00000000 0.00000000

1x clean SqliteJournal
max         min         total       avg        stddev     var
0.34027696  0.34027696  0.34027696  0.34027696 0.00000000 0.00000000

1x clean FileJournal
max         min         total       avg        stddev     var
0.00250816  0.00250816  0.00250816  0.00250816 0.00000000 0.00000000

1x clean SqliteJournal
max         min         total       avg        stddev     var
0.00097013  0.00097013  0.00097013  0.00097013 0.00000000 0.00000000

write FileJournal, konkurence: 30, kol: 5
max         min         total        avg        stddev     var
3.73856902  0.00193906  51.43360853  0.34519200 1.23055655 3.56484664

write SqliteJournal, konkurence: 30, kol: 5
max         min         total        avg        stddev     var
3.13552189  0.04577208  27.43233228  0.18288222 0.97480949 5.33025854

write FileJournal, konkurence: 5, kol: 30
max         min         total       avg        stddev     var
0.53182602  0.00178289  3.50739574  0.02338264 0.63881205 27.31993038

write SqliteJournal, konkurence: 5, kol: 30
max         min         total        avg        stddev     var
0.28351498  0.04531598  10.48244286  0.06988295 0.16275278 2.32893394

SqliteJournal používal (není-li uvedeno jinak) extenzi sqlite. Konkureční benchmarky probíhají tím způsobem, že v několika kolech se spustí naráz určitý počet zápisů a po jedné sekundě se přejde na další kolo (na to, až zápisy doběhnout, se čeká až na konci, ne mezi jednotlivými koly).

Z benchmarků je vidět, že FileJournal vede co se týče zápisů do určitého stupně konkurence. Při mazání ze žurnálu vyhrává SqliteJournal. Také stojí za povšimnutí, že časy u FileJournalu jsou dost rozptýlené – to proto, že vytváření nového fj souboru z logu je nákladná operace (u SQLite probíhá něco podobného taky /nevím, jestli SQLite vytvoří nový soubor, kterým přepíše ten starý, nebo provádí garbage collection přímo v souboru s databází/, nicméně to probíhá méně často než u FileJournalu a je to rychlejší) a probíhá poměrně často (jde o kompromis mezi tím, jestli častěji vytvářet nový fj, nebo načítat velký fj.log).

Jinak FileJournal myslím neaspiruje na to, aby se stal hlavním žurnálem, spíše je zamýšlen jako fallback řešení, když všechna ostatní nejsou dostupná kvůli nepřítomnosti potřebných extenzí.

MzK
Člen | 127
+
0
-

sry, že otevírám starší topic, dostal jsem se na něj googlením „FileJournal nette“.
Jen bych chtěl upozornit, na drobnou nekompatibilitu s původním FileStorage.
Viz příklad:

<?php
/* to substr tam je, abych zabránil velkému množství souboru v 1 složce. Článků je hodně a tak je ukládám do /clanky/1/, clanky/2/ clanky/a/, clanky/c/ … a pod dle prvního písmene hashe názvu.
*/
$storage = new FileStorage(TEMP_DIR . "/clanky/".substr(md5($page), 0,1)); //puvodní funguje, neexistující složky vytvoří
$storage = new FileJournal(TEMP_DIR . "/clanky/".substr(md5($page), 0,1)); // zde skončí chybou
//InvalidStateException
//Cannot open logfile /var/www/web.tld/www/../temp/clanky/1/fj.log for journal.
?>

A zrovna řeším co s tím a jak to spravit, aby mi to fungovalo, na hostingu nemám sqlite, proto.

jakubkulhan
Člen | 55
+
0
-

zacatecnik napsal(a):

sry, že otevírám starší topic, dostal jsem se na něj googlením „FileJournal nette“.
Jen bych chtěl upozornit, na drobnou nekompatibilitu s původním FileStorage.
Viz příklad:

<?php
/* to substr tam je, abych zabránil velkému množství souboru v 1 složce. Článků je hodně a tak je ukládám do /clanky/1/, clanky/2/ clanky/a/, clanky/c/ … a pod dle prvního písmene hashe názvu.
*/
$storage = new FileStorage(TEMP_DIR . "/clanky/".substr(md5($page), 0,1)); //puvodní funguje, neexistující složky vytvoří
$storage = new FileJournal(TEMP_DIR . "/clanky/".substr(md5($page), 0,1)); // zde skončí chybou
//InvalidStateException
//Cannot open logfile /var/www/web.tld/www/../temp/clanky/1/fj.log for journal.
?>

A zrovna řeším co s tím a jak to spravit, aby mi to fungovalo, na hostingu nemám sqlite, proto.

Žurnál je vytvářen přes Environment, takže FileStorage by měla vytvořit adresář pro něj dříve, než je otevřen žurnál. Tady je patch pro vytváření adresáře přímo ve FileJournalu. Ale obávám se, že stejné chování bude vykazovat i SqliteJournal.

Osobně bych se nebál vytvářet hodně souborů v jednom adresáři (kolik chceš mít položek v keši /řádově/? miliony?) a věřil filesystému, že to zvládne. Jinak, pokud vím, FileStorage vytváří pro každý namespace vlastní adresář, takže bych spíše využil namespaců ($cache = new Nette\Caching\Cache(new Nette\Caching\FileStorage(TEMP_DIR), 'clanky-' . substr(md5($page), 0, 1));).

hrach
Člen | 1838
+
0
-

Jakub: listování adresáře s pár tisíci soubory linux opravdu nezvládá.

jakubkulhan
Člen | 55
+
0
-

hrach napsal(a):

Jakub: listování adresáře s pár tisíci soubory linux opravdu nezvládá.

To bude silně závislé na použitém filesystému. I pro blbé staré ext3 to vypadá takhle (Intel Core 2 Duo 2,66GHz, HDD 300GB 7200RPM):

$ mkdir test
$ cd test
$ time for i in `seq 100000`; do touch $i; done # vytvořím 100000 souborů

real	2m7.387s
user	0m10.236s
sys	0m28.988s
$ time ls >/dev/null

real	0m0.445s
user	0m0.350s
sys	0m0.090s
Majkl578
Moderator | 1364
+
0
-

U mě na laptopu (ext4, AMD Athlon64 1,7GHz, 160GB 5400RPM):

$ time for i in `seq 100000`; do touch $i; done
real	6m11.629s
user	0m26.622s
sys	2m21.517s

$ time ls >/dev/null
real	0m0.969s
user	0m0.744s
sys	0m0.212s
hrach
Člen | 1838
+
0
-

Pánové, ale to není server, kterej s diskem pracuje :) a nepředpokládám, že máte taky v ntb raid :) ale hádat se s vámi nebudu, šlo jen o dobře míněnou radu z praxe.

Edit: možná sem to špatně napsal, nezvládá to disk, ne linux ;-) to je možná to, proč se to tak chytlo :D

Editoval hrach (6. 9. 2010 19:05)

Panda
Člen | 569
+
0
-

Jeden firemní server – Xeon X3360 @ 2.83GHz, disky 2× WDC WD5002ABYS za LSISAS1068E SAS v RAID1, ext4, LVM2:

server test # time for i in `seq 100000`; do touch $i; done

real    2m26.588s
user    0m17.434s
sys     2m10.786s
server test # time ls >/dev/null

real    0m0.154s
user    0m0.086s
sys     0m0.058s

Editoval Panda (6. 9. 2010 19:18)

MzK
Člen | 127
+
0
-

hrach: Z praxe mohu potvrdit. Pokud do té složky současně nějaký proces zapisuje, jiný z ní čte a do toho to je na sdílené hostingu, načítání aplikace se podstatně zpomalí.

Panda: To není moc reálný test, v praxi existuje nějaká fragmentace souborů, soubory jsou různě velké a různě se jmenují.

Editoval zacatecnik (6. 9. 2010 19:24)

jakubkulhan
Člen | 55
+
0
-

hrach napsal(a):

Pánové, ale to není server, kterej s diskem pracuje :) a nepředpokládám, že máte taky v ntb raid :) ale hádat se s vámi nebudu, šlo jen o dobře míněnou radu z praxe.

Edit: možná sem to špatně napsal, nezvládá to disk, ne linux ;-) to je možná to, proč se to tak chytlo :D

Uznávám, že rozdělení do více adresářů může věci urychlit. Ale kolik věcí to ztíží? Vím o tom, že např. Git to takhle dělá – udělá SHA1 hash položky, jestliže neexistuje, vytvoří adresář z prvních dvou písmen hex-řetězce hashe, a objekt uloží do souboru pojmenovaného podle zbytku hex-řetězce. Ovšem Git prostě vybírá objekty z databáze jen podle hashe. V keši budeš třebas chtít mazat podle tagu a budeš kvůli tomu vytvářet 16 keší a nad každou spouštět clean()? Nepraktické!

zacatecnik napsal(a):

hrach: Z praxe mohu potvrdit. Pokud do té složky současně nějaký proces zapisuje, jiný z ní čte a do toho to je na sdílené hostingu, načítání aplikace se podstatně zpomalí.

Stále by mě zajímalo, o jakých číslech se bavíme. Kolik článků chceš kešovat / kolik bude položek v keši celkem?

Jinak, proč jsem vůbec psal, že je to podle mě blbost. Pokud se nepotýkáš se škálovacími problémy, je to „premature optimization“ a „premature optimization is the root of all evil“. Zapojovat vůbec keš je do jistého stupně k ničemu – databáze to stihne obsloužit stejně rychle, né-li rychleji. Pak je pásmo, kdy FileStorage je dobré řešení. A potom bych se rozhodně nevydal tvou cestou, ale porozhlédl se po MemcacheStorage, nebo podobně.

zacatecnik napsal(a):

Panda: To není moc reálný test, v praxi existuje nějaká fragmentace souborů, soubory jsou různě velké a různě se jmenují.

hrach psal o listování souborů, tohle testuje rychlost listování souborů, nic jiného. Ber v potaz, že žádný benchmark nikdy nebude reálný :-) Jediné reálné statistické údaje se dají sehnat při reálném provozu.

David Grudl
Nette Core | 8218
+
0
-

FileStorage vytváří adresář jen jako vedlejší efekt, jelikož si vytváří složky pro jednotlivé namespaces. Skutečně není to záměr a spíš bych to měl fixnout. Hrozí totiž, že chybné zadání cesty k dočasnému adresáři nebude odhaleno.