Forschungswoche mit MongoDB
Diese Jahr sollte gut beginnen: Gleich in der ersten Woche habe ich meine Forschungswoche gemacht.
Als Thema habe ich mir dieses Mal die MongoDB geschnappt. Warum? Einmal weil ich NoSql Datenbanken ziemlich interessant finde und im letzten Jahr auf einem guten MongoDb Workshop war. Weiterhin stellt die Massendatenverarbeitung bei blau immer wieder eine Herausforderung dar, so das es mal an der Zeit ist, ist, neue Wege zu erkunden.
Fehlt also noch ein konkretes Projekt. Das war auch schnell gefunden: Blau erhält jeden Tag eine Menge CDRs, das sind Gesprächsdaten, die Informationen über jeden Anruf, SMS und Datenverbindungen enthalten. Die CDRs werden noch ein wenig aufbearbeitet und dann dem Kunden in einem Einzelverbindungsnachweis angezeigt. Da wir sehr viele solcher CDRs am Tag erhalten, spielt Performance eine wesentliche Rolle: wie gemacht für MongoDB!
Das CDR Umfeld eignet sich deshalb auch sehr gut, weil nur ein lesender Zugriff erfolgt und fehlende Transaktionen und MVCC kein Problem darstellt.
Der Einstieg
Mein Hauptziel war also, möglichst schnell die CDRs in der Datenbank abzuspeichern und darauf diverse Selektionen machen zu können. Dabei wollte ich die MongoDB Features genauer kennenlernen.
Also schnell den MongoDB Server heruntergeladen, gestartet und ein wenig mit der MongoShell herumgespielt. MongoDb bietet eine JavaScript Shell, worüber man schon sehr viel auf der Datenbank machen kann. Hier mal so ein kleiner Ausschnitt:
// find all documents of the cdr_records collection db.cdr_records.find() // find the first document of the cdr_records collection db.cdr_records.findOne() // find all documents where the filename is '01090C11100325455' db.cdr_files.find({"filename" : "01090C11100325455"}) // find all documents between 'start' and 'end' and return 'served_msisdn' property start = new Date("01/02/2011") end = new Date("01/04/2011") db.cdr_records.find({"eventtimestamp" : {"$gte" : start, "$lte" : end }}, {"served_msisdn" : 1})
Die Shell bietet da schon einige Möglichkeiten. Gewöhnen musste ich mich ein wenig an den JSON Syntax und die fehlende Code Completion. Zu erwähnen ist, das man vollen JavaScript Support hat, also auch eigene Funktionen definieren und mit diesen komplexere Queries absetzen kann.
Mapping mit MongoId
Nun wollte ich mir auch ein paar Ruby Projekte anschauen und habe mit MongoId einen Object-Document-Mapper ausgesucht, mit dem man seine Domäne a la ActiveRecord beschreiben kann. Ist bei dem kleinen Modell zwar nicht unbedingt nötig, aber der Erfahrung wegen auf jeden Fall spannend. So sieht dann eine CdrFile Klasse beispielhaft aus:
module Tempo class CdrFile include Mongoid::Document field :filename, :type => String field :record_count, :type => Integer field :total_amount, :type => BigDecimal field :earliest_call, :type => DateTime field :latest_call, :type => DateTime # refers to the embedded document cdr_record embeds_many :cdr_records # some regexp for parsing HeaderRegexp = /^01(\d{5})(.{30})(\w{17})(\d{12})(\d{3})(\w{3}).*$/ TrailerRegexp = /^02(.{9})(.{30})(.{20})(\w{17})(\d{14})(\d{14}).*$/ RecordRegexp = /^08(.{20})(.{21})(.{3})(.{24}).{33}(\d{14})(.{6}).{6}(.{13}).{59}(.{10}).*$/ def self.load_file(file_path) # load the file and return cdr_file instance end def self.parse(content, regexp) # some parsing code end end end
Die Idee hier war das die gesamte Datei als ein Dokument abzulegen, die CdrRecords sind als Embedded Document abgespeichert.
Nun schnell das Dokument mit MongoId gespeichert, und… oje, leider war das alles andere als schnell! Über 2 Minuten für 51.000 Zeilen. Schnell habe ich herausgefunden, das nicht das Speichern, sondern das Erzeugen der MongoId Objekte lange dauert und habe auch ein entsprechendes GitHub Ticket gefunden.
Weiter mit mongo-ruby-driver
Da ich eh nur ein kleines Modell hatte und MongoMapper nicht mehr ausprobieren wollte, habe ich mir den ruby-mongo Treiber geschnappt. Die Dokumentation ist super und so konnte ich ziemlich schnell ein Beispiel mit dem nativen Treiber bauen. Letztlich ist doch alles nur ein Hash.
Und siehe da, das geht schonmal wesentlich schneller Ca. 50.000 Einträge in knapp 10 Sekunden. Schon recht ordentlich.
Allerdings waren nun die Abfragen mit MongoHub recht langsam. Der Grund war auch recht schnell gefunden. Jedes Dokument in einer MongoDb darf max. 4MB groß sein. Das hat Performancegründe und wurde auch intensiv in der Community disskutiert, so dass das Limit in der 1.8er Version auf 8MB angehoben wird. Generell ist das aber eher eine künstliche Begrenzung und rührt daher dass die abgelegten BSON Dokumente nicht größer sein dürfen.
In meinem konkreten Fall sind aber Cdr Dateien immer deutlich größer als 4MB, was mich dann dazu gebracht hat, nochmal über mein Schema Design nachzudenken. Nachdem ich den “Rich Document” Ansatz verlassen und die CdrRecords in eine eigene Collection abgespeichert hatte, waren die Abfragen auch wieder schnell.
Erfahrungen mit Date und RegExp
Bisher habe ich alle Daten einfach nur als String abgespeichert. Ein wenig besser sollte es dann schon werden, also schnell die Datumsinformationen als Date abgelegt. Nun ging es aber mit der Performance deutlich runter, was an der recht langsamen DateTime Implementation in Ruby 1.8.7 liegt.
Da bin ich auf home_run gestoßen, ein Gem was die komplette Date Implementation von ruby autauscht. Hätte ja nicht gedacht, dass es so einen Unterschied macht, aber ich wurde eines anderen belehrt …
Aus ein paar Gesprächen hatte ich auch schon erfahren, dass die Standard RegExp Implementation nicht zu den schnellsten gehört und habe deshalb Oniguruma ausprobiert, wofür es auch ruby Bindings gibt. Oniguruma ist eine schnellere, in C implementiert RegExp Bibliothek und in ruby-1.9 die Standardimplementation. Um sie in ruby-1.8.x benutzen zu können, müssen noch ein paar Systembibliotheken via homebrew nachinstalliert werden:
$ brew install oniguruma $ gem install oniguruma
und schon kann es losgehen. Die Anpassungen im Code sind denkbar einfach, einfach RegExp durch ORegExp ersetzen. Und schon konnte ich noch ein wenig schneller CDRs importieren.
Diverses
Während meiner Forschungswoche hat mich “MongoDb – The definitive guide” begleitet. Ich kann das Buch jedem nur wärmstens empfehlen, es hat mir viele gute Tipps vermittelt und ich war dadurch sogar in der Lage, in nur ein paar Minuten eine Master-Slave Replikation aufzusetzen.
Ein wenig beschäftigt habe ich auch mit den Map/Reduce Möglichkeiten. Die Benutzung fand ich etwas umständlich und leider habe für mein Datenschema kein lauffähiges Beispiel zustande bekommen, zumindest aber gesehen, das man mit MongoHub sehr viel bequemer an die Sache rangehen kann.
Für das vorgestellte Cdr Szenario wird wahrscheinlich insbesondere das Hosting interessant werden. Aktuell bietet MongoDb nur eine Memory Storage Engine, was z.B. auf einem OpenVZ zu Problemen führt und auf 32bit Systeme unweigerlich an der 2,5GB Grenze aufhört.
Gerade für Massendaten wie CDRs ist das aber zu wenig und man wird auf Mechnismen wie Sharding (Partitionen unter Oracle) zurückgreifen müssen, um die Daten auf unterschiedliche MongoDb Instanzen aufzuteilen und um Hochverfügbarkeitsszenarien abbilden zu können. Aber möglich ist alles.
Fazit
Das “forschen” mit Mongo hat eine Menge Spaß gemacht, und gerade durch die kleinen Hürden konnte ich noch eine Menge Wissen mitnehmen. Man merkt auch bei NoSQL Dbs, das ein gutes Schema Design ziemlich wichtig ist!
Mehr? Aktuelle Artikel oder alle Artikel im Archiv.