Mateusz Mazurek – programista z pasją

Blog o Pythonie i kilku innych technologiach. Od developera dla wszystkich.

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.

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 […]