Blog: Classier Twitter threads - Tag Redis

Poslední sobota a errata k přednášce Kdyby/Redis

Na Pražské březnové posobotě jsem měl přednášku o Kdyby/Redis, zde jsou slajdy s komentáři:

Kamera byla, takže až to Patrik zpracuje, bude i video

Proč errata?

Přednášku jsem si jako vždy připravoval den předem a během přípravy jsem se rozhodl udělat nějaké jednoduché benchmarky. Naprosto mi ale padla čelist, když jsem zjistil že filesystem mám na localhostu rychlejší než RedisStorage. Pojal jsem to statečně a rozhodl se vyzvat publikum, aby mi to pomohlo vyřešit.

Po přednášce jsme měli plodnou diskuzi a kluci se mi vysmáli, že ukládám cache doctrine metadat a annotací do Redisu. Protože používáme nejnovější stable PHP (tedy 5.5.něco) žil jsem v mylné představě že APC je mrtvé a tedy nad tím nemusím vůbec přemýšlet. Jenže! Ono není tak úplně mrtvé a nepoužitelné jak jsem si myslel.

Ještě během Davidovy přednášky jsem nainstaloval APCu, tedy uživatelskou cache z APC (ta část která neztratila smysl existence) a vylepšil Kdyby/Annotations aby na nich šla lépe konfigurovat cache.

Nakonec jsem tedy cache annotací a metadat přesměroval do apcu a místo ~10000 (slovy: deseti tisíců) requestů na prvnotní inicializaci stránky s kompilací containeru a načítání doctrine metadata jsem se dostal na špičkových ~200 requestů do Redisu. Na průměrnou stránku, která už má vygenerovanou cache mi požadavky z původních minimálně 200 spadly na ~80.

Vytížení Redisu víc než o polovinu padlo, aplikace se nepatrně zrychlila a zatím se ani jednou nezasekla na generování cache (což byl předtím problém). Strašák shardování se odkládá na neurčito :)

*PS: Juzno, dlužíš mi ještě to vysvětlení, jak udělat konzistentní hashování klíčů do shardování, které nebude potřeba přehashovávat ani při přidání dalších instancí. Teď to dělám takto

Takže ještě jednou závěr

Nad použitím session storage z Kdyby/Redis není třeba vůbec přemýšlet a prostě ji použijte, vyplatí se vždy. A ikdyž je cache malinko pomalejší než jsem doufal (požadavky do 0,3ms na request včetně overheadu mého storage), pořád je brutálně rychlá oproti filesystému pod zátěží.

Continue reading ...

Testování konzistence dat

Objevil jsem docela zásadní bug ve svém RedisExtension pro Nette Framework. Poslední vyhrává. Zkuste si spustit 4 ajaxové requesty v jeden moment, které upravují session. Ty první 3 jako by nebyly, protože ten poslední všechny změny přepíše. Průser že? Co s tím?

Zamykání klíče v Redisu

Oficální dokumentace doporučuje použít SETNX. Je úplně jedno jakou datovou strukturu použijete. Redis prostě neblokuje a můžete si klidně dělat stojky na hlavě. Uspávat aplikaci a hádat se o zámek spamováním Redisu? Až jako poslední možnost.

Ovšem po zbytek článku (až ke komentářům) budeme předpokládat, že metodyku zamykání máme vyřešenou. Jak si ale ověříme, že funguje?

Konzistence v PHPUnit

Narovinu, nic takového v PHPUnit nejde. S tím se ale přece nesmíříme!

Nejprve jsem si musel ujasnit, co vlastně chci dělat - chci spouštět kus kódu opakovaně a potřebuji aby se spouštěl ve více vláknech. Strávil jsem půl dne hledáním řešení v čistém PHPUnit. Annotace, podědění a upravení TestCase, nebo TestSuite. Nic, nikde na internetu ani ťuk, ale možná jen neumím hledat?

Vlastní řešení

Nejrozuměji se to dá docílit nějak tak, jak to dělá Nette\Tester. Takže jsem se drobátko inspiroval a vypůjčil si dvě třídy. V mém případě to jsou Process a ParallelRunner. Když předám runneru název scriptu, tak mi ho 100x spustí ve 30ti vláknech (není problém navýšit).

Vymyslel jsem si také, že chci aby se to pěkně používalo.

public function testConsistency()
{
    $sessionDir = TEMP_DIR;
    $this->threadStress(function () use ($sessionDir) {
        session_save_path($sessionDir);
        session_start();
        $_SESSION['counter'] += 1;
    }, 100);
    $this->assertEquals(100, $_SESSION['counter']);
}

Samozřejmě jsem si musel napsat vlastní parser na funkce, protože se nechci spoléhat na přítomnost pcntl rozšíření.

Parsovat funkce a metody je ještě docela hračka, ale zkuste si to s closurama ;)

Tokenizerem projdu soubor a najdu všechny definice funkcí a metod a pomocí ReflectionFunctionAbstract::getStartLine() dokážu spárovat i closury (pokud jich není víc na jednom řádku).

Obsah closury vykopíruju a pomocí reflexe si přečtu hodnoty proměnných předaných přes use(). Přidám bootstrap z testů a nějaké use tříd na začátek souboru a všechno slepím do jednoho scriptu. Takový script už se dá krásně spouštět pomocí ParallelRunner.

Výsledek je funkční testování kódu v desítkách vláken s elegantním zápisem. Jenom to trochu žere (nečekaně) a už se to nedá považovat za unit test ;)

Na závěr zpět k Redisu

Nevíte někdo, co s těmi zámky? Dočasně jsem úplně vypnul RedisSessionHandler, protože je teď tak trochu k ničemu. Pokud na nic nepříjdu, asi přistoupím na řešení s SETNX, ale moc se mi do toho nechce.

Continue reading ...