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, 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.
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 |
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.
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.
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.
Cześć. Dziś luźny artykuł, bo dziś pobawimy się jedną z pierwszy wersji Pythona. Skompilujemy go i zobaczymy co tam w… Read More
Nowy rok czas zacząć! Więc lećmy z podsumowaniem. Nowy artykuł Nie uwierzycie, ale pojawił się na blogu nowy artykuł! Piszę… Read More
Cześć! W Pythonie 3.13 dodano JITa! JIT, czyli just-in-time compiler to optymalizacja na która Python naprawdę długo czekał. Na nasze… Read More
Cześć! Zapraszam na podsumowanie roku 2024. Książki W sumie rok 2024 był pod względem ilości książek nieco podobny do roku… Read More
Podtrzymując tradycję, prawie regularnych podsumowań, zapraszam na wpis! Nie mogło obyć się bez Karkonoszy We wrześniu odwiedziłem z kolegą Karkonosze,… Read More
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
Pokaż komentarze
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