17 October 2011 .NET, HowTo, Javascript, LINQ to SQL, mongodb, NoSQL, Web 2.0 oliver.guhr

Wer sich mit dem Trend? Thema NoSql beschäftigt wird früher oder später auch auf mongoDB stoßen. Was mongoDB für mich spannend macht, ist zu einem die versprochene Performance (auch wenn ich mit normalen Datenbank da noch nie an die Grenzen gestoßen bin) und zum anderen, das versprechen sich von diesem ganzen O/R Mapper Quatsch verabschieden zu können(und da stoße ich ständig an irgendwelche Grenzen).

Robert hatte sich schon hier und hier mit RavenDB beschäftigt und hier erste Ideen zum Design von NoSql Strukturen gesammelt. 

Wer sich jetzt Fragt: Was ist bitte der Unterschied zwischen mongoDB und RavenDB? (abgesehen vom cooleren Namen)

Naja eigentlich ist das ein Thema für einen eigenen Blogeintrag. Um es ganz kurz zu machen: monogoDB scheint schneller zu sein, unterstützt mehr Sprachen und hat deshalb die größere Community wohingen RavenDB die (viel) bessere Integration in die .net Landschaft bietet. Für mich persönlich ist mongoDB interessanter weil ich es auch für kommerzielle Projekte kostenlos nutzen kann.

So, toll… wo ist der Code?

Installieren

  1. Hier lädt man sich mongoDB für Win, OSX, Solaris oder Linux runter: http://www.mongodb.org/downloads
  2. Das Einzige was man selber machen muss ist dieses Verzeichnis anlegen “C:\data\db”
    Den Pfad kann man aber auch konfigurieren, nur wird mongoDB ihn nicht selber anlegen
  3. Unter Windows reicht ein auspacken der Zip Datei und der Doppelklick auf die mongod.exe
    Wenn man will kann man mongoDB auch als Windows Service starten.
  4. Das war einfach Zwinkerndes Smiley
  5. </ol>

     

    image

    Code

    1. Was wir noch brauchen ist der mongoDB .Net Treiber und den gibt’s hier:
    http://www.mongodb.org/display/DOCS/CSharp+Language+Center

    2. Jetzt muss man diese beiden Referenzen einbinden:

    image

    3. So jetzt können wir die Verbindung herstellen:

    MongoServer server = MongoServer.Create(); // connect to localhost                        
    
    MongoDatabase demo = server.GetDatabase("demo");  // connect to database       
    
    MongoCollection<User> users = demo.GetCollection<User>("users");

     

    Mit Zeile 1 bauen wir die Verbindung auf, wenn nichts angegeben ist zu localhost und ohne Nutzername/Passwort. Zeile 3 gibt uns eine Datenbank zurück und Zeile 5 eine “Tabelle”. Mongo verhält sich hier wie eine normale Datenbank.

    4. Daten einfügen

    //insert
    for (int i = 0; i < 10; i++)
    {
      var result = users.Insert<User>(new User { Name = "Karl", Age = 20 });    
    }

    5. Daten lesen

    var userResult = users.FindAll();
    foreach (var item in userResult)
    {
     Console.WriteLine(string.Format("{0} - {1}",item.Name,item.Age));
    }

    6. Datenmodell

    class User
    {
        public ObjectId _id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public List<ObjectId> Tags { get; set; }     
    }

    Ich habe als Typ für die ID ObjectId (kommt mit dem mongo Treiber) und nicht Guid benutzt. Der Treiber kann zwar auch mit Guid umgehen, allerdings wird die dann BASE64 Codiert als Binärfeld in die DB geschrieben und ich vermute einfach mal, dass dann die hauseigene ObjectId schneller ist.

    7. n:m-Beziehungen …

    … sind auch möglich, einfach in dem man das über die ID’s verknüpft. Ob man seine Daten Normalisieren will/ muss ist eine andere Frage, möglich ist es auf jeden Fall.

     

    //setup second collection
    MongoCollection<User> tags = demo.GetCollection<User>("tags"); // add & get a new collecton
    tags.Drop(); 
    List<Tag> initialTags = new List<Tag>() { new Tag("Cool"), new Tag("Fast"), new Tag("Jedi"), new Tag("Ninja") };
    tags.InsertBatch(initialTags);
                
                
    //add tags to a user (many to many) 
    foreach (var item in userResult)
    {
        var randomTags = initialTags.Take(new Random().Next(0, 4)).Select(x=>x._id).ToList(); // add some random tags
        item.Tags = new List<ObjectId>(randomTags);
        users.Save(item); // saves changes to mongo
    }
    class Tag
    {
        public Tag(string name)
        {            
            Name = name;
        }
        public ObjectId _id { get; set; }
        public string Name { get; set; }
    }

     

    Jetzt haben wir zwei “Tabellen” eine mit Nutzern und eine mit Tags die den Nutzern zugeordnet werden können. Diese Bespiel zeigt auch wie man bei NoSql DB’s eigentlich nicht vorgeht. (ist mir erst hinter aufgefallen) Normalerweise würde ich dem Nutzer einfach Tags als Text zuweisen und mir eine Map/Reduce Abfrage bauen, wenn ich z.B. die Tags zählen möchte und das kann so aussehen: http://cookbook.mongodb.org/patterns/count_tags/

    Wie schnell bist du?

    Performace Tests sind immer so eine Sache, weil sie von vielen Faktoren abhängig sind. Allerdings war ich irgendwie neugierig und wollte mal sehen was passiert, wenn ich viele Elemente anlege und wieder lese.
    Also hab ich mal auf verschiede Arten 10000 User angelegt, gelesen und wieder gelöscht:

    int UsersToAdd = 10000;
    //setup some users
    Func<List<User>> getTestUsers = () =>
    {
        var tmp = new List<User>();
        for (int i = 0; i < UsersToAdd; i++)
        {
            tmp.Add(new User { Name = "Karl" + i, Age = 20 });
        }
        return tmp;
    };
    
    //insert           
    Stopwatch insertTimer = new Stopwatch();
    insertTimer.Start();
    foreach (var item in getTestUsers())
    {
        users.Insert<User>(item);
    }
    insertTimer.Stop();
    Console.WriteLine(UsersToAdd + " users added in: " + insertTimer.ElapsedMilliseconds + "ms");
    
    //bulk insert
    insertTimer.Start();
    users.InsertBatch<User>(getTestUsers());
    insertTimer.Stop();
    Console.WriteLine(UsersToAdd + " users added in an Batch: " + insertTimer.ElapsedMilliseconds + "ms");
    
    
    //parallel insert
    insertTimer.Start();
    System.Threading.Tasks.Parallel.ForEach<User>(getTestUsers(), x => users.Insert<User>(x));
    insertTimer.Stop();
    Console.WriteLine(UsersToAdd + " users added parallel in: " + insertTimer.ElapsedMilliseconds + "ms");
    
    //load all 
    insertTimer.Start();
    var all = users.FindAll();
    insertTimer.Stop();
    Console.WriteLine("all users loaded in: " + insertTimer.ElapsedMilliseconds + "ms");
    
    //drop all 
    insertTimer.Start();
    users.Drop();
    insertTimer.Stop();
    Console.WriteLine("all users droped in: " + insertTimer.ElapsedMilliseconds + "ms");

     

    Und hier sind meine Ergebnisse (4GB RAM QuadCore, HDD RAID5, keine SSD)

    image

     

    Verwaltung

    imageZum verwalten von mongoDB gibt es eine Reihe von Tools. Ich hab fürs erste den MongoExplorer genommen. Das schicke Silverlight Tool kann man bequem direkt von der Webseite aus installieren.

    http://mongoexplorer.com/

     

     

    image

     

    Abfragen

    Zum Thema Abfragen und Map/Reduce lohnt es sich einen eigenen Artikel zu schreiben. Nur so viel: steuern kann man mongoDB komplett mit Javascript. Mir persönlich hat diese ziemlich gute Präsentation viele Fragen beantwortet: