Přejít k navigační liště

Zdroják » Různé » Scukařina, žádná dřina – díl třetí: Djangová je náš táta

Scukařina, žádná dřina – díl třetí: Djangová je náš táta

Články a Různé

V třetím díle Scukařiny vám mladý poutník Scuk dot cz představí účinný nástroj na hubení dotěrného hmyzu, odhalí temné denormalizační praktiky a jako dezert nám naservíruje špetku Javascriptu, map a geografie. No to bude vzrůšo! Předpokládáme, že znáte hlavní postavu a víte, kdo je vrah. Pokud ne, navštivte prosím předchozí díl!

Mucholapka

Tak. Máme hotový čupr systém, jedna vlastnost lepší než druhá. Vystavíme jej slavnostně na web a po čtvrthodině máme na lopatkách. Co se stalo? Aplikace přetížila server a hostingová společnost vám ji odstřelila.

Jedním z výkonnostních problémů často bývá počet a složitost SQL dotazů, které na vykreslení stránky potřebujete vykonat. Jak ale v rozrostlé aplikaci identifikovat problémová místa? Aplikací django-debug-toolbar!

Po nainstalování se vpravo objeví panel, ze kterého vyčtete zajímavé informace, včetně počtu SQL dotazů a době jejich zpracování:

Django debug toolbar nasazený na Scuku.

Po rozkliknutí sekce „SQL“ získáte kompletní seznam, přičemž u každého řádku můžete zjistit další podrobnosti:

  • čas vykonání dotazu,
  • výsledek dotazu (SELECT),
  • analýzu dotazu (EXPLAIN),
  • referenci na místo ve zdrojovém kódu, kde byl dotaz vykonán (Toggle Stacktrace),
  • samotný SQL dotaz,
  • vizuální vyjádření doby zpracování dotazu (viz čára nad dotazem),

django-debug-toolbar toho ale umí víc – viz video Django Debug Toolbar 0.8 od Idan Gazit na Vimeo.

Vyzkoušejte ho!

Denormalizace

Téma, které úzce souvisí s předchozí kapitolou. Začněme tentokrát příkladem z praxe:

Scuk je průvodce po místech, kde se dobře jí a nakupuje. Slovo „dobře“ z technického pohledu znamená, že veřejnosti v katalogu zobrazujeme pouze ty podniky, které splní následující parametry:

  • Musí mít alespoň jednu recenzi.
  • Recenze musí podnik dostatečně silně ohodnotit.
  • Samotná recenze musí být důvěryhodná.

Tři různé podmínky pro jediný podnik, přičemž v její formulaci se bavíme nejen o podniku, ale i o počtu recenzí, jejich síle a důvěryhodnosti recenzenta. Procházka minimálně po třech modelech. Je jisté, že pokud bychom měli takto podrobnou analýzu provádět s každým requestem, odrazí se to negativně na výkonnosti celé aplikace.

Můžeme si ale pomoci denormalizací — praktikou, která nám při určitých změnách v databázi automaticky ukládá pomocné informace k modelu. Podívejte se na následující obrázek:

Aktuální počet recenzí bychom mohli zjistit například takto:

good_shops = []
for shop in Shop.objects.all().iterator():
    reviews = Review.objects.filter(shop=shop)
    if reviews.count() > 0:
        good_shops.append(shop)
print good_shops

V seznamu good_shops budeme mít všechny podniky, které mají alespoň jednu recenzi (poznámka: na Scuku máme podmínku složitější, protože nás zajímají jen kvalitní recenze). Uvedený algoritmus je ale velmi neefektivní, musí projít všechny podniky a pro každý z nich zjistit počet recenzí.

Díky několika úpravám ale můžeme kód nahradit jediným rychlým dotazem:

Shop.objects.filter(reviews_count__gt=0)

Jak toho dosáhnout?

  1. Přidáme do modelu Shop nové pole reviews_count (s pomocí Jižana, samozřejmě):

    class Shop(models.Model):
        # ... puvodni pole modelu
        reviews_count = models.IntegerField(editable=False)
  2. Vytvoříme soubor reviews/signals.py a vložíme do něj následující kód:

    from django.db.models.signals import post_save, post_delete
    from scuk.reviews.models import Review
    def update_reviews_count(sender, **kwargs):
        # z recenze (instance) vytahneme referenci na podnik
        shop = kwargs.get('instance').shop
        # zjistime aktualni pocet recenzi u podniku
        count = shop.review_set.count()
        # vlozime jej do pole reviews_count
        shop.reviews_count = count
        # ulozime podnik
        shop.save()
    post_save.connect(update_reviews_count, sender=Review, 
                      dispatch_uid='update_reviews_count.post_save')
    post_delete.connect(update_reviews_count, sender=Review, 
                        dispatch_uid='update_reviews_count.post_delete')
  3. Na poslední řádek souboru reviews/models.py přidáme:

    import scuk.reviews.signals

Hotovo. Jakmile dojde k uložení nebo smazání jakékoliv recenze, spustí se funkce update_reviews_count. Ta nejdříve vytáhne referovaný podnik, pak pro něj z DB vytáhne údaj o celkovém počtu recenzí a nakonec počet uloží do instance modelu Shop. Více se o signálech dočtete v oficiální dokumentaci.

Podobným způsobem na Scuku ukládáme informace o počtech recenzí v profilech uživatelů, kategoriích podniku nebo průměrném hodnocení podniku. S jejich pomocí pak můžeme pokládat jednoduché a rychlé SQL dotazy i pro relativně složité vazby.

Poznámka (příjemná): od Djanga 1.2 je možné reagovat i na signály od M2M vazeb.

Poznámka (praktická): kód pro denormalizaci můžete vložit i do modelu, konkrétně do metodsave a delete . V obou případech (s využitím signálu i překrytím) ale musíte pamatovat na to, že některé ORM operace v Djangu signály neodpalují (update) a metody instance nevolají (queryset delete). Tak bacha!

Poznámka (varovná): pamatujte, že denormalizace je temná a Gandalf se za ni na vás bude zlobit (minimálně kvůli porušení principu DRY, či možnosti desynchronizace dat). Podobného (a mnohem čistšího) efektu můžete dosáhnout optimalizací kódu nebo nasazením keše. Je to jen na vás.

S mapou i bez buzoly

Na Scuku máme dvě vyhledávací políčka. První „Co“ prochází štítky, kategorie a titulky podniků, hodnotou v poli „Kde“ pak můžete dotaz zúžit na konkrétní geografickou oblast. Realizace druhého políčka byla paradoxně jednodušší. Stačilo vložit do hrnce službu Google Geocoder, zprovoznit GeoDjango (knihovna rozšiřující Django o geografické služby), zaGooglit a promíchat.

Pokud do pole „Kde“ vložíte nějakou hodnotu (např. adresu „Milíčova, 4, Ostrava“) a kliknete na „Hledat“, klientský Javascriptový kód pošle dotaz na službu Google Geocoder. Odpověď přijde ve formě JSON dat:

{
    u'Status': {
        u'code': 200,
        u'request': u'geocode'
    },
    u'Placemark': [{
        u'Point': {u'coordinates': [18.2891279, 49.836991300000001, 0]},
        u'ExtendedData': {
            u'LatLonBox': {
                u'west': 18.285980299999999,
                u'east': 18.292275499999999,
                u'north': 49.840138899999999,
                u'south': 49.833843700000003
            }
        },
        u'AddressDetails': {
            u'Country': {
                u'CountryName': u'u010ceskxe1 republika',
                u'AdministrativeArea': {
                    u'SubAdministrativeArea': {
                        u'SubAdministrativeAreaName': u'Ostrava',
                        u'Locality': {
                            u'PostalCode': {u'PostalCodeNumber': u'702 00'},
                            u'Thoroughfare': {u'ThoroughfareName': u'Milxedu010dova 1939/4'},
                            u'LocalityName': u'Moravskxe1 Ostrava'}
                        },
                    u'AdministrativeAreaName': u'Moravskoslezskxfd'
                },
                u'CountryNameCode': u'CZ'
            },
            u'Accuracy': 8
        },
        u'id': u'p1',
        u'address': u'Milxedu010dova 1939/4, 702 00 Moravskxe1 Ostrava, u010ceskxe1 republika'
    }],
    u'name': u'Milxedu010dova, 4, Ostrava, u010ceskxe1 republika'
}

Nejzajímavější pasáží je v tomto případě záznam ['Placemark'][0]['ExtendedData']['LatLonBox'], tedy oblast, která odpovídá vašemu dotazu. Stačí tyto informace vyzobnout a přilepit k datům odesílaným na server. Tam se jich chopí Django a vytáhne z DB pouze ty záznamy, které do poptávané oblasti spadají:

geodata = {'lon1': 18.292275499999999, 'lon2': 18.285980299999999, 
           'lat1': 49.840138899999999, 'lat2': 49.833843700000003}
polygon = "POLYGON((%(lon1)f %(lat1)f,%(lon1)f %(lat2)f,%(lon2)f %(lat2)f,
           %(lon2)f %(lat1)f,%(lon1)f %(lat1)f))" % geodata
shops = Shop.objects.filter(point__within=polygon)

Poznámka: within je operátor z GeoDjanga a point je pole modelu Shop, např.:

from django.db import models
from django.contrib.gis.db import models as geomodels
class Shop(models.Model):
    # ...ostatni pole modelu
    point = geomodels.PointField(u"Zeměpisná poloha", blank=True, null=True)

Podobným způsobem ve Scuku probíhá i automatické zařazování podniků do krajů. Pouze s tím rozdílem, že nás nezajímá oblast na mapě, ale podrobné informace o zadané adrese (viz ['Placemark']['AddressDetails']). K provádění dotazů ze strany serveru můžete využít knihovnu geocoders od Simona Willisona.

Předání žezla

Tímto dílem sice tóny Djanga dozněly, ale Scukopříběh žije dál. Příště se dočtete o dobrodružství, které zažil Martin Michálek během kódování HTML stránek (Zdroják přece nečtou jen programátoři… Nebo jo?).

Komentáře

Subscribe
Upozornit na
guest
11 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
petr_praus

Opět díky za skvělý článek, geolokaci v Djangu zrovna řeším a o Geocoder class jsem nevěděl.

Head of Operational Efficiency

tolik prace to da a pak nekdo vymysli jmeno SCUK, ach jo :(

Inkvizitor

Co to vlastně přesně znamená? S Cuketkou?

ja

scuk je sraz

pkroh

Myslel jsem si to… a taky myslím že pak máte trochu problém, protože tento dětinský slang „ahojky, určo si dáme scuka s Mirčou, Věrčou a Ifčou za hoďku na tramvošce, děcka“ dost lidí docela spolehlivě odpudí.

Inkvizitor

Upřímně řečeno, pokud je to takto (samozřejmě znám slovo „scuk“, akorát jsem doufal, že to vysvětlení bude vtipnější, už proto, že v projektu Pan Cuketka opravdu figuruje), taky se mi ten název nelíbí.

mikiqex

Vysvětlovali to jako „S CUketkou Kvalitně“ :)

Olda

Začíná to na S, bude se to dobře prodávat Seznamu.

m0rph

Nešlo by vyrobit jeden dotaz prostě použitím agregace?
http://docs.djangoproject.com/en/1.2/topics/db/aggregation/#filtering-on-annotations

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.