Thomas Bandt

Über mich | Kontakt | Archiv

RavenDB - es hätte schön werden können.

Kinder, wie die Zeit vergeht. Vor anderthalb Jahren habe ich die Entscheidung getroffen, dass es für mein neues Projekt an der Zeit ist, statt der bekannten und geschätzten relationalen Datenbanken NoSQL zu verwenden.

Die primäre Motivation neben der Entdeckungslust eines Techies: Größenwahn und die dafür erforderliche horizontale Skalierbarkeit. Wenige große Server sind teuer (*), viele kleine Server billig, so die Grundannahme. Und RDBMS wie SQL Server skalieren faktisch nur vertikal, auch wenn das wie im Fall von stackoverflow ebenso gut funktionieren kann - aber eben nur mit entsprechenden Investitionen in Hardware.

So begab es sich, dass wir uns nach der Evaluation verschiedener Systeme für RavenDB entschieden, womit wir, nur unterbrochen von einer kurzen Episode MongoDB, auch heute noch arbeiten.

Naiv, unerfahren und wagemutig begannen wir mit zu wenigen Aggregaten und totaler Denormalisierung, was uns schnurgerade in die Update-Hölle führte.

Angeregt durch die Kommentare rund um diesen Blogpost fanden wir dann aber eine wirklich schöne Lösung, die das Beste aus beiden Welten verbindet.

Um bei dem Beispiel aus diesem Post zu bleiben: Statt wie zuvor nur Blogs und User in einzelnen Dokumenten abzulegen, haben wir heute eigene Dokumente für Blogs, Artikel, Kommentare und User. Anstatt beispielsweise Autoren-Infos für Kommentare direkt in den Kommentar zu schreiben, legen wir dort nur noch die Referenz zum User in Form seiner ID ab.

An der Stelle kommt der Charme von RavenDB ins Spiel: über Indizes, die fortlaufend asynchron im Hintergrund gepflegt werden, werden die einzelnen Dokumente wieder so zusammengesetzt, wie wir sie nachher auslesen wollen.

Beispiel: ein Blog hat sowohl Autoren als auch Abonnenten innerhalb der Plattform. Im Blog-Dokument befindet sich nun jeweils eine Liste mit den IDs aller Autoren und eine Liste mit den IDs aller Abonnenten. Über den passenden Index erzeugten wir uns eine View auf das Blog, die die vollständigen und aktuellen Nutzerdaten wie Name oder die ID des Profilbildes anstatt der blanken ID enthält. So ist nur noch die Abfrage des einen Views auf das Blog notwendig, anstatt der 1+n Abfragen für das 1 Blog + n Autoren + n Abonnenten. Sobald nun ein Nutzer seine Daten ändert, wird der Index automatisch von RavenDB im Hintergrund aktualisiert.

An der Stelle darf man übrigens nicht den Fehler machen und das Ganze mit Views aus RDBMS verwechseln - denn über Joins bekommt man diese Daten (1 Blogpost + n Autoren + n Abonnenten) nicht über eine "einzelne Abfrage" heraus. Das ist natürlich auch für den Index eine komplexere Angelegenheit, da jedoch im Hintergrund indiziert wird, ist der Abruf vergleichsweise billig.

Apropos Generierung im Hintergrund: RavenDB bietet verschiedene Optionen um mitzubekommen, ob abgerufene Daten aktuell sind, oder nicht. Daraufhin lässt sich dann entsprechend reagieren, wenn man z.B. zwingend aktuelle Daten benötigt.

Damit waren wir nun vermeintlich der Update-Hölle entronnen - denn es mussten bei bloßer Änderung des Namens eines Nutzers nicht mehr 1+n Dokumente aktualisiert werden, sondern nur noch eines. Den Rest erledigte der Index.

Das alles ist in seinem Aufbau wirklich eine schöne Sache, es hat Spaß gemacht damit zu entwickeln.

Das endete abrupt, als sich die Entwicklung der ersten Phase dem Ende zuneigte und die Menge an Daten auf unserem Alpha-Server zunahm. Über Last-Tests hatten wir zwar mal gesprochen, aber es gab immer noch andere Aufgaben, die wichtiger waren ... tja, hätten wir der Intuition vorher mal ihren Lauf gelassen.

Denn es ist so: RavenDB verweigert sich per default Operationen, bei denen mehr als 30 Dokumente abgerufen werden. Man kann diesen Wert zwar hochsetzen, aber alles über 1024 Dokumente wird spätestens am Server nicht mehr unterstützt.

Das wird spätestens beim Löschen problematisch. Nehmen wir an, ein User möchte seinen Account löschen. Das bedeutet für uns, dass all seine Daten restlos entfernt werden, ohne Kompromisse.

Wenn er nun in 10 Blogs zu jeweils 5 Posts im Schnitt 2 Kommentare geschrieben hat, müssen diese 100 Kommentare aus den 50 Posts entfernt werden - bzw. nicht die Kommentare selbst, sondern die Referenzen auf die Kommentare. Das bedeutet: 50 Post-Dokumente öffnen, die ID aus der Comments-Collection entfernen, speichern. Und das alles sinnvollerweise in einer Transaktion, um keine Inkonsistenzen zu riskieren.

Aber das geht nicht, denn per default ist wie gesagt beim Anfordern des 31. Dokumentes Schluss, konkret steigt der RavenDB-Client mit einer Exception aus.

Sicher, es gibt Workarounds. Man kann das Limit hochsetzen und darauf wetten, dass User unsere Plattform so scheiße finden werden, dass sie sie nicht nutzen ;-). Man kann auch auf Transaktionen verzichten und inkonsistente Daten riskieren.

Aber will man das? Ich nicht. Ich muss aber auch gestehen, dass ich zu diesem Zeitpunkt mit der Erfahrung aus knapp anderthalb Jahren RavenDB auch nicht mehr ganz unvoreingenommen war.

Hier eine lose Liste meiner Erinnerungen:

Das sind jedenfalls die Eindrücke, die bei mir in Summe dazu geführt haben, für dieses Projekt nicht weiter auf RavenDB zu setzen.

So sind wir jetzt wieder auf Los - und haben uns für PostgreSQL als Datenbanksystem entschieden. Einerseits, weil sich "die Wahrheit in einem relationalen System" für uns schon immer besser aufgehoben angefühlt hat, andererseits weil wir tendenziell ohnehin in Richtung CQRS unterwegs sind und wir damit den potentiellen Performance-Nachteil durch Joins & Co. problemlos durch den Einsatz von (ggf. anderen) NoSQL-Tools beim Lesen wieder ausgleichen können werden.

(*) Bei Hetzner kostet ein DELL-Server mit Hexacore-CPU, gespiegelter SSD und 192 GB RAM momentan 300 EUR netto pro Monat. Das ist nichts im Vergleich zu den Anschaffungskosten für Hardware in diesen Dimensionen von vor ein paar Jahren.

Kommentare

  1. Thomas Freudenberg schrieb am Donnerstag, 13. Februar 2014 13:37:00 Uhr:

    Hättest Du das Löschen eines Accounts nicht auch mit Patches (http://ravendb.net/docs/2.5/client-api/partial-document-updates) erledigen können? Das scheint sogar über's Studio zu gehen (http://blog.hibernatingrhinos.com/12705/new-option-in-the-ravendb-studio-patching)
  2. Thomas schrieb am Donnerstag, 13. Februar 2014 14:25:00 Uhr:

    Ja, das wäre ein Weg - aber auch der wäre nicht über eine Transaktion absicherbar. Und wenn ich mir vorstelle, dass es tausende Dokumente betrifft und die Datenbank in dem Moment schon nicht mehr nur auf einem Server laufen würde, blieben dabei immer Restzweifel.
  3. Robert (INdotNET) schrieb am Freitag, 14. Februar 2014 15:02:00 Uhr:

    Danke Thomas, dass du über deine schlechten Erfahrungen mit NoSQL berichtest. So etwas liest man sehr selten und es ist wertvoll zu wissen, wie es sich in der Praxis bewährt.
  4. Sven schrieb am Samstag, 15. Februar 2014 18:31:00 Uhr:

    Wie sieht es mit MongoDB aus? Wurde kurz erwähnt.
  5. Thomas schrieb am Samstag, 15. Februar 2014 21:59:00 Uhr:

    MongoDB bietet keinen ACID-Support für Operationen, die über mehrere Dokumente gehen, also faktisch Transaktionen. Das war das KO-Kriterium gegen MongoDB und der Grund seinerzeit zu RavenDB zu wechseln.
  6. Alex schrieb am Sonntag, 16. Februar 2014 21:34:00 Uhr:

    Setzt Ihr mit PostgreSQL einen ORM ein? Oder plain ADO.NET?
  7. Alex schrieb am Sonntag, 16. Februar 2014 21:42:00 Uhr:

    Wie testet ihr jetzt? SQLite?
  8. Thomas schrieb am Sonntag, 16. Februar 2014 22:45:00 Uhr:

    Momentan Plain ADO.NET mit ein paar simplen Extensions, die die Zeremonie etwas verringern und das Mapping erleichtern. Wir sind bei momentan etwas um die 100 CRUD-Queries, womit das zwar in einem Rutsch etwas nervig aber machbar war. Der aufwendigere Teil war die Anpassung unserer Entities, weil ein Blog aus dem Beispiel nun eben nicht mehr all seine Autoren und Abonnenten drauf hat, da es direkt abgerufen zu teuer wäre und wir bewusst auf Lazy Loading verzichten.

    Getestet haben wir schon zuvor nicht direkt gegen die Datenbank, sondern immer das Verhalten gegen die API (Asp.net WebApi im Übrigen). An diesen Tests hat sich also nichts geändert (außer da, wo zur Gegenprobe zuvor auf Raven zugegriffen wurde innerhalb der Tests, da gehen wir jetzt direkt auf Postgres, das macht keinen Unterschied.)
  9. Marius schrieb am Montag, 17. Februar 2014 12:22:00 Uhr:

    Danke für diesen Beitrag!

    Ich kämpfe gerade mit der Performance bei der Index-Aktualisierung von ca. 500.000 Dokumenten. Es benötigt immer 10 Minuten bis ein Multi-Map-Index wieder non-stale ist. Zudem ist das RavenDb Studio (Silverlight) in der Handhabung echt nervig. Oft führt man eine Benutzer-Aktion aus, bekommt aber keine Rückmeldung, dass diese nicht geklappt hat (löschen von ganzen Collections, Index, ... ).

    Ich warte noch ab, was die Version 3.0 bring (mit HTML5 Studio) und entscheide dann, wie es weiter geht.

    http://ayende.com/blog/165441/the-ravendb-conference-april-7-11

  10. Thomas schrieb am Montag, 17. Februar 2014 13:03:00 Uhr:

    10 Minuten bei 500.000 Dokumenten? Wow. Da lag ich mit meinem Gefühl ja gar nicht so falsch. Das wäre für uns tödlich, da wir die Daten am Server auch mit beliebigen Clients synchronisieren (Smartphone-Apps) - hier sind wir darauf angewiesen, dass die Abfragen non-stale sind. Ich hatte mit Raven einen schönen Weg gefunden, auch den Fall zu handlen, dass mal eine Abfrage stale ist - aber wenn das in dem Fall zum Dauerzustand wird, kann man das ganze Konzept wegschmeißen.


« Zurück  |  Weiter »