10.022017

LevelDB in NodeJS - eine Einführung

Wir schauen uns heute das minimalistische Datenbank-DIY-Kit LevelDB an. Was für Google unter Android und im Chrome funktioniert, sollte schließlich auch für andere Zwecke verwertbar sein. Ich werde sehr knapp das Konzept von LevelDB erläutern und dann ein kleines Beispiels präsentieren, das zeigt wie schnell komplexe Funktionen umgesetzt werden können.

Beginnen wir also am Anfang. LevelDB ist ein Key-Value-Storage mit einigen kleinen, aber wichtigen Unterschieden zu anderen Datenspeicherlösungen:

LevelDB ist eine embedded DB (wie auch SQLite). Für die Nutzung ist keine eigenständige Server-Anwendung nötig.

Um also eine neue Datenbank anzulegen, genügen die folgenden Befehle:

const db = require('level')('/pfad/zur/datenbank');

Um nun Daten zu Speichern und auszulesen gibt es die Funktionen Put und Get

db.put('key', 'value');
[...]
db.get('key', console.log);
-> value

Alle Einträge werden in LevelDB nach ihrem Key sortiert abgelegt.

Somit unterstützt LevelDB effizientes Iterieren über Einträge mit einem beliebigen Startpunkt. In NodeJS bieten sich hierbei Streams an:

Legen wir also Daten mit den folgenden Schlüsseln ab:

  • anton
  • berta
  • cesar
  • dora
  • emil
  • friedrich
  • gustav

Die Ordnung wird unabhängig der Reihenfolge der put operationen eingehalten. So können wir uns schnell alle Namen, die mit 'c' oder 'd' beginnen ausgeben:

db.createKeyStream({gte: 'c', lt: 'e'})
 .on('data', console.log)
 .on('end', () => {console.log('fertig')})
-> berta
-> cesar
-> fertig

Natürlich sind wir hierbei nicht nur auf den Schlüssel begrenzt. So bietet levelup (eine NodeJS-spezifische Abstraktion von) auch Streams, die nur die Daten oder Schlüssel und Daten zusammen ausgeben. Ebenso lässt sich die Richtung (vorwärts oder rückwärts) festlegen.

Um den LevelDB-Kern sind insbesondere in der NodeJS-Community sehr viele Plugins, Erweiterungen und eigenständige Datenbankimplementierungen entstanden.

So gibt es für fast jeden Anwendungsbereich schon fertige Lösungen. Zum Schluss wollen wir noch eine kleine Testanwendung schreiben:

Wie wäre es mit Master-Master Replication zwischen einem Server und mehreren via Browser verbundenen Clients. Ziel ist es, dass der Server alle in der Datenbank gespeicherten Änderungen vorhält und automatisch Änderungen der Clients über alle verbundenen Datenbanken synchronisiert werden.

Kurz gesagt: Wenn ich bei mir im Browser einen Eintrag hinzufüge, so wird dieser auf alle anderen derzeit verbundenen Clients kopiert. Klingt aufwendig? Ist es aber nicht:

Zunächst schreiben wir unseren Server in NodeJS. Hierfür benutzen wir unter anderem 'express' um gleich die HTML-Seite für die Clients auszuliefern. Ansonsten nehmen wir noch 'level-replicate', welches die Hauptarbeit beim synchronisieren leistet. Da der Browser keine direkten TCP-Verbindungen aufbauen kann, tricksen wir etwas und benutzen Websocket-Verbindungen via 'websocket-stream'. Abgerundet wird das Packet durch 'level-sublevel', was uns erlaubt einzelne Bereiche in unserer LevelDB zu markieren.

Somit haben wir dann den folgenden Server-Code:

// express server für die clients
const staticSrv = require('express')();
staticSrv.use(express.static('/pfad/zu/den/client/dateien'))
staticSrv.listen(3000);

const level = require('level')
const SubLevel = require('level-sublevel')
const Replicate = require('level-replicate')

// Datenbank initialisieren.
const db = SubLevel(level('/pfad/zur/datenbank'));

// Replication hinzufügen
const master = Replicate(db, 'master', "MASTER-1");

// Replication zwischen verbundenen Clients aufsetzen.

const http = require('http');
const server = http.createServer();

const wss = require('websocket-stream').createServer({server}, (stream) => {
 stream.pipe(master.createStream({tail: true})).pipe(stream);
})

server.listen(9999);

Nun brauchen wir noch den Client. Hier nutzen wir 'level-js' welches LevelDB im Browser über Indexdb umsetzt (die Intern auch wieder eine LevelDB benutzt).

Wichtig für uns ist nur die Javascript-Datei. Hierbei nutzen wir das folgende Skript, dass wir dann durch 'browserify' jagen:

const levelup = require('levelup')
const leveljs = require('level-js')
const SubLevel = require('level-sublevel')
const Replicate = require('level-replicate')

// datenbank im browser bereitstellen
window.db = SubLevel(levelup('slave', {db: leveljs}))
const master = Replicate(window.db, 'master', "MASTER-2");

// synchronisieren über websockets
const websocket = require('websocket-stream');
const ws = websocket('ws://192.168.2.13:9999');

ws.pipe(master.createStream({tail: true})).pipe(ws);

Nun können wir alles zusammenbringen:

leveldb_sync

Ich hoffe ihr habt jetzt Lust mehr über LevelDB zu erfahren. Ich werde jedenfalls weiter mit LevelDB arbeiten und bin schon sehr gespannt wohin mich meine Reise führen wird.