Žangovinky #32
Komentovaný překlad Django Dose Community Catchup, Episode #32.
Během prosince se na svém blogu Seek Nuance pěkně rozepsal John DeRosa. V
článku “A performance lesson on Django QuerySets” dokazuje, že
bezmyšlenkovité nasazení QuerySet funkce iterator() přináší spoustu
problémů. Menší paměťové nároky byly v jeho případě draze vykoupeny četností
dotazů na databázi (co iterace to dotaz, navíc umocněné dvaceti simultánně
spuštěnými Celery tasky).
V druhé polovině článku se zmiňuje o špatném pochopení vlastnosti QuerySetu,
tj. že jsou lazy (k vyhodnocení dojde až je to nezbytně nutné) a kešované. Na
praktické ukázce demonstruje, že každý vytvořený QuerySet generuje databázový
dotaz, což v kontextu jeho kódu znamenalo neúměrné zatížení PostgreSQL.
David Cramer je masakrální drsoň. Pokaždé když něco publikuje, tak je to
perla. A pokaždé mi trvá sakra dlouho, než pochopím o co jde.
Nejinak tomu je i v článku “Tracking changes to fields in Django”.
Pokud jsem se někde šeredně nesek, tak nám dává k dispozici dekorátor, s
jehož pomocí můžeme sledovat historické změny atributů u konkrétního modelu.
Fachá to tak, že pokud např. obj.name == 'Petr' a přenastavíme jej na
obj.name = 'Pavel', pak metoda obj.has_changed('name') vrátí True
(atribut name se změnil), metoda obj.old_value('name') vrátí ‘Petr’ a metoda
obj.whats_changed() nás podaruje slovníkem změněných atributů. Tato ultimátní
“změnokeška” se resetuje při vytvoření instance a během uložení objektu.
Daniel Greenfeld rozvířil debatu o Template Languages. Ve svém postu hájí
směr, který tvrdí že šablonovací jazyk musí být stupidní. Čím méně toho umí,
tím lépe.
Reakce na sebe nenechaly dlouho čekat. Autoři alternativních šablonovací jazyků
(Armin Ronacher — Jinja2, Mike Bayer — Mako) vysvětlují, proč toho
jejich jazyky umí víc + jeden pohled zvenčí.
Špetka sci-fi z velkého světa (pro mě rozhodně). Pokud stejně jako chlapci
z The Washington Times spravujete vícero serverů a začínáte mít binec v
Python balíčcích (na serveru A verzi 1, na serveru B verzi 1.2 a na serveru C
nic), může se vám šiknout jejich fabfile.py.
Poslední zpráva hodná zmínky: nedávno implementovaná unittest metoda
assertNumQueries dráždila Luke Planta natolik, že pro ni napsal speciální
pomocnou třídu FuzzyInt. S její pomocí je možné testovat počet SQL dotazů v
zadaném intervalu. Jak Luke trefně komentuje, vlivem kešování se lehce stane,
že počet SQL dotazů v aplikaci kolísá, a testovat je na exaktní počet přináší
spíš problémy než užitek.
A teď něco úplně jiného. Novinky z chobotu:
- Byly rozšířeny funkce mail_admins() a mail_managers(). Odteďka je možné k nim připínat HTML přílohy, čehož se hned využilo pro odesílání přítulnějších error emailů. 3× hurá!
- Odskočme si do views, přesněji na jejich konce. Pokud nejste moc indie, budete
tam mít buďto nějakou
HttpResponse,render_to_responsenebo generické view. Odteďka ale budeme potkávat nového kamaráda TemplateResponse. Ten dostal do vínku kopec lenosti, a obsah generuje až to je skutečně nutné. Díky této povaze bude např. možné změnit na poslední chvíli jméno šablony, nebo do ní poslat upravený či rozšířený kontext. Už se těším na prčovní middlewary, které z této vlastnosti vytěží maximum.
Poznámka: Kopa práce, těžká témata a blížící se vánoce. Výsledek? Žangovinky #31 nejsou a nebudou. Komu chybí, nechť opráší AČ slovník, a postne sem link.
Pěkné vánoce!
Ladění výkonu Django aplikací
V září 2010 proběhl v Portlandu DjangoCon. Seznam probíraných témat je docela velký. Pokud to čas dovolí, pokusím se některé zajímavé přednášky zpracovat a pak se s nimi podělit zde na blogu.
První přednáška, která mě padla do oka byla “First steps in performace tuning” od Russella Keith-Mageeho řešící výkon Django aplikací.
Nejdříve si uvědomme základní fakta:
- vývoj probíhá na lokále, který je zatěžován sporadicky
- systém má k dispozici velké množství paměti
- k aplikaci přistupuje jediný uživatel
Situace na serveru ale býva přesně opačná.
Nejčastější prohřešky
Před publikováním aplikace na server je vhodné proklepnout několik bodů:
- Každé view by mělo svůj úkol vyřešit co nejrychleji. Odezva do 0.1s je dobrá, do 1s obstojná, nad 2s špatná. Pokud se view na nečem dlouho zdržuje, je lepší úkol poslat do fronty (message queue) a zpracovat jej offline. Posloužit může i AJAX, cron či “persistent services” (jestli jsem to dobře pochopil, pak měl Russell na mysli démona, který jede někde bokem, udržuje v paměti persistentí data a ta na žádost velice rychle vrací).
- Minimalizuj počet SQL dotazů. S pomocí Django Debug Toolbaru odhal neefektivní místa aplikace. Sniž počet SQL dotazů na minimum. Pro relační dotazy se vyplatí použít select_related. Pozor! Hromada malých “levných” dotazů je stejně špatná, jako jeden “drahý” dotaz (SQL příkaz EXPLAIN napoví).
- Na velikosti dat záleží. Pokud view zpracovává pouze zlomek atributů, které sebou model nese, nasaď only, defer, values nebo values_list. S jejich pomocí Django z DB tabulek vyzobne jen to co je třeba a objem přenášených dat mezi databází a aplikací výrazně klesne.
- Používej databázové indexy. U atributů, podle kterých aplikace třídí nebo vyhledává užší množinu dat (get, filter, exclude, order_by) je vhodné zvážit nasazení databázových indexů, viz parametr db_index.
- Denormalizuj. Když je to třeba. Dokud to jde, drž se čistého návrhu. Až to začne drhnou (výkonostně), neostýchej se denormalizace. Pamatuj, existují i jiné možnosti (databázová view, “umělé” tabulky s předpočítanými hodnotami, apod).
- Šablonový systém je pomalý. No a co (Russellova reakce). Pokud tvůj projekt obsahuje hodně aplikací, nebo máš v šablonách hodně {% include %} či {% extend %} tagů, nebo hodně TEMPLETE_DIRS adresářů, nasaď django.template.loaders.cached.Loader. Nebo použij jiný šablonový systém, třeba Jinja2 (říkám já).
Kešování
Invalidace
Russell se ptá: máš stránku, která se mění sporadicky? Pak ji zakešuj. Pak pokračuje: máš stránku která se mění často? A bude vadit, když po určitou dobu uvidí návštěvníci starý obsah?
Asi jak kde. S kešováním totiž přichází nepříjemný efekt, a to invalidace hodnot. Pokud například změním titulek u článku, většinou nestačí zničit keš s detailem článku, ale také přehledy článku v kategoriích, možná také úvodní stránku a bůh ví co ještě. Na invalidaci žádný recept nemá (naťukává sice signály, ale nijak zvlášť je nerozebírá). Sází spíše na jednoduchost, a k tomu mu bohatě stačí adekvátně nastavená doba expirace nakešovaných hodnot a smíření se s faktem, že návštěvníci jeho webu po určitou dobu neuvidí čerstvý obsah.
Cold start, thundering
Během této partie mě ale zaujalo jiné téma: “cold start” a “dogpiling/thundering herd”. Hrozné názvy.
Za optimální situace bude prvním návštěvníkem webu obsah stránky zakešován a každý další uživatel už uvidí obsah z keše. Pokud ale skokově, během malého okamžiku, dojde na stránku více uživatelů, bude se pro každého z nich stránka renderovat znovu a znovu, bez využití keše (protože v ní ještě nic uloženo není). Podobný efekt může nastat i při vyexpirování hodnoty z keše.
Russell radí, aby se před startem serveru spustil skript, který jednorázově vyrenderuje nejnavštěvovanější stránky (ale třeba i querysety, či jiná často používaná data) a uloží je do kešky. Doporučuje prostudovat aplikaci django-newcache od Erica Florenzana, která “thundering” efekt elegenatně řeší.
ETagy
Posledním kašotématem byly ETagy. Zjednodušeně: jde o mechanismus, který vyrenderovanou stránku “otaguje” na úrovni protokolu HTTP (libovolným řetězcem). Když uživatel navštíví stránku podruhé, pošle prohlížeč spolu s GETem i původně získaný ETag. Pokud aplikace zjistí, že se požadovaná stránka nijak nezměnila (vygeneruje stejný tag jako ten co přichází od prohlížeče), pak odpoví status kódem “304 Not Modified” a server je ušetřen generováním a přenosem nezměněné HTML stránky.
Jak ale efektivně zjistit, jestli se stránka změnila nebo ne? Pokud například máme view, které generuje seznam článků, pak musíme nejdřív provést dotaz na DB, vytáhnout naposledy přidaný či změněný článek a porovnat jej s ETagem. To ale není ono, lepší by bylo úplně DB obejít, situaci rychle vyhodnotit a kód ve view vůbec neprovádět.
Chytrý trik na svém blogu nabízí Will McGugan. View odekorujeme, a
informaci s posledním ETagem necháme v cache (Will má ve svých modelech atribut
version, kterou s každým uložením inkrementuje). Porovnání je pak mimořádně
rychlé, nicméně opět se dostáváme k invalidaci — musíme zajistit mazání keše
(ideálně pověšené opět na save).
Load testing
Jakmile aplikace jede na serveru, je třeba měřit. Všecko možné (response time z logů, využití paměti, zbývající místo na disku, zatížení I/O). Prostě vše.
Russell upozorňuje na memory leaks Pythoních C extensions (což jsou údajně všechna rozhraní populárních DB), několikrát naráží na neobvyklosti MySQL (podivné zamykání tabulek, chování planneru, apod).
K prověřování zátěže doporučuje curl, ab a siege.
Dotazy z publika
Na konci přednášky zaznělo z pléna i několik dotazů:
- Kterou optimalizací začít? Těžko říct, záleží na aplikaci. Nejdříve je nutné najít bolavé místo a pak ho řešit.
- Jak je to s thredy uvnitř view? Vytvořit thread uvnitř view je blbost a spíš to ukazuje na špatný návrh.
- Nějaký tip na ladící nástroje? Django Debug Toolbar + plugin pro profilování.
Video
No a konečně i záznam Russellovy přednášky: