Über unsMediaKontaktImpressum
Dennis Hoppe 20. September 2016

Berkeley DB – Embedded transactional database library

Die effiziente Datenverwaltung ist ein zentrales Thema in der modernen Anwendungsentwicklung. Die zunehmende Vernetzung von Geräten (IoT, Machine-2-Machine-Kommunikation) definiert neue Anforderungen hinsichtlich Interportabilität und Performance. Mit Berkeley DB bietet Oracle ein Werkzeug an, das es erlaubt, mit minimalem Aufwand eine performante Datenbank in die eigene Software zu integrieren.

Technologie

Berkeley DB ist eine Datenbankbibliothek, welche Schnittstellen zu mehreren Programmiersprachen bietet und somit eine direkte Einbettung in die jeweilige Software erlaubt. Im Gegensatz zu Stand-Alone-Datenbankmanagementsystemen entfällt hier die Interprozesskommunikation da die Datenbank im selben Prozess wie die Anwendung läuft. Die Berkeley DB-Bibliothek stellt Schnittstellen zur Verfügung, mit denen sich Datenbankoperationen durch simple Funktionsaufrufe ausführen lassen. Seit Version 11g wird auch ein SQLite-kompatibles API zur Verfügung gestellt.

Die Einsatzgebiete von Berkeley DB sind sehr vielseitig, da die Datenbank auf den meisten Betriebssystemen und Prozessorarchitekturen lauffähig ist, ob als leichtgewichtige Datenbank auf einem Netzwerkrouter oder als Backend für Hochleistungs-Internetserver. Berkeley DB bietet Unterstützung für multiple simultane Prozesszugriffe sowie Zugriffe über verschiedene Threads eines Prozesses. Transaktionslogging, Speichermanagement, Locking und andere Low-Level-Funktionen werden transparent von der Datenbankbibliothek gemanaged.

Berkeley DB hat seine Stärken da, wo Performance und ein geringer Overhead gefragt sind.

Ein Berkeley DB-Environment beschreibt eine aus einer oder mehreren Datenbanken bestehende Datenbankumgebung, welche transaktionale Prozesse sowie Replikationsmechanismen anbietet. Berkeley DB legt die Daten als Key-Value-Pair in einer B-Tree,Struktur oder Hashtable ab. Ein Datensatz bestehend aus Schlüssel und Wert kann bis zu 4 Gb groß sein und auch komplexe Strukturen beinhalten. Die Größe einer Datenbank kann bis zu 256 Tb betragen. Durch umfangreiche Journal- und Recoveryfunktionen ist der administrative Aufwand sehr gering. Selbst nach einem Systemabsturz setzen die Wiederherstellungsmechanismen die Datenbank in einen konsistenten Status zurück [1].

Berkeley DB im Einsatz

Die Berkeley DB-Distrbution steht als Quelltext zum Download bereit [2]. Neben der klassischen Berkeley DB-Distribution existiert auch eine komplett in Java programmierte Berkeley DB-Java Edition, welche in Form einer JAR-Bibliothek direkt in Java-Projekte eingebunden werden kann. Im aktuellen Artikel wird die klassische Version in Kombination mit dem C-API verwendet.

Installation

Bevor die Datenbank eingesetzt werden kann, muss diese für die Zielplattform kompiliert werden. Dieser Artikel beschränkt sich auf die einfache Konfiguration und Installation unter Linux. Oracle bietet eine umfangreiche Dokumentation über Konfigurationsmöglichkeiten und die Installation auf anderen Plattformen.

Nach dem Download der Berkeley DB-Distribution muss diese in ein Arbeitsverzeichnis entpackt werden.

# tar xfvz [berkeley-db_xxx.tar.gz] 

Anschließend muss in das Verzeichnis build_unix gewechselt und von dort aus der Konfigurationsvorgang gestartet werden.

# cd <bdb root>/build_unix
# ../dist/configure
# make
# make install

Nach dem erfolgreichen Kompiliervorgang stehen alle Bibliotheken und Includes zur Verfügung.

Datenbanken anlegen und öffnen

Das Anlegen und Öffnen einer Datenbank geschieht durch einen simplen Funktionsaufruf.

#include <db.h> /* Einbindung der Datenbankfunktionalität */

DB *db;           /* Datenbankstruktur*/
u_int32_t flags;  /* Flags zur Parameterisierung */
int ret;          /* Variable für Returncodes */

/* Wir arbeiten in diesem Beispiel mit einer
 * standalone Datenbank ohne Environment
 * daher kann NULL als Environmentparameter übergeben werden */
ret = db_create(&db, NULL, 0);
if (ret != 0) {
  /* Fehler bei DB Initialisierung */
}

flags = DB_CREATE;    /* Falls die Datenbank nicht existiert
                         so wird diese angelegt*/

/* Öffnen der Datenbank */
ret = db->open(db,         /* DB pointer */
               NULL,       /* Transaktionspointer (Optional)*/
               "my_db.db", /* Datenbankdateiname */
               NULL,       /* Logischer DB-Name (Optional) */
               DB_BTREE,   /* Zugriffsmethode */
               flags,      /* Flags */
               0);         /* Dateimodus (default) */
if (ret != 0) {
  /* Fehler beim Öffnen der Datenbank */
}

Datensätze anlegen

Zum Anlegen von Datensätzen wird ein Schlüssel-Wert-Paar der DBT-Struktur verwendet.

#include <db.h>
#include <string.h>

...

DBT key, value;
DB *my_database;
int ret;

long custNr = 12345;
char *custName = “Walter Müller”;

/* Öffnen der Datenbank wie erläutert */

/* Nullen der DBT Strukturen */
memset(&key, 0, sizeof(DBT));
memset(&value, 0, sizeof(DBT));

key.data = &custNr;
key.size = sizeof(long);

value.data = custName;
value.size = strlen(custName) +1;

/* Datensatz schreiben */
ret = my_database->put(my_database, NULL, &key, &value, DB_NOOVERWRITE);

if (ret == DB_KEYEXIST) {
    my_database->err(my_database, ret,
      "Insert failed because key %f already exists", custNr);
}

Im obigen Beispiel wird der Kunde Walter Müller mit der Kundennummer 12345 angelegt. Durch das Flag DB_NOOVERWRITE wird erzwungen, dass im Falle einer bereits existierenden Kundennummer 12345 der Fehlercode DB_KEYEXIST zurückgegeben wird.

Neben den primitiven Datentypen können auch komplexe Strukturen als Schlüssel oder Wert verwendet werden.

Datensätze lesen

Analog zur put()-Methode zum Anlegen von Datensätzen, wird die get()-Methode zum Lesen verwendet. Datenkollektionen werden per Cursor abgerufen.

#include <db.h>
#include <string.h>

...
#define NAME_SIZE 128
DBT key, value;
DB *my_database;
int ret;

char name[NAME_SIZE + 1];

/* Öffnen der Datenbank wie erläutert */

long custNr = 12345

/* Nullen der DBT Strukturen */
memset(&key, 0, sizeof(DBT));
memset(&value, 0, sizeof(DBT));

key.data = &custNr;
key.size = sizeof(long);

data.data = name;
data.ulen = NAME_SIZE + 1;
data.flags = DB_DBT_USERMEM;

/* Lesen des Datensatzes */
ret = my_database->get(my_database, NULL, &key, &value, 0);

if (ret == DB_NOTFOUND) {
    /* Kein entsprechender Datensatz gefunden */
}

Zusammenfassung

Die o. g. Beispiele zeigen, dass es mit wenigen Funktionsaufrufen bereits möglich ist, Datenbanken sowie Datensätze zu erzeugen und auszulesen. Die Berkeley DB-Bibliothek stellt noch weitere Funktionen, wie z. B. Transaktionen oder Replikation, zur Verfügung. Die Dokumentation hierzu ist umfangreich [3].

Erfahrungen aus der Praxis

Im praktischen Einsatz hat sich Berkeley DB als Persistenzschicht für eine unternehmenskritische Anwendung zur mobilen Datenerfassung auf Android-Geräten bewährt. Bei einer Datenbankgröße von über 500 Mb, mit mehreren Millionen Datensätzen, liegt bei einem mobilen Endgerät ein besonderes Augenmerk auf der Lese- und Schreibperformance. Die in Android standardmäßig vorhandene SQLite-Datenbank kann in dieser Hinsicht nicht mit Berkeley DB mithalten. Besonders hervorzuheben ist die Leseperformance bei gespeicherten Binärdaten wie z. B. Fotos. Ein weiterer Schwerpunkt bei der Arbeit im mobilen Umfeld ist der Wartungsaufwand. Bei Geräten die weltweit auf mehrere tausend Nutzer verteilt sind, muss eine einfache Fernadministration sowie sichere Wiederherstellung der Datenbank gewährleistet sein. Eine große Stärke von Berkeley DB ist die Hotbackup-Funktion, mit der eine Schattenkopie der Datenbank im laufenden Betrieb angelegt werden kann, mit Hilfe derer sich die Datenbank im Fehlerfall vollständig wiederherstellen lässt. Im mehrjährigen Einsatz hat sich gezeigt, dass diese Funktionalität jedoch äußerst selten verwendet werden muss, da Berkeley DB mit umfangreichen Journaling- und Selbstdiagnosefunktionen einen weitgehend reibungslosen Betrieb gewährleistet. So ist in o. g. Projekt, auch nach über 3 Jahren Produktivbetrieb auf mehr als 1.000 Geräten, noch kein Datenverlust aufgetreten.

Fazit

Berkeley DB hat seine Stärken da, wo Performance und ein geringer Overhead gefragt sind. Die direkte Integration in die Anwendung bedeutet anfangs eventuell ein wenig mehr Aufwand in der Anwendungsentwicklung, dieser Aufwand wird jedoch durch die o. g. Vorteile mehr als wett gemacht. Zudem macht man sich nicht abhängig von den Anbietern externer Datenbanksysteme, da Berkeley DB direkt in die Anwendung integriert wird und die Änderungshistorie des API selbst verwaltet werden kann. Für Anwendungsentwickler die nach einer leichtgewichtigen und gleichzeitig sehr leistungsfähigen Datenbank suchen, sollte Berkeley DB in die engere Auswahl fallen.

Autor

Dennis Hoppe

Dennis Hoppe ist Software Engineer und Enterprise Solution Architect mit Schwerpunkt im Embedded- und Mobility-Bereich. Der Fokus seiner Forschungsarbeit liegt auf einer effizienten Datenverarbeitung und der Integration...
>> Weiterlesen
botMessage_toctoc_comments_9210