PHP: Výsledky srovnání funkcí file_(get)put_contents & f(read/write/flush)

Zajímalo mě jak je to s bezpečností funkcí file_get_contents/file_put_content vs flock/fread/fwrite/flush při hromadném přístupu k téže souboru. Jelikož jsem nikde na netu nenašel žádné informace, které by mi jednoznačně a definitivně vyloučili možnost selhání (např. vzniku vadného souboru, ztráty dat apod.) při čtení/zápisu pomocí prvních dvou zmíněných funkcí, tak jsem se újmul toho, že jsem udělal testy. Test jsem nahrál na vzdálený server a otestoval jsem stránku (skript): spustil jsem stejný požadavek 4x paralelně, abych zjistil jak rychle dokáže vzdálený server načítat a zapisovat data na pevný disk.

Kompletní popis testu a výsledků je v angličtině zde:

https://stackoverflow.com/questions/58351839/is-file-get-contents-file-put-contents-reliable-or-can-lead-to-loss-of-data-b

graf zde:


První tři testy T2, T3, T4 probíhaly s file_read_contents/file_write_contents, z toho jen T2 vykazuje takovou rychlost, která svědčí o faktickém čtení z disku (nikoliv z bufferu). T3 a T4 měly tak malé časy, že na grafu téměř nejsou vidět.

Pak je tam T5, T6 a T7 což je použití flock, fread, fflush + ověření správnosti zadaných dat, občas se selháním a následným obnovením souboru pomocí copy(). Všechny funkce měly smyčku s 50 cykly a prodlevu smyčky 50 microsekund. V posledním testu se to zdálo jako překážka, tak jsem to usleep v T7 odstranil.

Z výsledku mi vyplývá, že první dvě zmíněné funkce používají buffering a není na ně spoleh pokud jde o konkurenční zápis do spouboru. Při paralelním přístupu je třeba použít metodu flock, ffread, fflush, fwrite.

Zajímá mě, jestli mi tu můžete potvrdit správnost závěrů ke kterým jsem došel. Případně, pokud by byl zájem o zdrojáky testů, mohu poskytnout. Ale zajímá mě hlavně váš pohled na to, zda ten test má nějakou vypovýdající hodnotu. Jelikož jsem měl k dispozici 4 sady dat z každého testu, celkem 7*50*4 čísel, měl jsem dostatek čísel ke zhodnocení průměrů časů, odchylek, minimum a maxim, vytvoření grafů. Dělat další testy v oblasti prvních zmíněných funkcí mi už dál nepřipadalo užitečné, protože jsem nepřišel na to jak je donutit aby zapsaly celý obsah zásobníku do souboru (nebo aby si ten soubor skutečně přečetly z disku a ne z bufferu).


Kit

  • *****
  • 704
    • Zobrazit profil
    • E-mail
Jako třetí parametr file_put_contents() zkus uvést konstantu LOCK_EX

Používám téměř výhradně tyto dvě funkce, ostatní jsem odstavil jako nepotřebné. Výsledky po úpravě volání mě zajímají.

Kit

  • *****
  • 704
    • Zobrazit profil
    • E-mail
Ještě bych k tomu dodal, že pokud ty funkce používají buffering na úrovni souborového systému, což předpokládám, není nutné se obávat nespolehlivosti a nekonzistence. Ukaž příklad, kdy k něčemu takovému došlo a rozebereme příčiny. Bez zámků se to stává jen u souborů >8 KiB.

Jako třetí parametr file_put_contents() zkus uvést konstantu LOCK_EX

Používám téměř výhradně tyto dvě funkce, ostatní jsem odstavil jako nepotřebné. Výsledky po úpravě volání mě zajímají.

Ale to tam mám.

test 2 obsahuje toto:
Kód: [Vybrat]
file_put_contents("523.txt",$s,LOCK_EX);
Díky za připomínku.

Ještě bych k tomu dodal, že pokud ty funkce používají buffering na úrovni souborového systému, což předpokládám, není nutné se obávat nespolehlivosti a nekonzistence. Ukaž příklad, kdy k něčemu takovému došlo a rozebereme příčiny. Bez zámků se to stává jen u souborů >8 KiB.

Já ty data nekontroloval, velikost odpovídá, jenže jak vysvětlíš rozdíl v časech mezi T2 a T3, T4?

T2 bylo načítání z originálního souboru a zápis do jiného souboru (tedy dva různé soubory) a má výrazně vyšší časy, než když jsem použil T3, T4 se čtením ze stejného souboru do kterého jsem provedl zápis.

Kód T2:

Kód: [Vybrat]
echo "file_get_contents/file_put_contents test ".time()."<br>";
die;
while ( time()<1570604800 )
{
usleep(500);
}

$file = "temp.jpg";

echo "<h4>523 at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
 $start = microtime(true);
 $s = file_get_contents("523");
 file_put_contents("523.txt",$s,LOCK_EX);
 usleep( 50 );
 $time_elapsed_secs = microtime(true) - $start;
echo "time: $time_elapsed_secs s<br>";
}
echo "<h4>948 at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
 $start = microtime(true);
 $s = file_get_contents("948");
 file_put_contents("948.txt",$s,LOCK_EX);
 usleep( 50 );
 $time_elapsed_secs = microtime(true) - $start;
echo "time: $time_elapsed_secs s<br>";
}
echo "<h4>1371 at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
 $start = microtime(true);
 $s = file_get_contents("1371");
 file_put_contents("1371.txt",$s,LOCK_EX);
 usleep( 50 );
 $time_elapsed_secs = microtime(true) - $start;
echo "time: $time_elapsed_secs s<br>";
}

echo "<h4>1913 at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
 $start = microtime(true);
 $s = file_get_contents("1913");
 usleep( 50 );
 file_put_contents("1913.txt",$s,LOCK_EX);
 $time_elapsed_secs = microtime(true) - $start;
echo "time: $time_elapsed_secs s<br>";
}

echo "<h4>2701 at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
 $start = microtime(true);
 $s = file_get_contents("2701");
 file_put_contents("2701.txt",$s,LOCK_EX);
 usleep( 50 );
 $time_elapsed_secs = microtime(true) - $start;
echo "time: $time_elapsed_secs s<br>";
}

echo "<h4>4495 at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
 $start = microtime(true);
 $s = file_get_contents("4495");
 file_put_contents("4495.txt",$s,LOCK_EX);
 usleep( 50 );
 $time_elapsed_secs = microtime(true) - $start;
echo "time: $time_elapsed_secs s<br>";
}

echo "<h4>6758 at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
 $start = microtime(true);
 $s = file_get_contents("6758");
 usleep( 50 );
 file_put_contents("6758.txt",$s,LOCK_EX);
 $time_elapsed_secs = microtime(true) - $start;
echo "time: $time_elapsed_secs s<br>";
}

T3:
Kód: [Vybrat]
echo "flock test ".time()."<br>";
die;
while ( time()<1570612500 )
{
usleep(500);
}

$file = "temp.jpg";

echo "<h4>523 at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
 $start = microtime(true);
 $s = file_get_contents("523.txt");
 file_put_contents("523.txt",$s,LOCK_EX);
 usleep( 50 );
 $time_elapsed_secs = microtime(true) - $start;
echo "time: $time_elapsed_secs s<br>";
}
echo "<h4>948 at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
 $start = microtime(true);
 $s = file_get_contents("948.txt");
 file_put_contents("948.txt",$s,LOCK_EX);
 usleep( 50 );
 $time_elapsed_secs = microtime(true) - $start;
echo "time: $time_elapsed_secs s<br>";
}
echo "<h4>1371 at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
 $start = microtime(true);
 $s = file_get_contents("1371.txt");
 file_put_contents("1371.txt",$s,LOCK_EX);
 usleep( 50 );
 $time_elapsed_secs = microtime(true) - $start;
echo "time: $time_elapsed_secs s<br>";
}

echo "<h4>1913 at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
 $start = microtime(true);
 $s = file_get_contents("1913.txt");
 usleep( 50 );
 file_put_contents("1913.txt",$s,LOCK_EX);
 $time_elapsed_secs = microtime(true) - $start;
echo "time: $time_elapsed_secs s<br>";
}

echo "<h4>2701 at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
 $start = microtime(true);
 $s = file_get_contents("2701.txt");
 file_put_contents("2701.txt",$s,LOCK_EX);
 usleep( 50 );
 $time_elapsed_secs = microtime(true) - $start;
echo "time: $time_elapsed_secs s<br>";
}

echo "<h4>4495 at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
 $start = microtime(true);
 $s = file_get_contents("4495.txt");
 file_put_contents("4495.txt",$s,LOCK_EX);
 usleep( 50 );
 $time_elapsed_secs = microtime(true) - $start;
echo "time: $time_elapsed_secs s<br>";
}

echo "<h4>6758 at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
 $start = microtime(true);
 $s = file_get_contents("6758.txt");
 usleep( 50 );
 file_put_contents("6758.txt",$s,LOCK_EX);
 $time_elapsed_secs = microtime(true) - $start;
echo "time: $time_elapsed_secs s<br>";
}

To die na začátku tam je jen kvůli tomu, abych si nejdříve opsal čas a zkopíroval do while + 20 vteřin. To proto, aby se všechny 4 "exekuce" spustily ve stejný okamžik.

Kontrolu dat jsem prováděl jen od toho fflush, fwrite
« Poslední změna: 12. 10. 2019, 16:40:51 od exkalibr »


Kit

  • *****
  • 704
    • Zobrazit profil
    • E-mail
Souborové systémy mají vlastní buffery a cache. Zkus z jednoho procesu neustále modifikovat jeden soubor a z druhého ho číst. Uvidíš, že je to spolehlivé.

V T2 máš chybu
Kód: [Vybrat]
$s = file_get_contents("523");Tohle čte ze souboru, který zřejmě neexistuje.

Souborové systémy mají vlastní buffery a cache. Zkus z jednoho procesu neustále modifikovat jeden soubor a z druhého ho číst. Uvidíš, že je to spolehlivé.

V T2 máš chybu
Kód: [Vybrat]
$s = file_get_contents("523");Tohle čte ze souboru, který zřejmě neexistuje.

Všechny testy byly úspěšně zkontrolovány (kdyby neexistoval, dostal bych chybu).

523 je název originálního zdrojového souboru (obraz) 523 kB.
523.txt je název cílového souboru. Ta koncovka tam je jen pro rozlišení, trochu nelogické, já vím, ale já si tu koncovku oblíbil, protože obvykle pracuju s texťáky a ne s obrazy :)

Kit

  • *****
  • 704
    • Zobrazit profil
    • E-mail
Nevím sice, o co se pokoušíš, ale obávám se, že ses vydal chybnou cestou. Pokud ti dělají starosti kolize, měl bys použít databázi, která se o takové problémy sama postará a k tomu ti nabídne luxusní přístupové metody. PHP v základní výbavě nabízí 5 různých databází, ze kterých si určitě vybereš.

Takže co je účelem zkoumání čtení a zápisu souborů? Konfigurace, blog, diskuzní fórum, ... ?

Nevím sice, o co se pokoušíš, ale obávám se, že ses vydal chybnou cestou. Pokud ti dělají starosti kolize, měl bys použít databázi, která se o takové problémy sama postará a k tomu ti nabídne luxusní přístupové metody. PHP v základní výbavě nabízí 5 různých databází, ze kterých si určitě vybereš.

Takže co je účelem zkoumání čtení a zápisu souborů? Konfigurace, blog, diskuzní fórum, ... ?

Takže přiznáváš, že u funkcí file_get_contents a file_put_contents existuje riziko kolize. Protože z tvého příspěvku to vyplývá. Se stejným přístupem mlžit a neříct to explicitně jsem se potkal na více místech internetu. To je ten důvod, proč jsem se vydal tou cestou, abych to prozkoumal. Jestliže považuješ file_get_contents a file_put contents za natolik nespolehlivé, že chceš raději používat databázi, nepřímo mi tím potvrzuješ výsledek mého zkoumání. Ale chtěl bych to slyšet explicitně, něco jako "Ano, už to tak asi bude" nebo "Z výsledků to vyplývá" nebo "Nevěřím tomu, jdu si to ověřit"... To je taky možnost.

Myslím že ten rozdíl v rychlosti T2 a T3/T4 dokazuje, že T3 a T4 neprovádí bezpečné čtení a zápis. Smůla je v tom, že kolizi nemám jak prokázat, protože tak chytrý algoritmus, který by zkontroloval validitu dat nemám. Mohu kontrolovat jen rozměr, ale to ještě nic nedokazuje. Podle mě čas zápisu na disk v rozsahu 22ms až 206 ms dokazuje (T2), že se jedná o čtení z disku a zápis na disk, kdežto časy u T3:

Kód: [Vybrat]
523 0,00251909
948 0,000630766
1371 0,002995359
1913 0,006401292
2701 0,002551624
4495 0,002908468
6758 0,019312313
a T4:
0,001060027
0,002461412
0,003131726
0,003644179
0,002659538
0,001959271
0,001399823
ukazují, že jde o čtení z bufferu. Tím pádem se nepokouší o ověřování skutečných dat a aktuálnost či správnost dat není zaručena. To co dělají ty funkce, že se podívají na jméno souboru a řeknou si "je to jeden a ten samý soubor" a obsahem se nezabývaj, prostě to tam plácnou tu starou kopii dat. Předpokládají, že ke změně nedošlo.
« Poslední změna: 12. 10. 2019, 17:50:18 od exkalibr »

Kit

  • *****
  • 704
    • Zobrazit profil
    • E-mail
Nevím sice, o co se pokoušíš, ale obávám se, že ses vydal chybnou cestou. Pokud ti dělají starosti kolize, měl bys použít databázi, která se o takové problémy sama postará a k tomu ti nabídne luxusní přístupové metody. PHP v základní výbavě nabízí 5 různých databází, ze kterých si určitě vybereš.

Takže co je účelem zkoumání čtení a zápisu souborů? Konfigurace, blog, diskuzní fórum, ... ?

Takže přiznáváš, že u funkcí file_get_contents a file_put_contents existuje riziko kolize. Protože z tvého příspěvku to vyplývá. Se stejným přístupem mlžit a neříct to explicitně jsem se potkal na více místech internetu. To je ten důvod, proč jsem se vydal tou cestou, abych to prozkoumal. Jestliže považuješ file_get_contents a file_put contents za natolik nespolehlivé, že chceš raději používat databázi, nepřímo mi tím potvrzuješ výsledek mého zkoumání. Ale chtěl bych to slyšet explicitně, něco jako "Ano, už to tak asi bude" nebo "Z výsledků to vyplývá" nebo "Nevěřím tomu, jdu si to ověřit"... To je taky možnost.

Nejde o kolizi těchto dvou funkcí, obě jsou atomické a spolehlivé. Problém je, pokud soubor přečteš, modifikuješ a uložíš. Tyto tři úkony nejsou v jedné transakci a proto při souběhu můžeš přijít o data. Pokud potřebuješ takový případ užití, použij databázi.

Uvědom si, že se vlastně sám snažíš vytvořit databázový engine, tedy vynalézáš kolo. Mnoho databází vypadá jako obyčejný textový soubor, ale ten engine nad nimi je už hotový a odzkoušený. Proč některý z těch pěti nepoužiješ? Chápu, že třeba nechceš použít MySQL, ale co ty ostatní? Zkusil jsi někdy nějakou databázi? Jsou tady proto, aby nám sloužily.

Kit

  • *****
  • 704
    • Zobrazit profil
    • E-mail
Myslím že ten rozdíl v rychlosti T2 a T3/T4 dokazuje, že T3 a T4 neprovádí bezpečné čtení a zápis.

Nedokazuje to vůbec nic.

Problém je, pokud soubor přečteš, modifikuješ a uložíš. Tyto tři úkony nejsou v jedné transakci a proto při souběhu můžeš přijít o data.

Díky. To mi bohatě stačí. Proč ale tyto informace nejsou uvedeny v manuálu. To je přece důležitá informace. Když do googlu zadám např. "php file_get_contents you can lose data" tak nenajdu nic s tímto výskytem. Takže očividně se tato informace tají. Jak jinak si vysvětlit, že před tímto nezbytným faktem nevarují?
« Poslední změna: 12. 10. 2019, 19:48:33 od exkalibr »

Zajímavé je ale to, že v manuálu těch funkcí není nic o tom, že data jsou bufferované.

Myslím že ten rozdíl v rychlosti T2 a T3/T4 dokazuje, že T3 a T4 neprovádí bezpečné čtení a zápis.

Nedokazuje to vůbec nic.

Ale jo, dokazuje to přesně to co si říkal. Je totiž z toho patrné, že ty data se nečtou z disku (rozuměj - z plotny), ale z bufferu.

Kit

  • *****
  • 704
    • Zobrazit profil
    • E-mail
Problém je, pokud soubor přečteš, modifikuješ a uložíš. Tyto tři úkony nejsou v jedné transakci a proto při souběhu můžeš přijít o data.
Díky. To mi bohatě stačí. Proč ale tyto informace nejsou uvedeny v manuálu. To je přece důležitá informace. Když do googlu zadám např. "php file_get_contents you can lose data" tak nenajdu nic s tímto výskytem. Takže očividně se tato informace tají. Jak jinak si vysvětlit, že před tímto nezbytným faktem nevarují?

Toto není nic, co by se tajilo. Je to základní vlastnost souborového systému, kterou by každý programátor měl znát. Týká se to všech programovacích jazyků, nejen PHP.

Kočku do mikrovlnky také nestrkáš, i když to v návodu není.

Doporučil jsem ti databázi proto, že umí zamknout nejen celý soubor, ale třeba jen kousek se záznamem, který chceš modifikovat. Umožní ti to modifikovat data třeba z deseti procesů současně, aniž by se mezi sebou popraly. Můžeš také jednu databázi sdílet třeba s deseti webovými servery. U fyzických souborů jsi bez šance na spolehlivé řešení.

K čemu potřebuješ v PHP práci se soubory?

Kit

  • *****
  • 704
    • Zobrazit profil
    • E-mail
Zajímavé je ale to, že v manuálu těch funkcí není nic o tom, že data jsou bufferované.
Myslím že ten rozdíl v rychlosti T2 a T3/T4 dokazuje, že T3 a T4 neprovádí bezpečné čtení a zápis.
Nedokazuje to vůbec nic.
Ale jo, dokazuje to přesně to co si říkal. Je totiž z toho patrné, že ty data se nečtou z disku (rozuměj - z plotny), ale z bufferu.

Buffery jsou záležitostí souborového systému, proto v manuálu PHP nesmí o nich být žádná zmínka. Jsou transparentní. Je úplně jedno, zda se data čtou z plotny nebo ze systémové cache. V obou případech se jedná o čerstvá data.