Vývoj náhrady za současný FileJournal

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

Ahoj. Díval jsem se na současnou implementaci FileJournalu v Nette a zdá se mi dost pomalá. Mám rozpracovanou implementaci založenou na B stromu, která se zdá být o dost rychlejší (například 100 insertů trvá na mém notebooku u současného FileJournalu kolem 20 sekund, u mé rozpracované implementaci kolem 1 sekundy, u získávání výsledků by to mohlo být ještě více). Je na tom ještě ovšem hodně práce (například chybí podpora pro priority či vyvažování stromu) a proto bych se vás chtěl zeptat, zda má smysl tento nový žurnál dopracovat do konečné podoby a zda by se mohl teoreticky stát náhradou současného žurnálu. Díky.

Editoval Acci (4. 12. 2010 18:29)

arron
Člen | 464
+
0
-

Rekl bych, ze smysl to rozhodne ma. Zda se to dostane do distribuce, to se asi uvidi, az to bude hotove, ze jo:-) Minimalne to bude alternativa. Nicmene pokud to bude tak dobre, jak rikas, tak ta pravdepodobnost, ze se to objevi primo v distribuci je celkem slusna (IMHO).

David Grudl
Nette Core | 8227
+
0
-

Určitě to smysl má! Klíčové je ale také to, jak se bude chovat v konkurečním prostředí. Viz https://forum.nette.org/…ti-na-sqlite?…

Acci
Člen | 83
+
0
-

Ahoj. Dost jsem na vývoji pokročil a mám první měření rychlosti současného souborového a Sqlite žurnálu a mého založeného na B+ stromech.

100 inserts
SqliteJournal: 1.64009404182 s
FileJournal: 26.8340108395 s
BtreeFileJournal: 0.143335103989 s

Clean priority
SqliteJournal: 0.0232048034668 s
FileJournal: 0.0149970054626 s
BtreeFileJournal: 0.0244851112366 s

Clean tag
SqliteJournal: 0.00389790534973 s
FileJournal: 0.0135850906372 s
BtreeFileJournal: 0.00154900550842 s

Jak jde z výsledků vidět, vývoj je na dobré cestě. Ještě mi zbývá optimalizovat mazání podle priority a otestovat jeho chování v konkurenčním prostředí. Poté bych jej uvolnil pro širší použití a v případě, že by se vám zalíbil, mohl by snad nahradit současný souborový žurnál.

Acci
Člen | 83
+
0
-

Zrovna jsem dokončil i testy v konkurenčním prostředí: Proces zapisuje 5× do žurnálu po 20ms intervalech záznam obsahující dva tagy a prioritu. Těchto procesů běželo zároveň 10 po dobu 30 sekund.

Výsledky jsou následující:

  • SQLite: 7,45 požadavku za sekundu
  • FileJournal: 2,65 požadavku za sekundu
  • BtreeJorunal: 80,04 požadavku za sekundu

Ne, opravdu jsem se nespletl a můj žurnál podává o řád lepší výsledky než SQLite žurnál. Abych řekl pravdu, samotného mne to dost překvapilo, předpokládal jsem, že SQLite na tom bude mnohem lépe.

Mám se tedy podle vás snažit žurnál odladit do použitelné podoby nebo by to byla zbytečná práce? Díky.

grey
Člen | 94
+
0
-

já bych řek že to cenu má, nevím kdo by si stěžoval když dostane něco rychlejšího než je současná implementace ;)

Ped
Člen | 64
+
0
-

Pokud tam neni nejaky race condition nebo skryty bug, tak 10× lepsi vykon smysl urcite ma.

Dale uvazil si pri jake strategii (situaci) je B-tree nejpomalejsi a testoval jsi to na takovou situaci? (priznavam ze o B-tree z hlavy toho moc nevim, takze otazka neni rypava, ale laicka) Je sice dulezite jak se to chova pri „bezne“ zatezi, ale je dobre vedet jak moc to muze byt spatne v extremni situaci.

srigi
Nette Blogger | 558
+
0
-

Bolo by faj to otestovat na realnom zdielanom hostingu. Ked to testujes na svojej masine, I/O je exluzivne dostupne tvojmu testu. Ked aplikacku presunies na zivy server, I/O (hrabanie disku) obsluhuje desiatky inych aplikacii a tvoj benchmark ma len malu cast v I/O queue.

No a v takejto situaci je treba spravit test aspon niekolko desiatok konkurencnych requestov na aplikaciu. Ziskas tak vysledky z ako tak realnych podmienok.

Acci
Člen | 83
+
0
-

Bugy tam zatím určitě budou, ono napsat v podstatě kompletní databázi není jen tak. Proto se nyní zaměřuji na psaní několika desítek testů. Race condition by snad nikdy nastat nemělo.

Co se týká otázky: Nenapadá mne situace, kdy by mohl být B strom nějak extrémně pomalý. Je to v podstatě nejlepší možná datová struktura používaná u prakticky všech souborových systémů a databází.

Acci
Člen | 83
+
0
-

srigi: Máš pravdu, všechno jsou to syntetické testy, ovšem i tak by výsledky mého žurnálu měly být mnohem lepší než ostatních. Zkusím to otestovat na nějakém freehostingu, tam bývá zátěž dost vysoká.

westrem
Člen | 398
+
0
-

B+ stromy maju problem najme ked sa maze a mali by sa vyvazovat a upravovat podla „standardnych“ pravidiel. Co cca viem, tak toto sa casto vobec neimplementuje, alebo implementuje len ciastocne, pretoze to spomaluje, inac s B+ stromami nevidim problem, a ak to bude nakodene dobre – co podla vysledkov ocividne je, bude to super a minimalne ja som za postupne nahradenie sucasnej implementacie v Nette.

Acci
Člen | 83
+
0
-

To jsem zapomněl zmínit: strom se při mazání nyní vůbec nevyvažuje. Vyvažování je totiž mnohem pomalejší, než vyhledávání v nevyváženém stromě. Nějakou jednoduchou a tedy rychlou implementaci plánuji, kvůli úspoře diskového místa.

Mesiah
Člen | 240
+
0
-

Hmm, takže v realu se neni problem se dostat na časovou složitost O(n2), co? :(

Acci
Člen | 83
+
0
-

Teoreticky taková situace nastat v nejhorším případě nastat může, ovšem v reálu je to dost nepravděpodobné. Nějakou zjednodušenou formu vyvažování ovšem v budoucnu plánuji implementovat.

Jinak jsem dokončil testy na hostingu IC s následujícími výsledky:

1000 vložení
SqliteJournal: 38.4401259422
FileJournal: 75.590485096
BtreeJorunal: 2.39867806435

Smazání 400 záznamů podle priority
SqliteJournal: 0.0956561565399
FileJournal: 0.496645927429
BtreeJorunal: 0.0224220752716

Smazání 600 záznamů podle tagu
SqliteJournal: 0.265986919403
FileJournal: 0.241114854813
BtreeJorunal: 0.0310559272766
arron
Člen | 464
+
0
-

A je uz nekde mozne si to vyzkouset? Resp. muzes zverejnit zdrojaky?

Acci
Člen | 83
+
0
-

Tento týden to dám k dispozici na testování.

Acci
Člen | 83
+
0
-

Tak konečně jsem se dopracoval k vydání testovací verze svého žurnálu. Je umístěn na Githubu i s celkem podrobným popisem. V průběhu zítřejšího dne se ještě pokusím dodat testy a benchmark, abyste si mohli ověřit výsledky.

Předem vám děkuji za otestování a v případě jakýchkoliv nesrovnalostí mne prosím kontaktujte.

David Grudl
Nette Core | 8227
+
0
-

Dobrá práce! Tak že bych ho dal přímo do Nette? Tak se spíš otestuje.

Acci
Člen | 83
+
0
-

Do repozitáře jsem doplnil vlastní testy a všemi to prošlo v pořádku jak na 64bit MacOS, tak na 32bit Linuxu, neměl by tedy být problém jej zařadit přímo do frameworku. Jen bude potřeba pro ostré nasazení změnit konstantu DEBUG na FALSE.

Díky.

Acci
Člen | 83
+
0
-

Přikládám ještě benchmark s kompletně hotovým žurnálem:

Init
SqliteJournal: 0.047590970993 s
FileJournal: 0.000597953796387 s
BtreeFileJournal: 0.000799894332886 s

1000 inserts
SqliteJournal: 8.93768596649 s
FileJournal: 69.2337641716 s
BtreeFileJournal: 1.148884058 s

Clean all
SqliteJournal: 0.0803961753845 s
FileJournal: 0.524681806564 s
BtreeFileJournal: 0.0179469585419 s

Same 1000 inserts again
SqliteJournal: 18.481400013 s
FileJournal: 78.605672121 s
BtreeFileJournal: 0.363624095917 s

Clean priority lower then 400
SqliteJournal: 0.0780811309814 s
FileJournal: 0.597638130188 s
BtreeFileJournal: 0.0151479244232 s

Clean one tag
SqliteJournal: 0.00354504585266 s
FileJournal: 0.00116205215454 s
BtreeFileJournal: 0.000761032104492 s

Clean 3 tags
SqliteJournal: 0.00691986083984 s
FileJournal: 0.00250887870789 s
BtreeFileJournal: 0.00107407569885 s

Clean all other tags
SqliteJournal: 0.0618770122528 s
FileJournal: 0.36325097084 s
BtreeFileJournal: 0.053032875061 s

Clean All
SqliteJournal: 0.0154650211334 s
FileJournal: 0.000211000442505 s
BtreeFileJournal: 0.0049159526825 s
David Grudl
Nette Core | 8227
+
0
-

Takže tvůj žurnál jsem zkusil nasadit do poslední vývojové větve. A děkuji ti za vývoj! (samozřejmě jsem vděčný i Jakubu Kulhanovi za starší FileJournal)

Poprosil bych ještě o drobnost: na pár místech v kódu se vyskytuje == resp. !=, můžeš ho, pokud je to možné, nahradit za === nebo v opačném případě doplnit komentářem // intentionally == nejlépe i s vysvětlením?

A taky jsem narazil na jednu docela závažnou chybu: pokud spustím (na Windows) test a násilně ho přeruším (ve fázi bigTests, dřív to nestihnu), další spuštění končí výjimkou, protože soubor nejspíš není v konzistentním stavu.

Bylo by super, kdyby jsi mohl komity posílat už přímo do svého forku Nette.

Acci
Člen | 83
+
0
-

A ta výjimka nastala i s vypnutým debug modem? V něm to všechny nekonzistence poctivě hlásí, jinak si s nimi dokáže poradit. Ale už mám připravený patch, který by jejich výskyt měl drasticky omezit.

David Grudl
Nette Core | 8227
+
0
-

Právě i s vypnutým debug módem. Ale už vím, kde je problém: chybí tam flock($this->handle, LOCK_SH) ve chvílích, kdy chceš soubor číst. Soubor je tak možno číst kdykoliv, nezávisle na tom, zda je exkluzivně uzamčen nebo ne. Na windows se to projeví tak, že se přečte prázdný řetězec a tím pádem selže kontrola hlavičky v konstruktoru. Můžeš tam prosím ty sdílené zámky doplnit?

Také kontrola file_exists je zbytečná, protože v zápětí mohl někdo soubor vytvořit, správný postup je rovnou volat fopen($this->file, 'xb') a pak teprve kontrolovat výsledek. Sice nelze rozlišit, jestli se soubor nevytvořil proto, že už existuje, nebo proto, že adresář je neplatný, ale s tím holt v PHP nelze nic dělat, správa chyb je tu zprasená. Případně lze prvně volat ‚r+b‘ a teprve když to selže, tak ‚wb‘.

Acci
Člen | 83
+
0
-

Udělal jsem tedy fork Nette na GitHubu, do kterého jsem přidal potřebné opravy. Že Windows načte z exkluzivně zamčeného souboru prázdný řetězec je fakt divné chování, na Mac OS i Linuxu to normálně funguje.

A k tvému druhému požadavku. Funkce file_exists je volána proto, že je rychlejší než fopen a nevidím v tom žádný problém s atomicitou. Pokud soubor neexistuje, funkce @fopen($this->handle, ‚xb‘) se jej pokusí vytvořit a pokud byl již v té době vytvořen, skončí chybou, která ovšem nevyvolá výjimku kvůli druhé kontrole pomocí funkce file_exists. Nebo se v něčem pletu?

David Grudl
Nette Core | 8227
+
0
-

Je to divné a hloupé chování, taky už jsem s tím měl nepříjemnosti.

Shared lock je každopádně potřeba používat pokaždé, když je soubor čten (ať už jedním příkazem nebo celou řadou příkazů). Jinak totiž exkluzivní zamykání nemá smysl. Pokud soubor má rozečteno 20 vláken a jedno je chce zamknout pro zápis, tak je třeba počkat, až čtenáři dočtou. Což právě zajistí použití shared locku. A naopak, pokud chci číst, tak musím počkat, než jiné vlákno dopíše, což opět provedu vytvořením sdíleného zámku. Tedy těch zámků bude potřeba v žurnálu asi přidat víc. (pokud už handle má shared lock, lze z něj udělat exclusive lock bez odemknutí).

Ad file_exists: tohle řešení není principiálně správné. Mezi jakýmkoliv file_exists a fopen se prostě může na disku něco změnit. Jistě, byla by to velká náhoda, proto se bavím o principiální stránce. Protože existuje vhodnější a rychlejší řešení (rovnou otevřít s r+b), šel bych touto cestou.

Acci
Člen | 83
+
0
-

V žurnálu se před každou operací (write nebo clean) vytváří exkluzivní zámek a tedy by v ten okamžik neměl nikdy jiný ze souboru číst nebo do něj zapisovat a soubor je odemčen až po provedení všech změn. Jediné místo, kde se četlo bez použití zámků bylo v hlavičce, protože tam na Unixu nebyl potřeba.

A jasně, mezi file_exists a fopen může dojít k vytvoření souboru, ale s tím počítám a proto volání fopen volám se zavináčem. Ale máš pravdu, přímé volání r+b bude lepší.

David Grudl
Nette Core | 8227
+
0
-

Acci napsal(a):

V žurnálu se před každou operací (write nebo clean) vytváří exkluzivní zámek a tedy by v ten okamžik neměl nikdy jiný ze souboru číst nebo do něj zapisovat.

Tak to právě nefunguje. Exkluzivní zámek neříká, že ze souboru nikdo nečte a nepíše, ale že nikdo nedrží sdílený zámek.

(samozřejmě může to na nějakém OS fungovat trošku jinak nebo obskurně, viz třeba ty windows, ale obecně zámky se vztahují jen k zámkům, nikoliv k reálným operacím se soubory).

Acci
Člen | 83
+
0
-

Ale protože do souboru zapisuje nebo z něj čte pouze FileJournal, který vždy vyžaduje exkluzivní zámek, neměly by být žádné jiné zámky potřeba.

David Grudl
Nette Core | 8227
+
0
-

Ještě: pokud do souboru zapisuji dvakrát související data, musím to udělat v rámci jednoho zámku, jinak může dojít k nekonzistenci. A taky soubor otevřený přes r+b může mít po získání čtecího zámku prázdný obsah, protože mohl být otevřen a uzamčen právě mezi operacemi fopen + flock(LOCK_EX) běžícími v jiném vlákně.

David Grudl
Nette Core | 8227
+
0
-

Acci napsal(a):

Ale protože do souboru zapisuje nebo z něj čte pouze FileJournal, který vždy vyžaduje exkluzivní zámek, neměly by být žádné jiné zámky potřeba.

A FileJournalu nevadí, když čte data, která mu někdo jiný přepisuje nebo je ještě nedopsal?

Acci
Člen | 83
+
0
-

Já ale stále nechápu, který jiný proces by do toho souboru měl zapisovat.

David Grudl
Nette Core | 8227
+
0
-

Proces A začne číst data, než je přečte všechny, proces B část z nich přepíše.

anebo proces B zapisuje data, než je zapíše všechna, proces A je nekompletní přečte.

Acci
Člen | 83
+
0
-

Proces A vytvoří exkluzivní zámek, začne číst data, proces B čeká na uvolnění zámku, proces A zapíše data a uvolní exkluzivní zámek, proces B uzamkne soubor, načte a zapíše data a nakonec uvolní zámek.

Zkoušel jsem najednou spustit několik stovek procesorů a žádný problém nenastal.

David Grudl
Nette Core | 8227
+
0
-

Už tomu rozumím, jediný okamžik, kdy proces čte a přitom nezapisuje, je v tom konstruktoru, takže čtecí zámek patří jen na to jedno místo. Ok.

Acci
Člen | 83
+
0
-

Přesně tak, jsem rád, že jsme se nakonec pochopili.

Editoval Acci (31. 12. 2010 0:31)

piler
Člen | 111
+
0
-

Mam problem s FileJournal. Vyhadzuje mi to vynimku:

<?php
if (!flock($this->logHandle, LOCK_EX)) {
	throw new InvalidStateException('Cannot acquite exclusive lock on log.');
}
?>

V com moze byt problem?

Aurielle
Člen | 1281
+
0
-

Jen z tohoto ti nikdo nic nepoví. Přilož chybu z Laděnky, soubor na kterém to padá…

piler
Člen | 111
+
0
-

gmvasek napsal(a):

Jen z tohoto ti nikdo nic nepoví. Přilož chybu z Laděnky, soubor na kterém to padá…

File: FileJournal.php

<?php
/**
 * Lock file for writing and reading and delete node cache when file has changed.
 * @return void
 */
private function lock()
{
    if ($this->handle) {
        if (!flock($this->handle, LOCK_EX)) {
            throw new InvalidStateException('Cannot acquire exclusive lock on journal.');
        }
        if ($this->lastModTime !== NULL) {
            clearstatcache();
            if ($this->lastModTime < @filemtime($this->file)) { // intentionally @
                $this->nodeCache = $this->dataNodeFreeSpace = array();
            }
        }
    }
}
?>

V ladenka vyhodi vynimku: „Cannot acquire exclusive lock on journal.

hrach
Člen | 1838
+
0
-

piler: co si nepochopil z vety priloz ladenku? Nazev chyby si tu uz psal…

piler
Člen | 111
+
0
-

hrach napsal(a):

piler: co si nepochopil z vety priloz ladenku? Nazev chyby si tu uz psal…

No, to je prave to, ze nechapem co mam zle. Co je potrebne nastavit, aby tato chyba nebola vyhodena.

Dik.

jasir
Člen | 746
+
0
-

Přilož kompletní výpis laděnky, nejen ten error, kapišto?

Editoval jasir (4. 3. 2011 15:18)

piler
Člen | 111
+
0
-

jasir napsal(a):

Přilož kompletní výpis laděnky, nejen ten error, kapišto?

Skusim to inak :).

Vsetko funguje OK na localhost. Ked som to uploadol live, tak sa prejavila chyba. Chyba vznika presne tu

<?php
if (!flock($this->handle, LOCK_EX)) {
?>

Takze je mozno potrebne nastavit na hostingu nieco, aby flock fungoval spravne. Je problem tam, alebo je problem v $this->handle

Ondřej Mirtes
Člen | 1536
+
0
-

Asi nerozumíš česky :) Nech si zobrazit Laděnku s touto chybou, dej v prohlížeči Uložit jako… a pošli nám html.

PaBi3
Bronze Partner | 62
+
0
-

Ja že je to vtip, tak čakám v napätí, čo sem pošle :-).
piler: pozri si Nette\Debug

Ondřej Brejla
Člen | 746
+
0
-

piler: Pošli něco, mě už to taky velice zajímá :-D

piler
Člen | 111
+
0
-

Mam niekedy pocit, ze vy uz poriadne ani neporozmyslate nad chybou, pokial to nie je pekne obalene v Ladenke.
Nechcem zobrazovat Ladenku, lebo obsahuje informacie o URL a ine informacie, ktore zatial zverenovat nechcem.

Ale skusim to inak.

Vo FileJournal.php je kod:

<?php
$this->handle = fopen($this->file, 'r+b');
echo $this->handle; // Resource id #151
?>

Nasleduje funkcia flock(), ktora podla manualu vracia TRUE, alebo FALSE.

<?php
if (!flock($this->handle, LOCK_EX)) {
	throw new Exception('...');
}
?>

A ta vracia FALSE. S ladenky sa neda nic viac vycitat, okrem tychto info co tu pisem. Problem nevidim so samotnym frameworkom, ale s flock() (lebo na localhoste mi funguje spravne). Su tam mozno nejake zavislosti spojene s touto funkciou, ktore sa snazim zistit.

Dakujem za porozumenie :).

Mikulas Dite
Člen | 756
+
0
-

Jaký je tam filesystem? Třeba lock nepodporuje.

piler
Člen | 111
+
0
-

Mikulas Dite napsal(a):

Jaký je tam filesystem? Třeba lock nepodporuje.

Neviem. Je moznost to zistit pomocou PHP?

Dik za rozumny reply :)

Patrik Votoček
Člen | 2221
+
0
-

Tohle je nějákej blbej vtip? Co ten člověk nechápe na tom pošli laděnku?

Jde o to že když pošleš laděnku tak vidíme kompletní stacktrace (a další údaje) toho jak k chybě došlo. A ty nám při odhalování chyby pomohou. Pokud nechceš zveřejňovat něco co obsahuje laděnka tak to před jejím posláním odstraň. Tak a teď už konečně POŠLI TU LADĚNKU!

Mikulas Dite
Člen | 756
+
0
-

Zkus pustit syrový

<?php
$handle = /* test handle, nějaký foo soubor */;
var_dump(flock($handle, LOCK_EX));
?>

úplně bez nette, samotné php.

Jestli tohle selže taky, není chyba v Nette/tvojí aplikaci, ale v prostředí.