Archiwum | mongo RSS for this section

Story of simple web service

Couple of years ago, I developed web a service. Service contains embedded web server, and is using MongoDB as its data source. All back-end code is C++ based. Everything works perfect.

Some time later

Two years past. My service was used internally by my co-workers. But new functionality has to be added. So I look at the old code, trying to estimate work needed. In the mean time, new MongoDB driver shows up. Driver is using C++11 capabilities, and is much much better, comparing to the old one.

Dependency hell

Machine, on which my service is running, is pretty old Debian distribution. I could update to new one, but unfortunately new Debian does not contain C++11 libraries for MongoDB.

My laptop works on newest Ubuntu distribution – same story.

There are more problems , „old” mongo driver is using Boost. Old version of Boost. I probably could recompile driver with newest Boost libraries. Probably.

To rewrite or not to rewrite

So. I had two choices:

  • Install old Debian on virtual machine and add required functionalities using VirtualBox. Sinking deeper and deeper into technological dept.
  • Rewrite service for newest MongoDB driver.

I decided to try second option. What I don’t like, is another ssh-based session of tweaking server machine. And reinstalls every major libraries update.

Docker

While searching through Google, I found mention of Docker. It could be solution for my problems. So I started research.

I love it.

Not a virtual machine

Docker is often described as light-weight virtual machine. I think description is not precise, and misleading. Docker (among other things) separates process (processes). Process is running in sandbox, any interactions between host, or another docker’s hosted processes has to be defined during first run command.

Image

Image is a snapshot of file system. It contains complete collection of files to run concrete process inside. For example. Image for MongoDb database contains very thin, but complete Linux distribution, plus all MongoDb files required to run database server. Image is read-only template, you can’t run image. Docker transforms image into Container.

One of cool features of Docker its layered structure. Let say, I want to build image containing MongoDb database server. First I have to choose base Linux distribution. Ubuntu 14.04 for example.

My image will consists two layers:

  • First – base Ubuntu layer
  • Second – MongoDb server layer. This layer will contain only CHANGED files by MongoDb installation process. So layer will be quite small

Now. I can create another image. Lets say a PostgreSQL server. As base image I’ll again use Ubuntu 14.04. Base Ubuntu image will be reused by both MongoDb and PostgreSQL images. Pretty neat, right?

Container

Image is a read only template. Container is writable instance of image. Image to Container is like class to object. Docker can run many containers based on single image.

Container can be transformed back into image. So created image can be base for another container. And so on.

Container has it’s own file system which is (surprise, surprise) a layer over image’s file system.

Container can be created from image using ‚docker run’ command.

Single process. Usually

Docker image contains command, which will be called after container start. Usually command starts single process. But nothing stands in the way to run script which starts multiple processes.

When command returns – docker will stop the container.

Tricky DNS

By default container files:

/etc/resolv.conf
/etc/hosts
/etc/hostname 

are overlayed by relevant files of host system. Don’t be surprised when changes make in those files disappears.

Dockerfile

Container can be transformed into image using ‚docker commit’ command. But usually images are created by Dockerfile.

Dockerfile is a recipe, contains rules and commands executed on base image leading to destination image.

Images are build using

docker build

command. Created image is placed inside local docker storage.

Order matters!

Commands are processed in the same order as they are placed in Dockerfile.

FROM

FROM ubuntu:latest

This line informs docker that images is based on latest release of Ubuntu. If image is not found locally, Docker will search and download from Docker Hub.

RUN

RUN apt-get -y update

Runs command on image.

EXPOSE

EXPOSE 80

Informs docker that image expose TCP port. Host can redirect port into another one. The way host threat exposed ports is defined by ‚docker run’ command

COPY

Copies local files into image.

COPY ./file_layer /
file_layer
├── usr
│   ├── bin
│   └── local
└── var
    └── lib

Inside file_layer directory I can emulate root directory. Files are going to be copied inside image. Eventually replacing image’s original ones.

WORKDIR

WORKDIR /var/lib

Command sets working directory for image initial command.

ENTRYPOINT

Entry command for Container.

ENTRTPOINT /bin/bash

Usually command starts Docker-hosted process, MongoDB for example. Before running command, docker sets working directory (WORKDDIR).

Story continues

Back to story of my web service.

I’ve updated kernel on my server. Installed Docker.

Also installed Docker on my development laptop.

Now I can create and test images on my development machine. When ready, image will be moved to production machine.

MongoDB libraries

Because Debian nor Ubuntu contains C++ drivers for MongoDb. I’m using COPY command to copy them from my development laptop into image.

ldconfig

Coping libraries into file structure in not enough. Libraries cache has to be rebuild using ldconfig command:

RUN ldconfig

LDAP

My services is using LDAP authentication. So proper packages has to be installed:

RUN apt-get -y libldap-2.4.2

Service start

ENTRYPOINT /usr/bin/myservice

Deployment

So I have my image created. No I need to transport it into destination machine. There are couple ways to do it.

  • Image can be saved as tar archive using docker export command. Tar archive can be imported into Docker using docker load command.
  • Image can be pushed into Docker Hub.
  • Image can be pushed into private repository

I like this trick:

docker save <image> | bzip2 | pv | ssh user@host 'bunzip2 | docker load'

Run it!

Image can be transformed into container, using docker run command.

docker run <image name>

Docker run command is very important. Defines behavior of the container. Single image can be run many times, each time transformed into different container. For example, using Docker there can be 10 instances of MongoDB running concurrently on single host machine. But each instance requires different hosts resources. TCP port number, for example, has to be different.

This fine-tuning of image is done by run command. I suggest you to consult docker documentation.

Happy end

My service was deployed and now it’s running. I have build automated system for build and deployment of images. Linux distribution on my server is meaningless. I can use any Linux distribution inside my images.

MongoDb i C#

W dzisiejszym wpisie postaram się przybliżyć bazę MongoDB i sposób korzystania z niej za pomocą C#. Nie widzę jakiegoś wielkiego zainteresowania tą bazą w polskiej blogosferze, a szkoda.

MongoDb jest obecnie chyba najbardziej popularną bazą danych NoSQL.
Co to jest NoSQL? Jak sama nazwa wskazuje jest to silnik który nie używa SQLa do zapytań.
Po co komu baza, która posiada jakiś autorski dialekt zapytań, i nie korzysta z dobrze znanej składni SQLa? Otóż są zastosowania w których bazy NoSQL sprawują doskonale. Wszędzie, gdzie mamy dużo (lub bardzo dużo) danych, mało (najlepiej w ogóle) relacji, warto zastanowić się nad Mongo.

Trzeba zaznaczyć, że MongoDB nie jest „silver bullet”, próba zastąpienia relacyjnej bazy danych w systemie mającym relacje między tablicami zakończy się wielkim rozczarowaniem uzyskaną wydajnością.

Tworzeniem i wsparciem silnika MongoDb zajmuje się firma 10gen. I robi to bardzo dobrze. Dostępne są drivery dla wszystkich liczących się języków programowania, driver dla .NET jest przyzwoicie napisany. Kilka miesięcy temu zostało dodane wsparcie dla Linq. Kod pozwalający na połączenie z bazą, dodanie kilku dokumentów do kolekcji, to zaledwie kilka linii.

Wspomniałem już, że Mongo nie jest bazą relacyjną. Co prawda, na upartego można próbować na piechotę łączyć dane z różnych kolekcji, ale wydajność takich operacji będzie niska.

Dane przechowywane są w formacie JSON, a dokładnie w klonie tego formatu pozwalającym na zapis danych binarnych, czyli BSON.
Dokumenty BSON są przechowywane we wspomnianych już kolekcjach. Na upartego można porównać dokument Mongo z rekordem relacyjnej bazy danych, a kolekcję z tablicą. I tu trzeba wspomnieć o rzeczy bardzo ważnej – kolekcje nie posiadają zadanego sztywnego schematu, można w nich przechowywać dowolne dokumenty BSON.

Niestety Mongo posiada także i ograniczenia. Uruchomione na 32-bitowym systemie, pozwala na przechowanie nieco ponad 2GB danych.
Jest to związane ze sposobem, w jaki dane są zapisywane w plikach bazy. Mongo mapuje plik w pamięci, a 32-bitowy system operacyjny nie jest w stanie zaadresować większych obszarów. Tak więc, jeśli baza przekroczy 2GB (cała baza, czyli dane + indeksy), mongo zakończy pracę mało przyjemnym błędem.

Jeśli komuś chodzi po głowie deployment bazy wraz z desktopowym klientem, niech się lepiej dobrze zastanowi.

Uruchomienie mongo jest bardzo proste:

mongod --dbpath "sciezka do folderu z danymi" --logpath "sciezka do folderu na logi"

aby zainstalować mongo jako usługę systemową należy dodać parametr –install do tych powyżej
Zainstalowaną usługę warto uruchomić:

net start MongoDB

Polecenia trzeba wykonywać za pomocą linii komend z uprawieniami administracyjnymi.

Jak już wspomniałem, dokument mongo nie ma ustalonego schematu. Do kolekcji można dodać dowolne dane, byle były opakowane w JSON.

Wszystko pięknie, ale czasami wygodnie jest zdefiniować sobie klasę danych, która będzie automatycznie serializowana i deserializowana, tak jak robią to nHibernate, czy Entity Framework.

Sterownik dla .NET zapewnia taką funkcjonalność.

pubic class KlasaA
{
  public ObjectId _id {get; set;}
  public List<KlasaB> ObiektyKlasyB {get; set;}
}

Jak widać, dane mogą być zagnieżdżone. KlasaA może zawierać listę obiektów klasy B

KlasaA, jako „root” powinna mieć zdefiniowane pole które będzie identyfikatorem. Najprościej jest dodać właściwość _id typu ObjectId. Ale za pomocą atrybutów można wyznaczyć praktycznie dowolne pole aby było identyfikatorem.

Atrybutów którymi można udekorować klasy i ich właściwości jest znacznie więcej, znajdują one się w przestrzeni nazw MongoDB.Bson.Serialization.Attributes. Bardzo przydatnym atrybutem jest BsonIgnoreExtraElements, dzięki któremu nie jest wyrzucany wyjątek, kiedy klasa nie zawiera pola które znajduje się dokumencie JSON.

Połączenie z bazą:

var connectionString = "mongodb://localhost";
var client = new MongoClient(connectionString);
var server = client.GetServer();
var database = server.GetDatabase("NAZWA_BAZY_DANYCH");

Baz danych możne być oczywiście kilka na serwerze.

Zdefiniuję teraz prostą encję:

public class User
{
  public ObjectId _id {get; set;}
  public string Name {get; set;}
}

Pobieramy kolekcję:

var users = database.GetCollection<User>("Users");

Dodajemy dokument do kolekcji

var user = new User() {Name="Arek"};
users.Insert(user);

Zapytanie:

var arki = users.AsQueryable().Where(u=> u == "Arek");

Niestety, na dzień dzisiejszy nie wszystkie funkcje LINQ są wspierane. Aby na
przykład pogrupować dokumenty trzeba się trochę nagimnastykować. Trzeba skorzystać z JavaScriptu. Takie zapytania to dosyć obszerny temat, postaram się je opisać w osobnym wpisie.

Jeśli pola używamy jako kryterium wyszukiwania, warto założyć na nim indeks. Poprawi to znacznie wydajność zapytań.

Robi się to bardzo łatwo:

users.EnsureIndex("Name");

To wszystko jeśli chodzi o podstawowe informacje. Mam nadzieję, że wystarczą do uruchomienia i rozpoczęcia eksperymentów z Mongo. A kolejnej notce omówię
grupowanie danych, i prace z klasę BsonDocument.