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:Jeśli to Cię interesuje to zapraszam również na swoje social media.
Jak i do ewentualnego postawienia mi kawy :)
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.
Mateusz Mazurek
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.
Dziękuję! A proszę powiedzieć mi, który element artykułu uznał Pan za ideologiczny?:)
wydaje się oczywiste, że Karol Marks w użytkownikach…
Aaaa faktycznie :D a to całkowity przypadek :D
[…] MongoDB a Redis […]