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.
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
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
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
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
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
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
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