MongoDB – Ruch NoSQL przyszłością baz danych?

Każdy z nas korzystał już chyba z relacyjnych baz danych. Nawet jeżeli nie było to MySQL, PostgreSQL czy Oracle to prawdopodobnie spotkaliście się z nimi na zajęciach z informatyki, gdzie był prezentowany Microsoft Access. Nie sposób przedstawić zalet i korzyści wynikających z teorii relacyjnych baz danych i matematycznego podejścia do przechowywania danych.

Niestety czekają nas także liczne ograniczenia. Mnie osobiście zawsze bolało zderzenie obiektowych systemów z relacyjnymi danymi, gdzie bardzo często dochodziło do zastosowania ORM-a (nigdy nie lubiłem, nie lubię i chyba już nie polubię tego rozwiązania nawet pomimo tego, że Doctrine 2.0 szykuje się całkiem smakowicie). Nie przedłużając zbytnio, w końcu wyłoniła się grupa przeciwników systemów RDBMS i standardu SQL z takimi projektami jak Amazon SimpleDB, Cassandra (używane przez Facebook i wkrótce także Digg), CouchDB czy MongoDB.

Nowe podejście to przede wszystkim rezygnacja z ograniczeń narzucanych przez sztywny schemat tabeli. Nasze dane opierają się na dokumentach, które mogą przechowywać dowolne dane w formie key-value. Oznacza to, że jeden element tej samej kolekcji może mieć zupełnie inny zestaw pól niż cała reszta. Brzmi jak bałagan? Niekoniecznie.

Tytuł serwisu brzmi phpgeek, skupmy się więc na potrzebach sieci i standardowym skrypcie – system CMS/blog. Ich autorzy dokładają wiele starań aby przewidzieć jakie dane będzie chciał zamieścić końcowy użytkownik udostępniając rozbudowane API kryjące zaawansowane struktury danych. Relacyjnych danych. Składując dane w nie-relacyjnej bazie nie ma żadnego problemu. Każdy dokument przechowuje dokładnie to co nam potrzebne bez zbędnego rozwodzenia się na tematy, o które nie powinniśmy się martwić.

Pamiętajmy jednak, że NoSQL nie jest panaceum na wszystkie problemy. Dla systemu bankowego czy rezerwacji hotelowej wręcz nie wyobrażam sobie zastosowania podejścia opartego o dokumenty zamiast sprawdzonej matematyki. Niektórzy mogą także skorzystać z rozwiązania hybrydowego, jak na przykład twórcy skinny board. Grunt to znać dostępne narzędzia i wiedzieć kiedy z nich skorzystać.

Stawiamy pierwsze kroki w MongoDB

Bazą, na którą głównie zwróciłem uwagę było MongoDB. Autorzy zapewniają, że zostało zaprojektowane z myślą o wydajności, a przy tym łączy zalety relacyjnych baz danych, posiadających mnóstwo możliwości z koncepcją dokumentów.

Instalacja

Do najfajniejszych nie należy. Nie jest to trudne, nie potrzeba jakiejś tajemnej wiedzy. Wszystko opisane. Po prostu przyzwyczaiłem się do starego ./configure; make; make install. Tu mamy półgotowca do skonfigurowania.

Integracja z PHP

Tytuł bloga zobowiązuje. W tym temacie zdecydowanie na plus. Szybko, łatwo i przyjemnie. Mamy gotowy sterownik napisany w C do instalacji z PECL. Krótkie pecl install mongo, dopisanie extension=mongo.so (albo extension=php_mongo.dll dla Windowsów o ile dobrze pamiętam) do php.ini i zrobione. Dokumentację rozszerzenia znajdziemy na oficjalnych stronach php.net.

Podstawy współpracy PHP z MongoDB

Jako, że nie lubię suchych przykładów z shella zacznijmy od razu od demonstracji działania tego systemu z poziomu naszego kodu.

Miłym mi jest, że całość wykonana została w obiektowy sposób. Od razu przyjemniej się patrzy na taki kod. Połączenie z bazą nawiązujemy tworząc nowy obiekt Mongo.

$mongo = new Mongo();

Opcjonalnie w parametrach konstruktora możemy zdefiniować gdzie znajduje się nasza baza danych. Jeżeli nie podamy nic, wartością domyślną będzie localhost:27017 czyli standardowa instalacja.

Następnym krokiem jest wybranie bazy danych z którą zamierzamy współpracować:

//test jest nazwą bazy danych
$db = $mongo->test;
//lub:
$db = $mongo->selectDB('test');

W wypadku, gdyby baza o podanej nazwie nie istniała… zostanie utworzona nowa. Trochę dziwne i niezbyt pożądane zachowanie. W wypadku literówki zdecydowanie wolałbym dostać wyjątkiem.

Podobnie rzecz ma się z wyborem/tworzeniem tabeli… wroć! Chciałem powiedzieć kolekcji. W MongoDB zamiast tabeli mamy kolekcje, a zamiast rekordów/wierszy – dokumenty.

//foo jest nazwą kolekcji
$collection = $db->foo;
//lub:
$collection = $db->selectCollection('test');

Nie muszę chyba mówić, że wybór bazy i kolekcji możemy załatwić jedną linijką? :)

Dodawanie danych

… nigdy nie było prostsze.  Przykład bezpośrednio z tutoriala na php.net.

$doc = array( "name" => "Lorem Ipsum",
    "type" => "whatever",
    "count" => 1,
    "info" => (object)array( "x" => 203,
        "y" => 102),
    "versions" => array("0.9.7", "0.9.8", "0.9.9")
);

$collection->insert($doc);

Tak, to działa. Tworzenie dokumentów to żaden problem jak widać. Ktoś powie, że to denormalizacja bazy, że tak się nie powinno… A ja powiem, że to bardzo wygodne i wydajne. Wspominałem, że nie ma znaczenia jakie pola umieścimy w każdym z dokumentów? Nawet w obrębie jednej kolekcji mogą być całkowicie różne.

Istnieje też metoda batchInsert(array $documents) która jednorazowo doda kilka dokumentów.

Pobieranie danych

Służą do tego dwie metody klasy MongoCollection: find() i findOne(). Jako pierwszy argument podajemy tablicę z warunkami. Jeżeli tego nie zrobimy zostaną zwrócone wszystkie/pierwszy rekord, tfu! dokument. Drugim argumentem są pola dokumentu, które zamierzamy pobrać.

Generalnie składnia zapytań jest dość… egzotyczna. Klucz tablicy jest nazwą pola, wartość… sami popatrzcie:

// wszystkie dokumenty
$rangeQuery = array();

// i równe 71
$rangeQuery = array( "i" => 71 );

// dokumenty, gdzie 5 < x < 20
$rangeQuery = array('x' => array( '$gt' => 5, '$lt' => 20 ));

$cursor = $collection->find($rangeQuery);

Po całą resztę możliwości w temacie wyszukiwania zapraszam do dokumentacji MongoDB.

Dostęp do pobranych danych

Zwrócone dane są obiektem klasy MongoCursor i zachowują się jak najzwyklejszy iterator:

foreach ($cursor as $document) {
    print_r($document);
}

Usuwanie danych

$collection->remove($criteria);

gdzie $criteria to tablica w identycznym formacie jak podczas wyszukiwania danych.

Uaktualnianie danych

$collection->update($criteria, $newDoc);

gdzie $criteria to tablica w identycznym formacie jak podczas wyszukiwania danych, a $newDoc to dane które mają zastąpić stary dokument.

Dot notation

Podczas prezentacji wstawiania danych pokazałem, że w bardzo łatwy sposób możemy dodawać tablice i hashe (w PHP znane jako tablice asocjacyjne). Jak się okazuje, podczas definiowania kryteriów bardzo łatwo możemy dotrzeć do tych pól stosując . (słownie: kropkę) jako operator przestrzeni (można to tak nazwać?). W ten sposób możemy na przykład odwołać się do wartości info.x z wcześniejszego przykładu.

Indeksy

Rzecz wszystkim znana, odczuwalnie przyspiesza sprawy wyszukiwania, nieznacznie zwalnia dodawanie i aktualizacje. Obecna w relacyjnych bazach, obecna także tutaj.

Do dodania indeksu służy metoda ensureIndex() klasy MongoCollection. Ponownie przykład z php.net.

// create an index on 'x' ascending
$c->ensureIndex(array('x' => 1));

// create an index on 'z' ascending and 'zz' descending
$c->ensureIndex(array('z' => 1, 'zz' => -1));

// create a unique index on 'x'
$c->ensureIndex(array('x' => 1), array("unique" => true));

Podsumowanie

Mam nadzieję, że ukazałem piękno i prostotę takiego podejścia do składowania danych. Nie mam zamiaru ukazywać MongoDB jako złotego środka, zwłaszcza, że nie omówiłem takich rzeczy jak GridFS czy Map/Reduce. Chciałem tylko zachęcić do zapoznania się z nowymi technologiami, poznania ich wad i zalet a przede wszystkim stosowania wtedy, gdy mogą poważnie odciążyć naszą pracę z danymi. Poza tym wymienione bazy oparte o dokumenty są bardzo szybkie, skalowalne i znalazły zastosowanie w wielu poważnych portalach.

Bonus, MySQL wolny od schema

Z takimi bazami na tradycyjnych hostingach prędko się nie spotkamy. Poza tym niektórzy mogą mieć wątpliwości co do niezawodności młodych projektów.

Serwis FriendFeed (czyli rzecz całkiem spora) postanowił zaimplementować obsługę dokumentów wolnych od schematu tabeli w zwykłej bazie MySQL. Oczywiście, nie będzie to tak wydajne i wygodne jak w specjalnie do tego przygotowanym systemie, jednakże u nich spełnia to swoje zadanie.

Miłej lektury.

Ten wpis został opublikowany w kategorii Bazy danych, Biblioteki, MongoDB, MySQL, Narzędzia, PHP. Dodaj zakładkę do bezpośredniego odnośnika.

11 odpowiedzi na „MongoDB – Ruch NoSQL przyszłością baz danych?

  1. Paweł S pisze:

    dzięki za wpis a szczególnie za zwrócenie uwagi, że taki coś jak NoSQL istnieje

  2. piotrooo89 pisze:

    A czy są gdzieś jakieś testy wydajności, żeby na własne oczy się przekonać że naprawdę używanie kolekcji i dokumentów jest lepsze od „standardowych” baz danych?

  3. muezin pisze:

    co z wydajnoscia dla wiekszych struktur?
    jak w tym definiowac cos na ksztalt kluczy obcych, modele jeden do wielu itd?

    da sie? :)

  4. Benchmarki mają to do siebie, że nie do końca odzwierciedlają produkcyjne warunki. Kilka linków znalezionych w internecie:
    http://obvioushints.blogspot.com/2009/07/benchmarking-mongodb-vs-mysql.html
    http://jayant7k.blogspot.com/2009/08/document-oriented-data-stores.html
    Zaletą MongoDB jest rozwijany sharding (chwilowo w fazie alpha), który pozwala na niemal nieograniczoną skalowalność porównywalną do pracy w chmurze. Coś, co w wypadku RDBMS jest rzeczą bardzo trudną do osiągnięcia.

    Pojedynczy shard może składać się z jednego lub dwóch serwerów (aby zapewnić trwały dostęp do danych w wypadku awarii jednego z nich), każdy z shardów przechowuje określoną liczbę danych, którymi zarządza serwer główny.

    Co do większych struktur – MongoDB oferuje GridFS, który w wyniku przekroczenia rozmiaru 1 dokumentu (4 MB) automatycznie podzieli go na mniejsze i obsłuży jak należy poza zasięgiem naszego wzroku (wygodne). Myślę, że nie powinno to wprowadzać jakiegoś większego narzutu. Swego czasu był problem ze sterownikiem dla Ruby, który faktycznie powodował jakieś opóźnienia, ale zdaje się już się uporali.

    Korzystając z baz opartych o dokumenty musimy pozbyć się starych nawyków i zacząć myśleć o naszych danych nieco inaczej. Działając na bazach zgodnych z SQL musieliśmy dbać o to, żeby jak najpóźniej wyjść z relacyjnej warstwy bazy danych (przefiltrować nasze dane w możliwie jak najdokładniejszy sposób opierając się o model matematyczny, a dopiero potem zająć sortowaniem itp. stąd joiny i tego typu konstrukcje). Lepiej ten temat opisuje na przykład książka „SQL. Sztuka programowania” Stephane Faroulta i Peter Robsona wydana przez Helion.

    W najprostszym przypadku możemy zawrzeć tablicę elementów w jednym dokumencie. Bardzo dobrze działa to na przykład w wypadku tagów artykułów. Co ciekawe dokumentacja MongoDB jako jeden z przykładów implementacji drzew podaje… zawarcie wszystkiego w jednym dokumencie. I podobno to nawet działa.

    Innym sposobem może być po prostu przechowywanie identyfikatora innego dokumentu i potem zwykłe odpytanie innej kolekcji o niego. Tutaj nie ma rozdziału na warstwę relacyjną i nierelacyjną. Relacje nie istnieją. Nie musimy się martwić spadkiem wydajności.

    Bardziej ambitną metodą może być skorzystanie z konstrukcji DB Ref która prócz identyfikatora przechowuje także nazwę zewnętrznej kolekcji. Wygodne, jeżeli nie chcemy się martwić skąd pobrać dodatkowe dane. Opisane dokładniej tutaj: http://www.mongodb.org/display/DOCS/DB+Ref sterownik PHP przewiduje to w ten sposób: http://us3.php.net/manual/en/class.mongodbref.php

  5. Się rozpisałem…

    Jeszcze nawiązując do drugiego z zaprezentowanych przeze mnie linków dotyczących wydajności. Zdziwić może rozmiar bazy danych. Co prawda jest to wytłumaczone w tamtym poście, ale na wszelki wypadek:
    MongoDB tworząc pliki przechowujące bazę danych od razu rezerwuje sobie określoną liczbę miejsca aby przyspieszyć działanie. Pierwszy plik z danymi ma 64 MB, następny 128 MB i tak aż do 2 GB. Gdy dalej będą przyrastać dane, za każdym przekroczeniem pojemności pliku będzie tworzony kolejny, o rozmiarze 2 GB. Nawet, jeżeli będzie on w 98% pusty.

  6. Zyx pisze:

    Rozwiązanie wygląda bardzo ciekawie do nieustandaryzowanych danych, gdzie nie mamy do końca informacji o tym, co przechowujemy. Jednak jak się ma kwestia dostępu współbieżnego? Przypuśćmy, że dana operacja musi zmodyfikować dane w jednym dokumencie na podstawie danych drugiego. Jak zapobiec temu, by w międzyczasie ktoś nie próbował zrobić tego samego, doprowadzając do niespójności?

    Osobiście idea mi się podoba, ale jeszcze lepsza byłaby w bezpośrednim połączeniu z bazą relacyjną, co pozwoliłoby korzystać z dobrodziejstw jednego i drugiego naraz.

  7. Cóż, MongoDB jest ubogie w tego typu mechanizmy, więc może być ciężko. Może Cassandra, CouchDB… pojęcia nie mam. Lektura http://www.mongodb.org/display/DOCS/Atomic+Operations + jakieś własne, pomysłowe zabezpieczenie.

    Wiele projektów stosuje rozwiązanie hybrydowe poprzez własne „middleware”. Trochę nie mam pomysłu jakby taka integracja mogła wyglądać z poziomu samej bazy.

  8. matipl pisze:

    Na ostatnim Zimowisku Linuksowym było o nieralycjnych bazach danych na przykładzie CouchDB
    http://zimowisko.macuk.pl/2010/couchdb.pdf

    Myśl wydaje się zacna, i spokojnie możemy zastosować w aplikacjach gdzie przeważają odczyty, chociażby systemy CMS, blogi.
    Przy bardziej zaawansowanych projektach bez transakcji ani rusz.

  9. Co do samych odczytów się nie zgodzę – tego typu bazy nadają się świetnie również na logowanie zdarzeń i statystyki, gdzie masz ogromną liczbę zapisów.

    Kwestia tego, co kto widzi pod pojęciem „zaawansowany”. Każdy chyba robi sobie spis wymagań stawianych bazie i wtedy jest w stanie bezbłędnie zdecydować czy potrzebuje modelu relacyjnego, czy wręcz przeciwnie. Albo może widzi sens wymieszania dwóch typów.

  10. jachu pisze:

    Bardzo ciekawie wygląda, nie miałem jeszcze do czynienia z innymi bazami niż relacyjne.. Czy ktoś to szerzej stosuję?

  11. Pingback: Od Informacji do Wiedzy » Archiwum bloga » mongoDB czyli nie relacyjna baza danych

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

*

Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>