Mateusz Mazurek – programista z pasją

Python, architektura, ciekawostki ze świata IT

Programowanie Programowanie webowe

MongoDB a Redis

Cześć.

Dziś wpis nawiązujący do zakończonej kilkanaście dni temu serii o Redisie i Pythonie. Wspomniany projekt to 10 artykułów prowadzących czytelnika od podstaw po zaawansowane zagadnienia w temacie wykorzystania nosql’owej bazy danych o nazwie Redis z poziomu języka Python. W czasie pracy nad tym projektem uświadomiłem sobie, że Redis, mimo, że jest szalenie popularny, to nie jedynym wiodącym rozwiązaniem na rynku. Ta luka w spójności przekazu trochę mi przeszkadzała więc dziś ją uzupełniam.

Zaczynajmy.

MongoDB

MongoDB, podobnie jak Redis, jest nosql’ową bazą danych. Pierwsza stabilna wersja pojawiła się w 2009 roku i powoli ale skutecznie zdobywała sympatyków. W przeciwieństwie do Redisa, który jest bazą typu klucz-wartość, Mongo jest bazą dokumentową. Dane składowane są jako dokumenty w stylu JSON, co umożliwia aplikacjom bardziej naturalne ich przetwarzanie, przy zachowaniu możliwości tworzenia hierarchii oraz indeksowania.

Mongo posiada możliwość tworzenia konkretnych baz, w których możemy tworzyć kolekcje a do nich dodawać owe dokumenty.

Zapis danych

W Redisie możemy wybrać jakiego typu danych użyjemy. Chcemy po prostu zbiór elementów? Wybieramy set. Chcemy pojedynczą wartość? Wybieramy string. Chcemy słownik? Wybieramy hash. I tak dalej…. W Mongo natomiast możemy stworzyć kolekcję a w niej nasze dokumenty. I to tyle. Więc jak widać mamy tu mniej elastyczności. Ale nim weźmiesz to za wadę – doczytaj do końca.

Żeby dodać dokument do kolekcji w Mongo możemy posłużyć się biblioteką pymongo. Instalujemy ją standardowo pip’em:

1
pip install pymongo

A samo podstawienie Mongo załatwi nam docker:

1
$ docker run --name some-mongo -p 27017:27017 -d mongo

I w sumie te dwa polecenia pozwalają nam już na eksperymenty.

Zacznijmy więc od wypełnienia kolekcji o nazwie test_collection:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pymongo import MongoClient
from pymongo.database import Database, Collection

client = MongoClient("localhost", 27017)

test_db: Database = client.test

sample_collection: Collection = test_db.test_collection

sample_collection.insert_many({
    'number': x,
    'sample_value': 'test ' + str(x)
} for x in range(0, 20))

W kodzie wyżej łączymy się z Mongo i używając metody insert_many dodajemy wygenerowane dane. Aby zobaczyć efekt, można skorzystać z klienta mongod – czyli odpowiednika redis-cli. Pierw się do niego podłączamy:

1
$ docker exec -it some-mongo  mongo

i już możemy wykonać:

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
> use test
switched to db test
> db.test_collection
test.test_collection
> db.test_collection.find()
{ "_id" : ObjectId("5fe2380316ebbb487a0b213e"), "number" : 0, "sample_value" : "test 0" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b213f"), "number" : 1, "sample_value" : "test 1" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b2140"), "number" : 2, "sample_value" : "test 2" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b2141"), "number" : 3, "sample_value" : "test 3" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b2142"), "number" : 4, "sample_value" : "test 4" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b2143"), "number" : 5, "sample_value" : "test 5" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b2144"), "number" : 6, "sample_value" : "test 6" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b2145"), "number" : 7, "sample_value" : "test 7" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b2146"), "number" : 8, "sample_value" : "test 8" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b2147"), "number" : 9, "sample_value" : "test 9" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b2148"), "number" : 10, "sample_value" : "test 10" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b2149"), "number" : 11, "sample_value" : "test 11" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b214a"), "number" : 12, "sample_value" : "test 12" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b214b"), "number" : 13, "sample_value" : "test 13" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b214c"), "number" : 14, "sample_value" : "test 14" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b214d"), "number" : 15, "sample_value" : "test 15" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b214e"), "number" : 16, "sample_value" : "test 16" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b214f"), "number" : 17, "sample_value" : "test 17" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b2150"), "number" : 18, "sample_value" : "test 18" }
{ "_id" : ObjectId("5fe2380316ebbb487a0b2151"), "number" : 19, "sample_value" : "test 19" }

Czyli najpierw wybieramy bazę, potem kolekcję a potem wykonujemy metodę find. Już nawet na postawie sposobu pracy z konsolą można zauważyć, że Mongo jest zdecydowanie bardziej obiektowe niż Redis, co raczej można uznać za zaletę.

Ogólnie kolekcje można w jakimś stopniu porównać do tabeli w SQLu. Co sprawia że powyższe pobranie danych można byłoby zapisać jako:

1
SELECT * FROM test_collection

Czekaj, stop!

Podoba Ci się to co tworzę? Jeśli tak to zapraszam Cię do zapisania się na newsletter:
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 :)
Postaw mi kawę na buycoffee.to

Pobieranie danych

Wyżej użyliśmy funkcji find do pobrania wszystkich elementów kolekcji. Zazwyczaj jednak chcemy mieć większy wpływ na to co pobieramy i tutaj Mongo zostawia Redisa w tyle. Ale po kolei. Możemy pobrać np. konkretne elementy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pymongo import MongoClient
from pymongo.database import Database, Collection
from pymongo.collection import ObjectId

client = MongoClient("localhost", 27017)

test_db: Database = client.test

sample_collection: Collection = test_db.test_collection

some_val = sample_collection.find_one({'number': 10})

"""{'_id': ObjectId('5fe2380316ebbb487a0b2148'), 'number': 10, 'sample_value': 'test 10'}"""

some_values = sample_collection.find({'number': {'$in': [1, 5]}})

"""[{'_id': ObjectId('5fe2380316ebbb487a0b213f'), 'number': 1, 'sample_value': 'test 1'},
{'_id': ObjectId('5fe2380316ebbb487a0b2143'), 'number': 5, 'sample_value': 'test 5'}]"""


sample_collection.find_one(ObjectId('5fe2380316ebbb487a0b2148'))

"""{'_id': ObjectId('5fe2380316ebbb487a0b2148'), 'number': 10, 'sample_value': 'test 10'}"""

korzystając z generowanego przez Mongo ObjectId. Ale to nie koniec. Dodajmy inne dane do kolekcji:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from pymongo import MongoClient
from pymongo.database import Database, Collection

client = MongoClient("localhost", 27017)

test_db: Database = client.test

sample_collection: Collection = test_db.test_collection

data = [
    {
        'name': 'Mateusz',
        'surname': 'Mazurek',
        'born_year': 1991,
        'tags': ['blogger', 'programista', 'techlead'],
        'skills': {
            'Python': 6,
            'SQL': 6,
            'MongoDB': 1
        }
    },
    {
        'name': 'Karol',
        'surname': 'Marks',
        'born_year': 1989,
        'tags': ['blogger', 'programista'],
        'skills': {
            'Python': 3,
            'SQL': 2,
            'MongoDB': 4
        }
    },
    {
        'name': 'Daniela',
        'surname': 'Kowalska',
        'born_year': 1996,
        'tags': ['blogger', 'hr'],
        'skills': {
            'Python': 0,
            'SQL': 0,
            'MongoDB': 0
        }
    }
]

sample_collection.insert_many(data)

I zerknij na taki kawałek kodu:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
from pymongo import MongoClient
from pymongo.database import Database, Collection

client = MongoClient("localhost", 27017)

test_db: Database = client.test

sample_collection: Collection = test_db.test_collection

print(list(sample_collection.find({'skills.Python': {'$gt': 2}})))

"""[
   {
      '_id':ObjectId('5fe23f6ac6cb7927c96d6a4f'),
      'name':'Mateusz',
      'surname':'Mazurek',
      'born_year':1991,
      'tags':[
         'blogger',
         'programista',
         'techlead'
      ],
      'skills':{
         'Python':6,
         'SQL':6,
         'MongoDB':1
      }
   },
   {
      '_id':ObjectId('5fe23f6ac6cb7927c96d6a50'),
      'name':'Karol',
      'surname':'Marks',
      'born_year':1989,
      'tags':[
         'blogger',
         'programista'
      ],
      'skills':{
         'Python':3,
         'SQL':2,
         'MongoDB':4
      }
   }
]
"""


print(list(sample_collection.find({'tags': {'$all': ['hr']}})))

"""[
   {
      '_id':ObjectId('5fe23f6ac6cb7927c96d6a51'),
      'name':'Daniela',
      'surname':'Kowalska',
      'born_year':1996,
      'tags':[
         'blogger',
         'hr'
      ],
      'skills':{
         'Python':0,
         'SQL':0,
         'MongoDB':0
      }
   }
]"""


print(list(sample_collection.find({'tags': {'$in': ['hr', 'techlead']}})))

"""[
   {
      '_id':ObjectId('5fe23f6ac6cb7927c96d6a4f'),
      'name':'Mateusz',
      'surname':'Mazurek',
      'born_year':1991,
      'tags':[
         'blogger',
         'programista',
         'techlead'
      ],
      'skills':{
         'Python':6,
         'SQL':6,
         'MongoDB':1
      }
   },
   {
      '_id':ObjectId('5fe23f6ac6cb7927c96d6a51'),
      'name':'Daniela',
      'surname':'Kowalska',
      'born_year':1996,
      'tags':[
         'blogger',
         'hr'
      ],
      'skills':{
         'Python':0,
         'SQL':0,
         'MongoDB':0
      }
   }
]"""

i zauważ jak fajny sposób zapytań udostępnia Mongo. Możesz łatwo pobrać te dokumenty, które np. mają jakąś konkretną wartość w konkretnym polu obiektu. W Redisie tego tak zgrabnie nie zrobisz.

Ale to nie wszystko! Mongo pozwala agregować dane. Czyli coś co w Redisie musiałbyś sobie napisać w LUA, tu masz od tak dostępne:

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
28
from pymongo import MongoClient
from pymongo.database import Database, Collection

client = MongoClient("localhost", 27017)

test_db: Database = client.test

sample_collection: Collection = test_db.test_collection

print(list(sample_collection.aggregate([
    {
        '$match': {
            'skills.Python': {'$gt': 2}
        }
    },
    {
        '$group': {
                '_id': None,
                'average_python_skill': {
                    '$avg': '$skills.Python'
                }
            }
    }
])))

"""
[{'_id': None, 'average_python_skill': 4.5}]
"""

i pozwala wyliczyć np. średną wartość konkretnego pola dla elementów spełniających konkretny warunek. Tutaj wyliczamy średni skill pythonowy dla osób które mają go większego niż 2.

To w końcu Redis czy Mongo lepsze?

Praktycznie cały ten artykuł to zachwyt nad Mongo. Ale ostatnie 10 artykułów to zachwyt nad Redisem. To w końcu które rozwiązanie lepsze? Odpowiedź może być tylko jedna… Oba! Bo to zależy od tego do czego chcesz go wykorzystać. I wiem że brzmi to bardzo poprawnie politycznie, ale te bazy są tak różne, że tylko szaleniec próbowałby jednoznacznie wskazać lepsze rozwiązanie.

Zmierzając do brzegu

MongoDB nie używam komercyjnie i podstaw tej bazy nauczyłem się na potrzeby tego wpisu. Nie żałuję ani chwili poświęconej na to, bo to bardzo interesujące rozwiązanie. Jeśli sądzisz, że warto wspomnieć o jakimś ficzerze Mongo, którego po prostu być może nie znam, to nie krepuj się, zostaw komentarz.

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.

5 komentarzy
Inline Feedbacks
View all comments
Someone

Artykuł lekki, dość rzeczowy. Oprócz technologii warto w przyszłości wspomnieć o porównaniu także przypadków użycia.

Drobna uwaga: nie wplatajmy polityki czy jak niektórzy mogliby powiedzieć: ideologii do tematów technologicznych, bo jest to jednak mało profesjonalne.

Pozdrawiam i życzę czasu i siły na kolejne artykuły.

Aleks

wydaje się oczywiste, że Karol Marks w użytkownikach…

[…] MongoDB a Redis […]