NoSQL

Ruch NoSQL promuje klasę nierelacyjnych baz jako alternatywę dla tradycyjnych baz relacyjnych, nazywając je czasami bazami następnej generacji. Bazy te unikają poleceń JOIN, nie posiadają sztywnych schematów oraz cechują się dobrą skalowalnością.

Oto kilka rozwiązań zaproponowanych przez ruch NoSQL:

  • Bazy zorientowane dokumentowo:
    • MongoDB (SourceForge, SugarCRM, EA, The New York Times)
    • Apache CouchDB
  • Bazy typu klucz/wartość:
    • BigTable (Google App Engine)
    • Dynamo (Amazon Web Services)
    • Apache Cassandra (Facebook)
    • Project Voldemort (LinkedIn)

MongoDB

MongoDB to bardzo wydajna i skalowalna baza danych, zorientowana na przechowywanie dokumentów z pominięciem schematów. Projekt został wydany na licencji AGPL w wersji 3* i można z niego korzystać w aplikacjach biznesowych.

MongoDB jest bazą nowej generacji, która jest zorientowana na przechowywanie dokumentów JSON, o dowolnej strukturze. Dokumenty te są przechowywane wewnętrznie jako BSON – Binary JSON. Całość została napisana w języku Cpp. Bazę stworzono z myślą o pracy w dużych obciążeniach oraz posiada wbudowane mechanizmy skalowania i replikacji W przypadku instalacji 32-bitowej rozmiar pojedynczej bazy danych jest ograniczony do około 2GB.

MongoDB składa się z trzech komponentów:

  • mongod - serwer bazodanowy
  • mongos
  • mongo - powłoka shell'owa umożliwiająca administrację bazą danych

Sterowniki

Dostęp do bazy możliwy jest przy wykorzystaniu sterowników, które oficjalnie w tej chwili są dostępne dla języków:

  • C
  • Cpp
  • Java
  • JavaScript
  • Perl
  • PHP
  • Python
  • Ruby

oraz poprzez sterowniki nieoficjalne, które wspierają:

  • REST
  • C# i .NET
  • Clojure
  • ColdFusion
  • Delphi
  • Erlang
  • Factor
  • Fantom
  • F#
  • Go
  • Groovy
  • Haskell
  • Lua
  • Node.js
  • PowerShell
  • Scala
  • Scheme
  • Smalltalk

Baza dokumentowa

W bazach zorientowanych na dokumenty, dane nie są przechowywane w tabelach o z góry założonej strukturze i takich samych polach dla każdego rekordu. Każdy dokument może mieć różne pola, a dane w każdym polu mogą być zbiorem innych danych. Puste pola nie są w dokumencie w ogóle przechowywane, dzięki czemu ich nagłówki nie marnują miejsca w bazie.

Przykład:

FirstName="Bob", Address="5 Oak St.", Hobby="sailing"
FirstName="Jonathan", Address="15 Wanamassa Point Road", Children=("Michael,10", "Jennifer,8", "Samantha,5", "Elena,2").

BSON pozwala również na przechowywane plików binarnych.

Oto prosty przykład reprezentacji tych samych danych w formacie XML:

<?xml version="1.0" encoding="utf-8"?>
        <dane>
                <user>
                        <imie>jan</imie>
                        <nazwisko>Kowalski</nazwisko>
                </user>
                <user>
                        <imie>Piotr</imie>
                        <nazwisko>Nowak</nazwisko>
                </user>
        </dane>

oraz JSON:

{
        "dane" : {
                "user" : [
                        {
                        "imie" : "Jan",
                        "nazwisko" : "Kowalski"
                        },
                        { 
                        "imie" : "Piotr",
                        "nazwisko" : "Nowak"
                        }
                ]
        }
}

Instalacja na systemie Windows

  1. Wchodzimy na stronę MongoDB
  2. Ściągamy binarną wersję MongoDB odpowiednio:
    • 32-bitową dla systemu 32 bitowego
    • 64-bitową dla systemu 64 bitowego
  3. Wypakowujemy ściągnięty plik do wybranej przez nas lokalizacji.
  4. Tworzymy foldery w których MongoDB będzie przechowywać dane. MongoDB nie stworzy tych folderów automatycznie, więc musimy to zrobić ręcznie. W katalogu głównym dysku, na który wypakowaliśmy ściągnięty wcześniej plik MongoDB, tworzymy następujące foldery:
    • data
    • data\db
  5. Uruchomienie i połączenie z serwerem.

Podczas pierwszego uruchomienia korzystamy z plików: mongod.exe oraz mongo.exe, które znajdują się w folderze bin w miejscu gdzie wypakowaliśmy binarną wersję MongoDB. Najpierw uruchamiamy plik mongod.exe, czyli serwer bazodanowy, a następnie mongo.exe czyli powłokę administracyjną.

Jak widać nie musimy sami tworzyć bazy. Zostanie ona utworzona automatycznie, po dokonaniu pierwszego wpisu.

GridFS

MongoDB pozwala na przechowywanie plików binarnych. Rozmiar obiektów BSON jest ograniczony do 4 MB. GridFS dostarcza mechanizmu, który pozwala na podzielenie dużych plików pomiędzy kilka dokumentów. Dzięki temu możemy przechowywać większe pliki np.: filmowe w bazie, a przy okazji dostępne są operacje działające na podanym przez nas zakresie np.: możemy pobrać pierwsze N bajtów pliku.

Replikacja

MongoDB wspiera replikację danych pomiędzy serwerami dla zapewnienia redundancji oraz zwiększenia niezawodności. MongoDB powinien zawsze być rozlokowany na co najmniej dwóch serwerach: master i slave. Master może odczytywać i zapisywać, a slave kopiuje od niego dane i może być użyty tylko do odczytu i kopii bezpieczeństwa.

Przykład uruchomienia pary master i slave lokalnie:

  • tworzymy katalogi w miejscu gdzie znajduje się plik mongod.exe :
    • dbs/slave
    • dbs/master
  • uruchamiamy plik mongod.exe dwukrotnie z parametrami:
    • –master –port 10000 –dbpath dbs/master
    • –slave –port 10001 –dbpath dbs/slave –source localhost:10000

nazwy katalogów możemy oczywiście podać dowolne pod warunkiem, że ustawimy odpowiednie ścieżki jako parametry przy wywołaniu pliku mongod.exe.

Jeżeli jakieś operacje zostaną wykonane na serwerze master, serwer slave zreplikuje wszelkie zmiany w danych.

Sharding

MongoDB skaluje się poziomo, co znaczy że wykorzystuje siłę przetwarzania rozproszonego. System zwany shadringiem, jest podobny do BigTable and PNUTS. Dane są dzielone i zostają rozdystrybuowane pomiędzy kilka shardów (shard jest to serwer master z co najmniej jednym serwerem slave). Dla aplikacji nie ma różnicy czy komunikuje się z sharem czy też z pojedynczą bazą danych, ponieważ wszystkie żądania przechodzą przez proces z nazwie mongos. Proces ten wie, jakie dane znajdują się na jakim shadrze i przekierowuje odpowiednio żądanie klienta.

Sharding automatycznie dostosowuje zmiany odnośnie obciążenia i dystrybucji danych pomiędzy klastrami oraz zapewnia mechanizm fail-over. Dzięki temu, że każdy shard zawiera jakąś ciągłą porcję danych z konkretnej kolekcji, mamy bardzo szybki dostęp do danych oraz możemy czytać równolegle z różnych serwerów. Skalowalność takiej bazy danych jest praktycznie nielimitowana, co jest wręcz niemożliwe do osiągnięcia przy typowych RDBS. MongoDB świetnie nadaje się do pracy w chmurze, jest jednym z rozwiązań, które może naśladować googleowe BigTable czy amazonowe SimpleDB.

Do równoległego przetwarzania danych przygotowano mechanizm Map/Reduce, który umożliwia wykonywanie równoległych zapytań, sortowanie i dokonywanie innych operacji na kolekcjach.

Podstawy korzystania z shella mongo

Uruchamiamy powłokę shellową. Domyślnie zostaniemy połączeni z bazą „test” na hoście lokalnym.

„connecting to:” wskazuje nam nazwę bazy z której korzysta shell. Aby zmienić bazę danych wpisujemy np.:

> use mydb
switched to db mydb

wpisując powyższe polecenie przeszliśmy z bazy o nazwie „test” do bazy „mydb”.

Przyglądając się następnym przykładom, warto zwrócić uwagę, że nigdy ręcznie nie tworzymy bazy danych lub kolekcji. MongoDB zajmie się tym za nas, zaraz po tym jak wydamy polecenie wstawienia danych do bazy. MongoDB stworzy bazę jeśli do tej pory taka nie istnieje i uzupełni ją danymi. Jeśli chcemy pobrać coś z kolekcji, która nie istnieje, MongoDB traktuje to jako pustą kolekcję.

Jeśli zmieniamy bazę danych korzystając z komendy use, to baza nie zostanie utworzona od razu, lecz przy pierwszym wstawieniu do niej informacji.

Aby wyświetlić już istniejące bazy danych korzystamy z polecenia:

> show dbs

Wstawiane danych do kolekcji

Stworzymy teraz dwa obiekty: j oraz t, a następnie zapiszemy je w kolekcji things.

Utworzenie obiektu j:

 j = { name : "mongo" };  

Utworzenie obiektu t:

 t = { x : 3 }; 

Zapisanie obiektu j w kolekcji things:

 db.things.save(j);  

Zapisanie obiektu t w kolekcji things:

 db.things.save(t);   

Wypisanie zawartości kolekcji things:

 db.things.find();  

możemy również dodać większą ilość danych za pomocą pętli for:

> for (var i = 1; i <= 20; i++) db.things.save({x : 4, j : i});
> db.things.find();
{ "_id" : ObjectId("4c2209f9f3924d31102bd84a"), "name" : "mongo" }
{ "_id" : ObjectId("4c2209fef3924d31102bd84b"), "x" : 3 }
{ "_id" : ObjectId("4c220a42f3924d31102bd856"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("4c220a42f3924d31102bd857"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("4c220a42f3924d31102bd858"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("4c220a42f3924d31102bd859"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("4c220a42f3924d31102bd85a"), "x" : 4, "j" : 5 }
{ "_id" : ObjectId("4c220a42f3924d31102bd85b"), "x" : 4, "j" : 6 }
{ "_id" : ObjectId("4c220a42f3924d31102bd85c"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("4c220a42f3924d31102bd85d"), "x" : 4, "j" : 8 }
{ "_id" : ObjectId("4c220a42f3924d31102bd85e"), "x" : 4, "j" : 9 }
{ "_id" : ObjectId("4c220a42f3924d31102bd85f"), "x" : 4, "j" : 10 }
{ "_id" : ObjectId("4c220a42f3924d31102bd860"), "x" : 4, "j" : 11 }
{ "_id" : ObjectId("4c220a42f3924d31102bd861"), "x" : 4, "j" : 12 }
{ "_id" : ObjectId("4c220a42f3924d31102bd862"), "x" : 4, "j" : 13 }
{ "_id" : ObjectId("4c220a42f3924d31102bd863"), "x" : 4, "j" : 14 }
{ "_id" : ObjectId("4c220a42f3924d31102bd864"), "x" : 4, "j" : 15 }
{ "_id" : ObjectId("4c220a42f3924d31102bd865"), "x" : 4, "j" : 16 }
{ "_id" : ObjectId("4c220a42f3924d31102bd866"), "x" : 4, "j" : 17 }
{ "_id" : ObjectId("4c220a42f3924d31102bd867"), "x" : 4, "j" : 18 }
has more

Napisem has more, shell informuje nas o tym, że nie wszystkie dane zostały wyświetlone. Aby przejrzeć kolejną część, musimy wpisać polecenie:

> it
{ "_id" : ObjectId("4c220a42f3924d31102bd868"), "x" : 4, "j" : 19 }
{ "_id" : ObjectId("4c220a42f3924d31102bd869"), "x" : 4, "j" : 20 }

Wyciąganie danych z bazy

Zagadnienie to jest łatwiej przedstawić na przykładach niż wyjaśnić, dlatego poniżej zostaną podane przykładowe zapytania napisane w języku SQL, a zaraz pod nimi sposób reprezentacji tego samego zapytania korzystając z MongoDB poprzez nakładkę shellową.

SELECT * FROM things WHERE name=„mongo”

> db.things.find({name:"mongo"}).forEach(printjson);
{ "_id" : ObjectId("4c2209f9f3924d31102bd84a"), "name" : "mongo" }

SELECT * FROM things WHERE x=4

> db.things.find({x:4}).forEach(printjson);

SELECT j FROM things WHERE x=4

> db.things.find({x:4}, {j:true}).forEach(printjson);

Jak widać zapis: { a:A, b:B, …} oznacza: „where a=A and b=B and …”

Pole _id jest generowane automatycznie i zwracane po każdym wykonaniu zapytania.

Funkcja findOne()

Funkcja findOne() przyjmuje te same parametry co funkcja find(), ale zwraca albo tylko pierwszy dokument zwrócony z bazy danych, albo null jeśli żaden dokument nie odpowiada zapytaniu.

Przykład:

> printjson(db.things.findOne({name:"mongo"}));
{ "_id" : ObjectId("4c2209f9f3924d31102bd84a"), "name" : "mongo" }

jest to ekwiwalent zapytania:

find({name:"mongo"}).limit(1);

Funkcja limit()

Funkcja limit() pozwala ograniczyć nam ilość zwróconych wyników. Jest szczególnie polecana, ze względów wydajnościowych. Pozwala ograniczyć pracę bazy danych i ogranicza liczbę danych przesyłanych przez sieć.

Oto przykład:

> db.things.find().limit(3);
{ "_id" : ObjectId("4c2209f9f3924d31102bd84a"), "name" : "mongo" }
{ "_id" : ObjectId("4c2209fef3924d31102bd84b"), "x" : 3 }
{ "_id" : ObjectId("4c220a42f3924d31102bd856"), "x" : 4, "j" : 1 }

Indeksy

Indeksy niejednokrotnie drastycznie poprawiają wydajność bazy danych. Bardzo ważne jest to, aby zastanowić się jakie zapytania będzie wykonywała nasza aplikacja, dzięki czemu będziemy mogli określić istotne indeksy. Indeksy w MongoDB są koncepcyjnie podobne do tych w relacyjnych bazach danych jak MySQL. Tworzymy je w tych samych sytuacjach, w których utworzylibyśmy je np.: w MySQLu.

W MongoDB indeksem może być wszystko – dowolne pola dokumentu, a także części tablic zagnieżdżonych w dokumencie. Każda kolekcja posiada domyślny indeks specjalny _id, który tworzony jest automatycznie i nie można go usunąć.

Tworzenie nowego indeksu:

db.things.ensureIndex({'author':1});

Tworzenie indeksu po całej kolekcji:

db.posts.ensureIndex({ 'comments': 1});

oraz jej jednym elemencie:

db.posts.ensudeIndex({ 'comments.author': 1});

Indeksy mogą się składać z wielu pól:

db.posts.ensureIndex({'author':1, 'date';-1});

a za ich pomocą możemy zapewnić sobie unikalność danych w kolekcji:

db.posts.ensureIndex({'date':1}, {unique: true});

Aby usunąć wszystkie indeksy z danej kolekcji wpisujemy:

db.collection.dropIndexes();

Aby usunąć pojedynczy indeks:

db.collection.dropIndex({x: 1, y: -1})

Ciekawe funkcje pomocy

Jeżeli jesteśmy ciekawi co robi dana funkcja shellowa wystarczy, że wpiszemy jej nazwę w powłoce i zostanie wyświetlone jej źródło:

> printjson
function (x) {
    print(tojson(x));
}

Mongo jest powłoką w pełni JavaScriptową, więc dowolna funkcja JavaScriptowa, składnia lub klasa może zostać użyta w powłoce.

Przykład:

> db.eval(function(name) { return "Hello, "+name; }, ["Joe"])
Hello, Joe

PHP i MongoDB

Konfiguracja środowiska

  1. Pobieramy i instalujemy wybraną wersję php
  2. Ze strony http://github.com/mongodb/mongo-php-driver/downloads pobieramy odpowiedni sterownik dla naszej wersji php:
    • VC6 jest dla Apache a VC9 dla IIS.
    • Thread safe jest dla typowej instalacji php (moduł Apache), non-thread dla CGI.
  3. Pobrany plik rozpakowujemy i znajdujący się w nim plik o nazwie: php_mongo.dll kopiujemy do folderu ext znajdującego się w folderze domowym instalacji php
  4. Otwieramy plik php.ini i dopisujemy linijkę:
    extension=php_mongo.dll
  5. Uruchamiamy serwer php.
  6. Uruchamiamy serwer mongod

Podstawy współpracy PHP z MongoDB

Połączenie z bazą danych 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 MongoDB.

W kolejnym kroku wybieramy bazę danych z którą będziemy współpracować:

 
//test jest nazwą bazy danych
$db = $mongo->test;

W wypadku, gdy baza o podanej nazwie nie istnieje to zostanie utworzona nowa. Tak samo dzieje się w przypadku kolekcji.

//foo jest nazwą kolekcji
$collection = $db->foo;

Dodanie przykładowych danych wygląda następująco:

$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);

Jak widać operacja ta jest bardzo prosta. Warto zaznaczyć, że nie ma znaczenia jakie pola umieścimy w każdym z dokumentów, ponieważ nawet w obrębie jednej kolekcji mogą być one całkowicie różne.

Możemy też skorzystać z metody batchInsert(array $documents) która jednorazowo doda kilka dokumentów.

Pobieranie danych możemy zrealizować następująco:

// 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);

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

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

Usuwanie danych wygląda tak:

$collection->remove($criteria);

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

Uaktualnianie danych
<code=php>
$collection→update($criteria, $newDoc); </code> gdzie $criteria to tablica w identycznym formacie jak podczas wyszukiwania danych, a $newDoc to dane które mają zastąpić stary dokument.

Indeksy odczuwalnie przyspieszają wyszukiwanie, nieznacznie zwalniają dodawanie i aktualizacje. Są obecne w relacyjnych bazach i są obecne także w MongoDB.

Do dodania indeksu służy metoda ensureIndex() klasy MongoCollection.

Przykład:

// 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));

Prosty przykład aplikacji

Poniżej przedstawiony został kod okazujący działanie php z MongoDB:

//tworzymy obiekt klasy Mongo
$mongo = new Mongo();
 
//wskazujemy bazę danych, na której chcemy operować
$db = $mongo->test;
 
//wskazujemy kolekcję
$collection = $db->foo;
 
//przygotowujemy dane do wstawienia
$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")
);
 
//wstawiamy dane do kolekcji
$collection->insert($doc);
 
//pobieramy wszystkie dane z kolekcji
$rangeQuery = array();
$cursor = $collection->find($rangeQuery);
 
//iterujemy po zwróconych wynikach i wypisujemy je
foreach ($cursor as $document) {
    print_r($document);
}
 
//usówamy wstawione dane
$collection->remove($doc);

Plik z kodem do ściągnięcia: tutaj

Podsumowanie

Projekt MongoDB zyskał na popularności po kilku udanych wdrożeniach. Portal Veoh wykorzystuje MongoDb do przechowywania około 100MB plików wideo, a ServerDensity przeszedł z MySQL na rzecz MongoDB w swojej flagowej aplikacji do monitoringu serwerów.

MongoDB z jednej strony trzyma dane jak CauchDB, czyli w postaci hierarchicznych dokumentów, korzystając przy tym z JSONowej składni. Z drugiej strony posiada funkcję find(), która może przyjąć wyrażenie regularne, jako parametr do odfiltrowania danych. Baza posiada również indeksy. Czyli jednym zdaniem: obiektowo, hierarchicznie, JSONowo, nie SQLowo ale z możliwością robienia zapytań.

Analogie do baz SQL:

MongoDB Baza SQL
Dokument Wiersz / Rekord
Kolekcja Tablica
_id Klucz główny
Zagnieżdżenie Relacja 1:N
Tablica referencji do obiektów Relacja M:N
Indeks Indeks

Zastosowania:

  • Tak:
    • Źródło danych dla serwisu internetowego: MongoDB bardzo szybko operuje na danych, świetnie się skaluje oraz posiada mechanizmy replikacji, które są wymagane dla dużych serwisów internetowych.
    • Cache'owanie: wydajność Mongo można wykorzystać jako cache danych w np. infrastrukturze intranetowej. Dzięki trwałemu keszowaniu po restarcie systemu, czy bazy sieć nie zostanie przeciążona generowaniem cache'a.
    • Duża ilość mało wartościowych danych: w przypadku napływu bardzo dużej ilości „mało ważnych” danych (np. logi) przetrzymywanie ich w tradycyjnej bazie staje się nieefektywne i zazwyczaj trzeba obsługiwać to na poziomie plików. MongoDB można wykorzystać w prosty sposób jako magazyn tego typu danych.
    • Problemy wymagające wysokiej skalowalności, wydajności i rozszerzalności: MongoDB dobrze czuje się w wieloserwerowych konfiguracjach. Zaimplementowano również silnik map/reduce.
    • rozwiązania GIS
  • Nie:
    • Wszędzie tam gdzie wymagana jest wysoka transakcyjność
    • Przy problemach wymagających języka SQL

Bibliografia

pl/dydaktyka/ztb/2010/projekty/nosql_mongodb/start.txt · ostatnio zmienione: 2017/07/17 08:08 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0