Mateusz Mazurek – programista z pasją

Czyli o użyciu Pythona i kilku innych technologii do tworzenia świetnej jakości aplikacji w oparciu o stabilny proces dostarczania oprogramowania.

Programowanie Programowanie webowe

Redis i Python – dobrze dobrana para #4

Cześć.

Ostatni wpis wprowadzał nam listy, bazy oraz TTL’a. Dziś postawimy kilka kolejnych kroków, przybliżających nas do poznania Redisa.

Bez przydługich wstępów – zaczynajmy.

Zbiory

Zbiory, podobnie jak w innych rozwiązaniach, to kolekcje unikalnych, nie zachowujących kolejności, elementów. W przypadku Redisa – stringów.

Dwie podstawowe komendy służące do pracy ze zbiorami to SADD i SMEMBERS. Pierwsza z nich dodaje element do zbioru a druga pobiera elementy. Zerknij na kod:

1
2
3
4
5
6
7
8
9
from redis import Redis

redis_connection = Redis(decode_responses=True)

redis_connection.sadd("key", "val1")
redis_connection.sadd("key", "val2")
redis_connection.sadd("key", "val3")

print(redis_connection.smembers("key"))

Który, zgodnie z zasadą działania zbiorów, zwróci za każdym uruchomieniem, elementy w innej kolejności.

Jak łatwo się domyśleć, na zbiorach można wykonywać pewne operacje znane z matematyki np. część wspólna zbiorów, różnica zbiorów itp.

Wspomnianą różnicę zbiorów możemy osiągnąć stosując polecenie SDIFF, część wspólną zbiorów uzyskamy poleceniem SINTER a suma zbiorów to polecenie SUNION.

Gdzie zysk? To co przychodzi mi od razu na myśl to złożoność obliczeniowa tych operacji. Raz że jest dobra a dwa że zawsze jest podana w dokumentacji i np.:

  • SDIFF – O(N) where N is the total number of elements in all given sets.
  • SINTER – O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.
  • SUNION – O(N) where N is the total number of elements in all given sets.

Dostępność tych komend pozwala nam robić operacje na zbiorach bez wyciągania ich z bazy – co oszczędza chociażby pamięć RAM, bo nie musimy trzymać tych elementów w pamięci. Ponadto, operacje typu SDIFF mają warianty (SDIFFSTORE) pozwalając od razu zapisać rezultat pod konkretny klucz, bez konieczności pobierania go do programu.

Posortowane zbiory

Redis udostępnia pewna wariację zbiorów, jaką jest posortowany zbiór, czyli sorted set. W tym typie danych, do każdego elementu zbioru, przypisana jest pewna wartość numeryczna, zwana score, używana do zachowania kolejności. Elementy w zbiorze są posortowane rosnąco względem tej wartości. Na potrzeby wpisu będziemy mówić na tę wartość „waga”.

Siłą tych zbiorów jest, ponownie, złożoność obliczeniowa. Podstawowe polecenia oscyluja w okolicach złożoności liniowej.

Komendą ZADD dodajemy elementy a ZRANGE pobieramy je. Zerknij na kod niżej.

1
2
3
4
5
6
7
8
9
10
from redis import Redis

redis_connection = Redis(decode_responses=True)

redis_connection.zadd("sorted_set_key", {"key1": 1})
redis_connection.zadd("sorted_set_key", {"key2": 2})
redis_connection.zadd("sorted_set_key", {"key3": 3})
redis_connection.zadd("sorted_set_key", {"key4": 4})

print(redis_connection.zrange("sorted_set_key", 0, -1))

Którego efektem jest

['key1', 'key2', 'key3', 'key4']

Jeśli zmodyfikujemy wagi elementów np. na:

1
2
3
4
5
6
7
8
9
10
from redis import Redis

redis_connection = Redis(decode_responses=True)

redis_connection.zadd("sorted_set_key", {"key1": 5})
redis_connection.zadd("sorted_set_key", {"key2": 2})
redis_connection.zadd("sorted_set_key", {"key3": 6})
redis_connection.zadd("sorted_set_key", {"key4": 10})

print(redis_connection.zrange("sorted_set_key", 0, -1))

To otrzymamy kolejność zgodną z rosnącym porządkowaniem elementów, czyli:

['key2', 'key1', 'key3', 'key4']

Skoro mamy do czynienia z kolekcją, która zachowuje kolejność, to nasuwa się oczywiście pytanie: Co jeśli zdefiniujemy tę samą wartość score kilka razy? Jaka wtedy będzie kolejność?

Spieszę z odpowiedzią: klucze wtedy będą posortowane alfabetycznie. Taką sytuację pokazuje kod:

1
2
3
4
5
6
7
8
9
10
from redis import Redis

redis_connection = Redis(decode_responses=True)

redis_connection.zadd("sorted_set_key", {"key2": 1})
redis_connection.zadd("sorted_set_key", {"key1": 1})
redis_connection.zadd("sorted_set_key", {"key4": 1})
redis_connection.zadd("sorted_set_key", {"key3": 1})

print(redis_connection.zrange("sorted_set_key", 0, -1))

którego efektem będzie:

['key1', 'key2', 'key3', 'key4']

Komenda ZRANGE pozwala dostać się do wag. Wystarczy, że kod wyżej, nieco zmodyfikujemy, dodając do zrange nazwany parametr withscores o wartości True. Efektem programu po zmianie będzie:

[('key1', 1.0), ('key2', 1.0), ('key3', 1.0), ('key4', 1.0)]

Z ciekawych rzeczy, to istnieją komendy ZPOPMAX oraz ZPOPMIN (oraz ich blokujące odpowiedniki o których naturze wspomniałem w poprzednim artykule). Pozwalają one pobrać element o największej lub najmniejsze wadze.

Poleceniem ZINCRBY możemy zwiększyć wagę konkretnego elementu, ZCOUNT służy do wybrania elementów z pomiędzy zadanego przedziału wag a ZSCORE po prostu zwróci wagę podanego elementu.

Zmierzając do brzegu

Część czwarta to kolejne kilka metrów wgłąb Redisa, a dokładniej typów danych, jakie on oferuje. Poznaliśmy zbiory oraz ich wariację – zbiory posortowane. Niewątpliwą ich zaletą jest to ze złożoność obliczeniowa większości operacji jest bliska liniowej, co pozwala tworzyć wydajne aplikacje.

Dzięki za wizytę,
Mateusz Mazurek

A może wolisz nowości na mail?

Subskrybuj
Powiadom o
guest

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.

2 komentarzy
Inline Feedbacks
View all comments

[…] Redis i Python – dobrze dobrana para #4 […]

[…] Poprzedni wpis z tej serii w zasadzie wyczerpał podstawowe typy danych dostępne w Redisie. W tym artykule przyjrzymy się więc mechanizmowi pub-sub. Skrót ten rozwija się do publish-subscribe i jest on implementacją wzorca o tej samej nazwie. […]