Redis i Python – dobrze dobrana para #8

Cześć.

Tak jak pisałem w poprzednim artykule z tej serii, podstawowe zagadnienia związane z Redisem i jego użyciem z poziomu Pythona mamy za sobą. Ten wpis, jak i dwa ostatnie, to już tematy które przekraczają magiczną barierę podpisaną „podstawy” wgłębiając się w nieco bardziej zaawansowane zagadnienia. Dzisiejszy blog post będzie traktował o skryptach które można napisać w języku Lua. Takie skrypty można wysłać jako komendę do Redisa i wykonać. To tak w dużym skrócie. Ciekawe? No to zapraszam na dalszą część wpisu. Ale, ale! Nim przejdziesz dalej, upewnij się że nic Ci nie uciekło po drodze, bo to już 8ma część serii!

Czym jest Lua?

Lua to skryptowy język programowania. Pierwotnie projektowany był w celu rozszerzenia funkcjonalności różnych aplikacji, jednak jest często używany jako samodzielny język. Składnia tego języka jest prosta i przypomina składnię większości języków programowania. W odróżnieniu od Pythona, Lua jest daleki od podejścia „batteries included”, co sprawia, że pełny interpreter tego języka to zaledwie 247 kB. A to natomiast powoduje, że w pełni pasuje do tego do czego był tworzony – do rozszerzania możliwości innych aplikacji. I dokładnie w takim kontekście wykorzystuje go Redis.

Przykład

Bo przykład powie więcej niż 1000 słów.

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

redis_connection = Redis(decode_responses=True, db=0)


script = """
return "test"
"""


print(redis_connection.eval(script, 0))

Powyższy kod wykonuję komendę EVAL, przekazując w niej po prostu ciało skryptu napisanego w LUA, Redis odbiera ten kod, wykonuje go i zwraca rezultat. Efektem wykonania się tego kodu jest wyświetlenie słowa „test”.

Drugi argument w poleceniu EVAL to 0 i określa on ilość argumentów które można przekazać do skryptu. Jeśli podamy tam wartość „n” to kolejne „n” argumentów które przekażemy do funkcji będą przekazane również do skryptu. W skrypcie będą one dostępne w tabeli KEYS (uwaga – Lua indeksuje tabele od 1!). Wszystko przekazane po określonej liczbie „n”, zostanie przekazane do tabeli ARGV.

Zerknij na kod poniżej:

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

redis_connection = Redis(decode_responses=True, db=0)


script = """
return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}
"""


print(redis_connection.eval(script, 2, 1, 2, 'first', 'second'))

Określamy tutaj że dwa pierwsze parametry będą dostępne w skrypcie w tabeli KEYS a kolejne – w tabeli ARGV. Sam skrypt odczytuje przekazane dane i zwraca je jako tablicę. Wynik tego kawałka kodu to oczywiście:

['1', '2', 'first', 'second']

Oczywiście Lua umie wszystko co inne języki programowania, więc nic nie stoi na przeszkodzie by np. wygenerować sobie w nim listę 10 liczb:

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

redis_connection = Redis(decode_responses=True, db=0)

script = """
local arr = {}
for i = 0, 10 do
  arr[i] = i
end
return arr
"""


print(redis_connection.eval(script, 0))# lista od 1 do 10

albo pracować z formatem danych JSON:

1
2
3
4
5
6
7
8
9
10
11
12
from redis import Redis
from json import dumps

redis_connection = Redis(decode_responses=True, db=0)

script = """
local json_data = KEYS[1]
local decoded_data = cjson.decode(json_data)
return decoded_data['a'] + decoded_data['b']
"""


print(redis_connection.eval(script, 1, dumps({'a': 1, 'b': 6}))) # wynik to 7

No dobra, ale po co to?

Skrypty w Redisie pojawiły się po to by rozwiązać problem sytuacji kiedy potrzebujemy coś pobrać z Redisa, przetworzyć i zapisać wynik z powrotem lub co gorsza, rozpropagować go po większej ilości kluczy. Celem ogólnym jest zmniejszenie ilości interakcji po sieci przez przeniesienie części logiki właśnie do Redisa.


Czekaj, stop!

Podoba Ci się to co tworzę? Jeśli tak to zapraszam Cię do zapisania się na newsletter:

Aby potwierdzić swoją subskrypcję, odbierz pocztę i kliknij w link potwierdzający:) jeśli maila nie ma to poczekaj chwile i/lub sprawdź folder spam/inne/oferty itp :)

Aby potwierdzić swoją subskrypcję, odbierz pocztę i kliknij w link potwierdzający:) jeśli maila nie ma to poczekaj chwile i/lub sprawdź folder spam/inne/oferty itp :)
a w ramach prezentu otrzymasz całkowicie za darmo, dwa dokumenty PDF „6 (nie zawsze oczywistych) błędów popełnianych podczas nauki programowania” który jest jednym z efektów ponad siedmioletniej pracy i obserwacji rozwoju niejednego programisty oraz „Wstęp do testowania w Pythonie”, będący wprowadzeniem do biblioteki PyTest.
Jeśli to Cię interesuje to zapraszam również na swoje social media.

Jak i do ewentualnego postawienia mi kawy :)

Żeby można było tego dokonać, trzeba umieć wykonywać komendy Redisowe ze skryptów Lua. I znów, najłatwiej będzie na przykładzie:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from redis import Redis

redis_connection = Redis(decode_responses=True, db=0)

redis_connection.set("key1", 10)

script = """
local arg1 = redis.call('get','key1')
redis.call('set', 'key2', arg1 + KEYS[1])
return nil
"""


print(redis_connection.eval(script, 1, 5)) # None, ze względu na "return nil"
print(redis_connection.get("key2")) # 15, bo 10 + 5 = 15 ;)

Powyższy kod kazał Redisowi wykonać skrypt w którym odczytuję wartość z pod klucza „key1”, dodaję do niej wartość przekazaną jako argument i zapisuję nową wartość w kluczu o nazwie „key2”. Poza „redis.call” istnieje jeszcze „redis.pcall”, pozwalający na inne podejście do ewentualnej obsługi błędów, po więcej szczegółów odsyłam jednak do dokumentacji.

Bardziej realny przykład

Przeanalizuj taki kod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from redis import Redis

redis_connection = Redis(decode_responses=True, db=0)

permission = 'ADD_BOOKING'

redis_connection.sadd("users_group:2", *list(range(0, 50)))

redis_connection.sadd('permissions', permission)

# dotąd jest przygotowanie danych

add_permission_script = """
local is_valid_permission = redis.call('sismember', 'permissions', KEYS[2])
if is_valid_permission == 1 then
  local users = redis.call('smembers','users_group:'..KEYS[1])
  for _, user in ipairs(users) do
      redis.call('sadd', 'user_permissions:'..user, KEYS[2])
  end
  return true
else
  return false
end
"""



print(redis_connection.eval(add_permission_script, 2, 2, permission))

Ten kod dodaje konkretne uprawnienie wszystkim użytkownikom którzy są w zadanej grupie. Początek tego snippetu to przygotowanie danych, określamy nazwę uprawnienia, wypełniamy grupę użytkownikami (ich ID oczywiście) i dodajemy uprawnienie do listy przechowującej wszystkie możliwe uprawnienia. Jako argumenty do skryptu Lua przekazujemy ID grupy i uprawnienie które chcemy rozpropagować. Sam skrypt pierw sprawdza czy uprawnienie jest poprawne (znajduje się w zbiorze poprawnych) i jeśli tak to dla każdego użytkownika który należy do przekazanej grupy, dodaje uprawnienie. Ewentualna duplikację uprawnień załatwiamy odpowiednim typem danych, klucz „user_permissions:ID” jest SETtem, więc nie pozwala na duplikacje.

Podobną logikę można zrealizować w Pythonie oczywiście, ale zwiększa to ilość danych które są przesyłane przez sieć, bo rozbiło by nam to tę sytuację na minimum 3 odpytania Redisa. I to zakładając, że ktoś ostatni wpis rzetelnie przeczytał. A i skrypty są w pełni atomowe!

Ale czekaj.. Przecież ten skrypt jest długi. A pewnie szybko i dłuższe byłyby konieczne. To co z tym oszczędzaniem na przesyłaniu danych?

Cache skryptów

Przesyłanie za każdym razem ciała skryptu nie dawałoby oszczędności na ilości danych przesyłanych przez sieć. Redis pozwala na zapisanie go u siebie i posługiwanie się skrótem SHA w celu jego uruchomienia. Skrypt zapisujemy komendą SCRIPT LOAD gdzie jako argument podajemy skrypt a zwraca on nam skrót, którego możemy użyć w celu uruchomienia naszego kawałka kodu LUA:

1
2
sha = redis_connection.script_load(add_permission_script)
print(redis_connection.evalsha(sha, 2, 2, permission))

Zamieniając EVAL na EVALSHA.

Podsumowując

Przeszliśmy sobie przez jedną z ciekawszych opcji jakie oferuje Redis. Skrypty to potężne narzędzie, które pozwala na grupowanie kodu i oszczędność w kontekście ilości danych przesyłanych przez sieć.

Dzięki za wizytę,
Mateusz Mazurek
Mateusz M.

Ostatnie wpisy

Podsumowanie: maj, czerwiec, lipiec i sierpień 2024

Oj daaawnoo mnie tu nie było. Ale wakacje to był czas dużej liczby intensywnych wyjazdów i tak naprawdę, dopiero jakoś… Read More

3 miesiące ago

Podsumowanie: kwiecień 2024

Cześć! Zapraszam na krótkie podsumowanie kwietnia. Wyjazd do Niemiec A dokładniej pod granicę z Francją. Chrześnica miała pierwszą komunię. Po… Read More

7 miesięcy ago

Podsumowanie: luty i marzec 2024

Ostatnio tygodnie były tak bardzo wypełnione, że nie udało mi się napisać nawet krótkiego podsumowanie. Więc dziś zbiorczo podsumuję luty… Read More

8 miesięcy ago

Podsumowanie: styczeń 2024

Zapraszam na krótkie podsumowanie miesiąca. Książki W styczniu przeczytałem "Homo Deus: Historia jutra". Książka łudząco podoba do wcześniejszej książki tego… Read More

10 miesięcy ago

Podsumowanie roku 2023

Cześć! Zapraszam na podsumowanie roku 2023. Książki Zacznijmy od książek. W tym roku cel 35 książek nie został osiągnięty. Niemniej… Read More

11 miesięcy ago

Podsumowanie: grudzień 2023

Zapraszam na krótkie podsumowanie miesiąca. Książki W grudniu skończyłem czytać Mein Kampf. Nudna książka. Ciekawsze fragmenty można by było streścić… Read More

11 miesięcy ago