Podsumowanie ostatnich miesięcy

Autor: nme · niedziela, 25 Lipiec, 2010 · Brak komentarzy ·

Bardziej spostrzegawczy którzy tutaj zaglądają pewnie zauważyli, że od kilku miesięcy nie pojawił się na tym blogu żaden wpis — śpieszę więc z wyjaśnieniami.

Na nme.pl, poza podstawowym blogiem, znajduje się jeszcze odrębna kategoria z własnym kanałem RSS — zawierająca wpisy w języku angielskim. Trochę głupio to wyglądało, że widoczna była jedynie pusta kategoria — dlatego trafiło tam kilka wpisów. Z bloga zniknęły linki do mojego minibloga i niedawno założonego mikrobloga. Uznałem, że nie są tu potrzebne — jedynie zaciemniają wizerunek bloga, który miał być z założenia techniczny.

Od mniej więcej miesiąca pracuje sobie nad nowym projektem w Django. Na chwilę obecną aktualny jego stan można zobaczyć na stronie urevs.com. Informacje na temat postępów prac opisuje na blogu pod adresem blog.urevs.com. Projekt w obecnej postaci wydaje się być jeszcze nie zaczęty, ale zawiera już wiele funkcjonalności:

  • możliwość zakładania kont które będą identyfikatorami OpenID (czyli OpenID Provider), podczas zakładania kont wykorzystana została reCAPTCHA
  • Aby zalogować się w serwisie nie trzeba mieć założonego lokalnie konta — można zalogować się poprzez m.in:
  • Serwis obsługuje https, lokalizację językową (obecnie dostępne są języki polski i angielski) i mój bootstrap do rozwijania aplikacji JavaScript i jego rozszerzenie — do generowania CSS'ów.

W międzyczasie muszę się jeszcze zabrać do roboty i dokończyć odświeżoną wersję mojego portalu do zarządzania siecią, którą już kończę przenosić na Django. Zabrałbym się pewnie prędzej, gdyby nie kompletny brak motywacji... W końcu trwa lato... i nie chce się tyle czasu siedzieć nad mądrymi projektami :)

Wygodny storage z użyciem memcache w Django

Autor: nme · środa, 12 Maj, 2010 · Brak komentarzy ·

Czasami zachodzi konieczność napisania na szybko aplikacji w Django która standardowo coś tam trzyma w bazie danych, coś przetwarza i wyświetla. Jeśli aplikacja ma być prosta, a w bazie mają być trzymane proste struktury danych, to czy napewno musimy tyle czasu poświęcać na dopracowanie modeli? Jasne, że nie.

Poniższy kod oferuje nam możliwość trzymania dowolnych danych w postaci klucz-wartość w bazie danych. Dodatkowo — owe dane mogą mieć dowolną postać — może być to tekst, tablica asocjacyjna czy wartość typu Boolean.

Ponadto — biblioteczka ta opiera się o opisaną w poprzednim wpisie obsługę memcache'u w Django — co sprawia, że do póki nie zmienimy wartości danych, a ich obecność w cache'u nie zdąży wygasnąć — nie będziemy w ogóle obciążać naszej bazy danych. Osobiście wykorzystuję ten kod do trzymania ustawień aplikacji. Niektóre ustawienia są typu boolean, inne to stringi, jeszcze inne — tabele. Rozwiązanie sprawdza się świetnie.

Właściwy kod

Modele — models.py:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2009 nme.pl
# Dual licensed under MIT and GPL.
from django.db import models
class Vars (models.Model):
    key = models.CharField(max_length=50)
    value = models.TextField()

Proste prawda? Nie zapomnijcie wydać komendy django-admin syncdb która utworzy odpowiednią tabelę w bazie. Teraz czas na właściwą bibliotekę — vars.py:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2009 nme.pl
# Dual licensed under MIT and GPL.
""" Simple variables (key-value) handling with memcache """
import pickle,base64
import memcache,models
from settings import conf
class __Vars:
    def get (self,key,default=None):
        """ get Vars variable value """
        if memcache.enabled:
            value = memcache.get('va-%s' % key)
            if value != None:
                return pickle.loads(base64.b64decode(str(value)))
        settings = models.Vars.objects.filter(key=key)
        if settings:
            value = settings[0].value
            if value:
                return pickle.loads(base64.b64decode(str(value)))
            else:
                return default
        else:
            return default
    def __getitem__ (self,msg):
        return self.get(key)
    def set (self,key,value):
        """ set Vars variable value """
        value = base64.b64encode(pickle.dumps(value))
        if memcache.enabled:
            memcache.set('va-%s' % key,value)
        settings = models.Vars.objects.filter(key=key)
        if settings:
            setting = settings[0]
            setting.value = value
            setting.save()
        else:
            models.Vars(key=key,value=value).save()
    def has_key (self,key):
        """ return True or False if Vars variable exists """
        settings = models.Vars.objects.filter(key=key)
        if settings:
            return True
        else:
            return False
    def delete (self,key):
        """ deletes given key from Vars and memcache """
        if memcache.enabled:
            memcache.delete('va-%s' % key)
        settings = models.Vars.objects.filter(key=key)
        for i in settings:
            i.delete()
variables = __Vars()

Przykładowe użycie

Zastosowanie biblioteki z poziomu django-admin shell:

In [1]: from vars import variables

In [2]: if not variables.has_key('test'):
   ...:     variables.set('test','tekst')
   ...:
   ...:     

In [3]: print variables.get('test')
tekst

In [4]: variables.set('test',{'asd':1,'data':['struct','ure']})

In [5]: variables.get('test')['data'][1]
Out[5]: 'ure'

In [6]: variables.delete('test')

In [7]: variables.get('test')

In [8]: variables.has_key('test')
Out[8]: False

Jak to działa?

Składowanie dowolnego typu danych w rekordzie bazy zrealizowane jest w oparciu o dwie standardowe biblioteki pythonowe — pickle i base64. Pierwsza z nich oferuje zakodowanie dowolnej struktury danych do stringa, druga natomiast — zastosowanie notacji base64 — przez co zabezpieczamy się przed ewentualnymi problemami z obsługą znaków z którymi nasz bazodanowy backend mógłby mieć ewentualnie problemy. Innymi słowy — rozwiązanie przedstawione powyżej gwarantuje nam pełną przenośność danych.

Zachęcam gorąco do korzystania :)

Memcache w Django: krok ku lepszej skalowalności

Autor: nme · wtorek, 11 Maj, 2010 · Brak komentarzy ·

Podczas tworzenia aplikacji webowych, warto w miarę wcześnie pomyśleć o skali z jaką nasze rozwiązanie będzie miało się w przyszłości zmierzyć. Niezależnie od tego czy owa aplikacja ma pracować w jednym przedsiębiorstwie, czy próbuje zainstnieć szerzej, w sieci — w każdym przypadku może się okazać, że odniesie sukces. To z kolei sprawi, że ilość jej użytkowników urośnie... Firma może się rozwinąć, przejąć konkurencję, aplikacja webowa może się okazać takim strzałem w dziesiątkę jak nie tak dawno temu Nasza-Klasa — nigdy nie wiadomo :) W momencie gdy miałaby go odnieść, nie będzie już zbyt wiele czasu na przebudowę kodu, a napewno nie będzie go na jego całkowitą reorganizację.

Jeśli rozwiązanie jest skryptem uruchamianym od święta — może to być zwykły skrypt CGI. Jeśli jednak miałby być uruchamiany częściej — wartałoby już pomyśleć o mod_php czy mod_python... Albo jeszcze lepiej — fastcgi, a w przypadku pythona — wsgi. Kiedy użycie aplikacji rośnie nadal — frontend mnożymy na kolejne węzły stawiając przed nimi balancera robiącego za reverse proxy. A w backendzie klastrujemy... ale co na styku macierz, klaster? Ilość zapytań do bazy rośnie, io waity zaczynają rosnąć i robi się nieciekawie... Da się to rozwiązać?

Jak to robi Google

Google od jakiegoś czasu pokazuje nam jak powinny być tworzone intensywnie wykorzystywane aplikacje webowe i na jak zorganizowanym backendzie powinny pracować, aby skalowały się najefektywniej. Myślę, że warto z tej wiedzy skorzystać zanim io waity zaczną spędzać nam sen z powiek.

Miałem okazję napisać sobie kilka małych aplikacji które pracują w chmurze na platformie Google — tzw. Google App Engine (w skrócie GAE). Warto było poświęcić trochę czasu aby to rozwiązanie poznać. Łatwiej było mi dzięki temu można zrozumieć między innymi dlaczego poszczególne serwery Google nie są potężnymi serwerami rackowymi z bardzo silnymi procesorami, ale raczej "lżejszymi" serwerkami, ale za to wypełnionymi po brzegi koścmi pamięci.

Dlaczego takie lżejsze maszyny? Poza oszczędnością energii i pieniędzy są one wystarczające do obsługi pythonowych frameworków, w przeciwieństwie do tych PHP'owych.

Google skupia się na Javie i Pythonie, gdzie prawie pewne jest to, że ich rozproszony Datastore — system bazodanowy, został stworzony w oparciu o język Python. Język ten znany jest m.in z wydajności, szczególnie, jeśli osoba która go używa stosuje się do najbardziej podstawowych zasad jak nie allokowanie ogromnej ilości pamięci itp. Dlaczego więc w serwerach Google tyle pamięci? Odpowiedzią jest memcache.

Google chwali się tym, że stworzyli sobie własną implementację cache, inspirowaną oprogramowaniem memcached i zachęca, aby developerzy aplikacji App Engine'owych również korzystali z memcache'a.

Na czym polega memcache

Uproszczony graf przedstawiający działanie memcache'u:

Koncepcja działania jest prosta — zamiast odwoływać się bezpośrednio do bazy danych, odwołujemy się do memcache'a — jeśli trafiliśmy na dane (hit), wykorzystujemy je, jeśli nie (miss), dopiero wtedy odwołujemy się do bazy, jednocześnie zapisując pobrane z bazy dane w naszym cache'u. Dzięki temu prostemu zabiegowi, nasza strona, która podczas każdego odwołania do niej potrzebuje jakichś danych z bazy, jest w stanie funkcjonować praktycznie w ogóle jej o nic nie odpytując — wszystko co potrzebne będzie trzymać w pamięci podręcznej.

Zastosowanie i dokumentacja

Aby wykorzystać memcache w Google App Engine, polecam zapoznanie się z dokumentacją na stronach Google.

A co jeśli chcielibyśmy wykorzystać memcache w Django? Naturalnie — nic nie stoi na przeszkodzie — Django posiada coś takiego jak cache framework, który na dodatek jest bardzo dobrze udokumentowany.

Django samo z siebie jest przystosowane do wykorzystania cache'u przy obsłudze widoków czy template'ów, ale również oferuje dostęp niskopoziomowy — niemalże analogiczny do tego z Google App Engine... Niemalże, bo jakoś wersja Google bardziej przypadła mi do gustu. Dlatego przygotowałem sobie prosty interfejs, który wydaje mi się być odrobinę bardziej logiczny. Kod oczywiście załączam. Mam nadzieję, że komuś się przyda.

Konfiguracja systemu, projektu Django oraz moja niskopoziomowa obsługa

Zakładając, że pracujemy na systemie GNU Debian/Ubuntu oraz Django jest już w systemie jakąś drogą zainstalowane (osobiście w tym akurat przypadku polecam instalację z oryginalnych źródeł nad paczką Django z dystrybucji), doinstalowujemy obługę memcached, włączamy ją i restartujemy demona memcached:

sudo -i
aptitude install memcached python-memcache
sed -i 's/no/yes/' /etc/default/memcached
service memcached restart
exit

teraz w naszym projekcie Django włączamy obsługę cache:

w pliku settings.py:

CACHE_BACKEND = 'memcached://127.0.0.1:11211/'

jeśli chcemy wykorzystać moją małą biblioteczkę poniżej, do settings.py musimy dodać też konfigurację memcache:

class conf:
    class memcache:
        disabled = False
        timeout = 600

Moja niskopoziomowa obsługa memcache.py:

#!/usr/bin/python
# -*- coding: utf-8 -*-
""" cache low-level backend based on memcached """
from django.core.cache import cache
from settings import conf
def get(key, default=None, timeout=conf.memcache.timeout):
    """ returns value for given key from cache, and refreshes it
    in memcache with automatic conversion from unicode
    to str — fix to:
    __import__() argument 1 must be string without null bytes, not str """
    value = cache.get(key, default)

    if value is not default:
        cache.set(key, value, timeout)

    if type(value) is unicode:
        value = str(value)

    return value

def set(key, value, timeout=conf.memcache.timeout):

    """ sets a value for given key in cache """

    cache.set(key, value, timeout)

def delete(key):

    """ remove given key from cache """

    cache.delete(key)

# in the name of syntatic sugar ;)

disabled = bool(conf.memcache.disabled)
enabled = not disabled

A przykład zastosowania pojawi się jutro albo pojutrze — w nowym wpisie.

Dodatkowy wolumin Ext3 jako plik na Fat32

Autor: nme · piątek, 7 Maj, 2010 · Brak komentarzy ·

Wielu użytkowników Linuxa ma zinstalowane dwa systemy operacyjne na notebookach i komputerach stacjonarnych. Nie jestem tutaj wyjątkiem. Jak już mam naklejkę licencyjną to przecież jej nie zdrapię ;) Mojemu Linuxowemu LVM'owi dałem 30 GB, Windowsowi również, a resztę stanowi wolumin FAT32. Kiedyś reszty nie było, ale cóż — musiałem kupić nowego twardziela z powodu podejścia producentów do "ekonomicznego" trybu pracy dysków twardych ;)

Co jednak można zrobić w sytuacji, gdy nagle potrzebujemy trochę więcej przestrzeni dyskowej z obsługą Linuxowych uprawnień? Odpowiedź jest prosta — możemy wykorzystać trochę wolnej przestrzeni FAT32, aby stworzyć sobie na nim wolumin ext3 w postaci zwykłego pliku.

Tworzenie woluminu ext3 w pliku

Aby utworzyć 15 GB wolumin ext3, przechodzimy w linii komend na dysk FAT32 i wydajemy polecenie:

cd /media/[nasz podmontowany wolumin FAT32]/
dd if=/dev/zero of=volume bs=1G count=15

Chwilkę to potrwa, ale ostatecznie powstaje wolumin:

15+0 przeczytanych recordów
15+0 zapisanych recordów
skopiowane 16106127360 bajtów (16 GB), 957,413 s, 16,8 MB/s

Sprawdzamy nasz plik:

du -sh volume

15G	volume

Wszystko się zgadza. Tak przygotowany plik należy teraz sformatować:

mke2fs -j volume

Zostaniemy zapytani, czy napewno chcemy formatować urządzenie które nie jest blokowe — potwierdzamy nasze intencje.

mke2fs 1.41.11 (14-Mar-2010)
volume nie jest specjalnym urządzeniem blokowym.
Kontynuować mimo to? (t,n) t
warning: Unable to get device geometry for volume
Etykieta systemu plików=
Typ OS: Linux
Rozmiar bloku=4096 (log=2)
Rozmiar fragmentu=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
983040 i-węzłów, 3932160 bloków
196608 bloków (5.00%) zarezerwowanych dla superużytkownika
Pierwszy blok danych=0
Maksymalna liczba bloków systemu plików=4026531840
120 grup bloków
32768 bloków w grupie, 32768 fragmentów w grupie
8192 i-węzłów w grupie
Kopie zapasowe superbloku zapisane w blokach:
	32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208
Zapis tablicy i-węzłów: zakończono
Tworzenie kroniki (32768 bloków): wykonano
Zapis superbloków i podsumowania systemu plików: wykonano
Ten system plików będzie automatycznie sprawdzany co każde 39 montowań
lub co 180 dni, zależnie co nastąpi pierwsze. Można to zmienić poprzez
tune2fs -c lub -i.

Montowanie woluminu plikowego

Teraz możemy już podmontować sobie tak stworzony wolumin:

sudo mkdir /mnt/tmp

sudo mount -o loop volume /mnt/tmp

df -h

System plików            rozm. użyte dost. %uż. zamont. na
/dev/mapper/sys-root   22G   11G  9,8G  52% /
[...]
/dev/loop0             15G  166M   14G   2% /mnt/tmp

I to wszystko — wolumin jest już gotowy do użycia :)

Wydajna i bezpieczna zdalna synchronizacja katalogów w Ubuntu

Autor: nme · poniedziałek, 12 Kwiecień, 2010 · Brak komentarzy ·

Będąc między innymi developerem, który pracuję na różnych maszynach, potrzebuje mieć swój kod dostępny na każdej stacji przy której zasiadam.

Przez jakiś czas sprawdzał się rozproszony system kontroli wersji. Wystarczyło robić push i pull poprzez ssh. Z czasem, kiedy np. miałem rozgrzebany kod, nie chciałem robić commita, tworzyć odgałęzienia — zacząłem pakować kod i przesyłać w tej postaci. Przez jakiś czas to znosiłem. Nie było to jednak zbyt przyjemne. Kilka drobnych zmian, a okazywało się, że muszę przesyłać kilka MB danych. Nie narzekałbym, gdybym nie miał w domu łącza niesymetrycznego :)

Z czasem, kiedy ilość projektów nad którymi pracowałem wzrosła, poczułem pewien dyskomfort (prawie jak pani Żanet Kaleta ;)). Tym większy, że zdarzało mi się pracować nad aplikacją i biblioteką jednocześnie w ramach jednego projektu, gdzie oba kody miały swoje indywidualne drzewa kontroli wersji. Nawet jeśli jedno było w podgałęzi drugiego — Mercurial, bo z tego systemu kontroli wersji korzystam — zdawał sobie sprawe, że dany katalog należy już do innego drzewa kontroli wersji.

Co począć w takiej sytuacji? Rozwiązanie przypadkowo podsunął kolega, jednocześnie przypominając mi jak to rozwiązanie efektywnie spisywało się w dystrybucji Gentoo, z której miałem okazję jakiś czas temu przez dłuższy nawet czas korzystać. Chodzi oczywiście o rsync'a.

Niżej przedstawioną metodę praktykuję już od jakiegoś czasu. Synchronizuję się do/z jednej centralnej maszyny. Synchronizuję katalog zawierający 300 MB kodu, źródeł grafik, duże drzewa hg. Nie zdarzyło mi się jeszcze, mimo wykonywania dość drastycznych zmian w podstrukturze, żeby trwało to dłużej niż wcześniej trwało przesłanie spakowanej paczki o rozmiarze 1 czy 2 MB.

Załóżmy, że w katalogu domowym /home/user/dev mam swoje repozytorium. Na maszynie centralnej, dane trzymane będą w katalogu /srv/archive/dev.

Aby wysłać dane z notebooka, na serwer, wydajemy polecenie:
rsync -azv --delete -e ssh /home/user/dev MASZYNA_CENTRALNA:/srv/archive

Aby pobrać dane z serwera na notebooka wydajemy:

rsync -azv --delete -e ssh MASZYNA_CENTRALNA:/srv/archive/dev /home/user

Jeśli nie jesteśmy pewni, czy efekty komendy będą zadowalające, warto dodać parametr --dry-run, który sprawi, że zostanie nam wyświetlona jedynie lista zmian, a nic tak naprawdę nie zostanie zmienione.

Myślę, że warto poświęcić kilka minut aby wewnątrz swojej biblioteczki stworzyć katalog sync, a w nim dwa skrypty send i recv które będą wykonywać te czynności, a gwarantuje, że w zamian zaoszczędzicie znacznie więcej czasu w przyszłości.