Django: Kešování a škálování

Velká návštěvnost webového projektu sice autora většinou těší, ale často přináší i problémy se stabilitou a dostupností systému. V předposledním díle seriálu o frameworku Django se proto podíváme na kešovací systém Djanga a na to, jak pomocí něj zvládnout příval návštěvníků.
Seriál: Hrajeme si s Djangem (16 dílů)
- Django: Úvod a instalace 14. 8. 2009
- Django: Nastavení projektu a první pokusy 21. 8. 2009
- Django: Databázový model 28. 8. 2009
- Django: Databázový model podruhé 4. 9. 2009
- Django: Administrace 11. 9. 2009
- Django: Prezentace dat 18. 9. 2009
- Django: Prezentace dat podruhé 25. 9. 2009
- Django: Zpracovávání formulářů 2. 10. 2009
- Django: Autentizace a autorizace 9. 10. 2009
- Django: Nahrávání souborů 16. 10. 2009
- Django: Zabudované aplikace 23. 10. 2009
- Django: Rozšiřování možností Djanga 30. 10. 2009
- Django: Internacionalizace 6. 11. 2009
- Django: Nasazování projektu 13. 11. 2009
- Django: Kešování a škálování 20. 11. 2009
- Django: Závěr 27. 11. 2009
Kešování obsahu
Při každém požadavku prohlížeče se stránka musí před odesláním sestavit. To zahrnuje načtení dat z databáze přes pohled a následné vygenerování HTML kódu dle šablony. Tento proces většinou není příliš rychlý, proto se od určité návštěvnosti vyplatí sestavené stránky ukládat do vyrovnávací paměti a poté je při příštím požadavku servírovat odtamtud. Tuto vyrovnávací paměť (nebo též keš) může Django ukládat do souborů, databáze, nebo použít externí kešovací systém, jako je např. memcached, který drží keš v operační paměti serveru.
Úložiště keše
Ke specifikaci úložiště slouží konstanta CACHE_BACKEND, do níž je potřeba zadat URL. Výchozí hodnota je locmem:///
, což je naprosto základní kešování procesů, které není vhodné používat na produkčním serveru. Ukládání keše do souborů se zadává pomocí prefixu file://
, po kterém následuje absolutní adresář. Kupříkladu kdybychom chtěli udržovat keš v adresáři /tmp/cache/
, připíšeme do souboru settings.py
tento řádek:
CACHE_BACKEND = 'file:///tmp/cache/'
V případě ukládání keše do databáze to je trochu složitější. Nejprve musíme vytvořit kešovací tabulku pomocí příkazu python manage.py createcachetable <tabulka>
. Takže když bychom chtěli keš držet v tabulce cache
, stačí napsat v adresáři projektu příkaz:
$ python manage.py createcachetable cache
… a vytvoří se nám odpovídající tabulka. Poté je potřeba zadat do konstanty CACHE_BACKEND
prefix db://
následovaný názvem tabulky, tedy v našem případě:
CACHE_BACKEND = 'db://cache/'
Další možností je použít kešovací systém memcached
, jenž se zadává prefixem memcached://
následovaným adresou serveru. V případě použití více serverů se oddělují adresy středníkem. Pokud máme jeden memcached
na lokálním počítači a druhý na adrese 192.168.1.100, nastavení konstanty vypadá takto:
CACHE_BACKEND = 'memcached://127.0.0.1:11211;192.168.1.100:11211/'
Nastavení keše
Po výběru úložiště je potřeba zapnout kešování stránek. K tomu slouží dvě middleware komponenty: 'django.middleware.cache.UpdateCacheMiddleware'
a 'django.middleware.cache.FetchFromCacheMiddleware'
. Ty přidáme do konstanty MIDDLEWARE_CLASSES
, což v projektu z minulých dílů vypadá takto:
MIDDLEWARE_CLASSES = ( 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.cache.FetchFromCacheMiddleware', )
UpdateCacheMiddleware
musí být vždycky na prvním místě a FetchFromCacheMiddleware
na posledním.Tím zapneme kešování v celém projektu. V případě, že na serveru provozujeme více projektů a ty sdílejí úložiště, měli bychom nastavit v každém projektu jiný obsah konstanty CACHE_MIDDLEWARE_KEY_PREFIX
, aby si projekty navzájem kešované stránky nepřepisovaly. Dále máme možnost pomocí konstanty CACHE_MIDDLEWARE_SECONDS
nastavit dobu kešování (udává se v sekundách). Tuto volbu máme možnost změnit také přes argumenty konstanty CACHE_BACKEND
.
Pokud byste potřebovali nastavit dobu kešování u jednotlivých pohledů, dá se to lehce zařídit pomocí dekorátoru cache_page. Je možné kešovat i samotné části šablon, k čemuž slouží značka {% cache %}
.
Škálování
Správné nastavení keše je ale jenom začátek. Při provozování projektu s narůstající návštěvností se časem nevyhneme rozdělení zátěže na více serverů. Ukážeme si několik modelových případů. Podotýkám, že popisovaná návštěvnost je odhadovaná a může se lišit i o několik řádů.
Projekt s nejvýše tisíci návštěvníky denně
Pro projekty s malou návštěvností bohatě stačí jeden fyzický server. Už v této fázi se vyplatí zapnout kešování aspoň do souborů nebo databáze. Jestliže očekáváme postupný nárůst zatížení, není dobrý nápad použít databázový systém SQLite, protože se špatně škáluje. Doporučuji použít databázový systém PostgreSQL. (Typické nasazení se občas označuje zkratkou LAPD, což znamená Linux, Apache, PostgreSQL a Django.)
Projekt s nejvýše desetitisíci návštěvníky denně
Jakmile přestane osamocený server stíhat obsluhovat požadavky návštěvníků, následujícím logickým krokem je přesunout databázovou část na další stroj. V tomto okamžiku totiž latence lokální sítě přestane být větším znevýhodněním než přepínání mezi zpracováváním webového požadavku a hledáním dat v databázi. Jednoduše řečeno, jeden server nebude dělat dvě věci najednou a bude se zabývat pouze jednou úlohou. Databázový server doporučuji vybavit co největším množstvím operační paměti, aby se plně využilo kešování databázových dotazů.
Dále je vhodné servírovat statické soubory zvlášť z vlastního odlehčeného webserveru (např. lighttpd nebo tux). Je to z toho důvodu, že na odesílání statických souborů není potřeba plně vybavené Apache včetně Djanga. Pokud používáte k vývoji nějakou z populárních javascriptových knihoven, můžete si je nechat servírovat přes Google AJAX Libraries API. Výhodou tohoto řešení je, že pokud návštěvník předtím někdy navštívil stránku, na které také používají servírování dané knihovny Googlem, bude ji mít už načtenou v keši prohlížeče.
Projekt s více než statisíci návštěvníky denně
Pokud vašemu projektu přestal stačit na každou funkci jeden server, gratuluji, váš projekt se stal masově oblíbeným. V této fázi doporučuji zprovoznit memcached
na samostatném počítači a nechat vracet výsledky dotazů z něj. Následujícím krokem je rozdělení jednotlivých služeb na více strojů a vyvažovat mezi nimi zátěž. K tomu slouží tzv. load balancer (např. Perlbal nebo HAProxy), který podle určitého klíče vybere webserver pro obsloužení požadavku a uživatele na něj přesměruje. Tak můžeme postupně rozšiřovat počet webových serverů. Podobně lze přidávat databázové servery, kupříkladu PostgreSQL podporuje několik způsobů vyrovnávání zátěže.
Po zprovoznění load balanceru je pokračování jednoduché — stačí podle potřeby dodávat další a další webové, databázové a kešovací servery…
Související odkazy
- Kešování na Djangoproject.com
- Dvanáctá a patnáctá kapitola v The Definitive Guide to Django
Příště zakončíme tento seriál závěrečným shrnutím a několika tipy a triky.
Bohužel nemohu souhlasit s uvedeným způsobem škálování aplikace. Zní to jednoduše „jen přidávejte databázové a webové servery“. Jenže praxe takhle bohužel nefunguje.
Kdybyste se v problematice více orientoval, tak byste musel vědět, že nikdo z velkých hráčů takto nefunguje, protože to nejde. Na škálování ve větším měřítku se musí jinak.
Mohl byste prosím upřesnit, proč takovýto způsob škálování není možný? Rád se nechám poučit. To co jsem naznačil v tomhle článku, platí pro postupně se rozrůstající webový projekt, ne pro „velké hráče“, kteří mají už vybudovanou infrastrukturu.
Prostě proto, že od určité fázi už není možné zrychlovat pouze přidáváním serverů.
V popisovaném LAPD schématu je možné přidávat aplikační servery (pokud nejsou extrémně špatně napsané), ale úzkým hrdlem se po čase stane databáze – a tam se donekonečna přidávat nedá, zejména kvůli synchronizaci více databázových strojů.
Ale to už se bavíme o extrémně velkých zátěžích a na ty je možné se obvykle připravit předem. Pak nastupují věci jako AppEngine/AppScale – mimochodem docela hezky řešené.
Ale jsem spíš teoretik, takové velké zátěže jsem ještě nepotkal.
Na vyber su dalsie, dobre skalovatelne moznosti:
– round robin DNS load balancing
– source IP hash-based load balancing pomocou iptables alebo pf
Keby Statisticky urad SR nemal takeho neschopneho dodavatela IS (http://www.partnersoft.sk), tak ta druha moznost hravo zvlada spickovu zataz na redundantnom firewalle na http://volbysr.sk este v predoslych prezidentskych volbach, samozrejme LIVE. Akurat sa z toho vsetci naokolo pokakali kvoli „utoku hackerov“ na ich server, ktory bol umiestneny na ich stary 1 Mb uplink. :-)
Předpokládám, že „Projekt s nejvýše tisíci návštěvníky denně“ je jen nadsázka. Dva tisíce návštěvníků pro web na Python+PostgreSQL (ne Django) mi uneslo s přehledem staré Pentium 4 a ještě zvládalo zhruba stejné množství práce s ostatními weby.
Rozhodně díky za seriál, podíval jsem se aspoň, jak se řeší věci „jinde“.
Ano, jedná se o pouhý odhad. Záleží to na mnoha faktorech, kupříkladu složitost aplikace, rozložení návštěvníků v průběhu dne, nebo objem dat v databázi. Mimochodem označením „Projekt s nejvýše tisíci návštěvníky denně“ jsem měl na mysli 0–9999 návštěvníků denně, což je ještě v toleranci toho, co jste dával za příklad.
Že to záleží na různých faktorech je mi vcelku jasné, proto mne překvapila ta tisícovka – chápáno jako 1000 návštěvníků denně by to byla pro Django velmi špatná vizitka.
Chápu a děkuji za osvětlení.
Ještě jsem zapomněl zmínit konstantu
CACHE_MIDDLEWARE_ANONYMOUS_ONLY
, která vypíná kešování u přihlášených uživatelů. U nich je kešování vhodné potlačit, protože pak mohou nastat problémy se zobrazováním neaktuální verze stránek. (Např. uživatel zadá údaje do formuláře a poté se mu zobrazí předchozí verze.) Navíc to nedává moc smysl, kešovat v takových případech.Není to to samé, jako odeslání komentáře a obecně odeslání jakéhokoliv formuláře, který změní výpis stránky?
Odesílání formuláře a vlastně jakékoliv
POST
dotazy se nekešují, ale například zobrazení daného formuláře ano, tedy není to stejné.