Donnerstag, 13. September 2012

U1DB

U1DB ist Canonicals neue Datenbank zum Speichern und Synchroniseren von Daten. Synchronisieren heißt hier, dass "Desktop Daten" wie z.B. Kontaktdaten aus dem E-Mail Programm oder Kalenderdaten zwischen verschiedenen Rechner - und auch einem Serverdienst wie z.B. UbuntuOne - abgeglichen werden können. Wobei U1DB an sich die API beschreibt, und nicht die Datenbank selber.

Solche Dienste gibt es aber bekanntlich bereits - wozu programmiert Canonical also an einer eigenen Lösungen (die übrigens von Anfang an OpenSource ist und unter GNU LGPL v3 Lizenz steht)? Nun, neu ist die Synchronisation unter Ubuntu nicht. Genau genommen gibt es die schon länger, U1DB ist aber der 2. Anlauf. Grund: der 1. ist zwar nicht gescheitert, wurde aber wegen Problemen aufgegeben. Die 1. Variante setzte auch CouchDB als Datenbank-Server und lokal auf Desktopcouch. Dieser Ansatz war auch komplett in die Ubuntu Desktop Installationen integriert, wurde dann aber nach dem Release von Ubuntu 11.10 als "Auslaufmodell" deklariert, weil es bei Canonical zu Skalierungsproblemen mit CouchDB kam, die sich wohl nicht lösen ließen (wer darüber mehr erfahren möchte sollte einfach "ubuntu couchdb" als Suchbegriff in die Suchmaschine seiner Wahl eingeben). Der Nachfolger, und damit 2. Anlauf, ist jetzt U1DB. In Ubuntu 12.04 LTS konnte die Lösung noch nicht intergriert werden, dazu war die Zeit zu knapp. Für den kommenden Ubuntu Release 12.10 gibt es wohl eine Intergration, jedenfalls gibt es bereits fertige Pakete in den offiziellen Quellen.

Was heißt das jetzt für den Desktop-Nutzer? Nun, erst Mal nichts, da die Synchronisation der Daten transparent erfolgt, d.h. man selber hat keinen direkten Kontakt zu U1DB. Interessanter sind da schon die Pläne von Canonical, was die Integration in andere Betriebssysteme angeht. Diese sind nämlich recht ehrgeizig. U1DB soll - früher oder später - für alle gängigen Systeme verfügbar sein, also neben Linux auch MacOS, Windows, Android, iOS und eine Web-Schnittstelle. Dazu soll U1DB in Python, C, Vala, Go, C, C#, Objective C, Java und JavaScript umgesetzt werden. Die Implementierung in Python und C läuft, der aktuelle Release (Stand: 13.9.2012) ist 0.1.3. An der Vala und Go Umsetzung wird gearbeitet, es gibt aber noch keine Releases.

Schaut man in die API und Technik von U1DB, gibt es eine Reihe von Ähnlichkeiten mit CouchDB. Die Datenbank speichert text-basierte Daten im JSON-Format (ebenso wie CouchDB) - und ist alleine darauf ausgelegt. Binäre Daten (wie z.B. Bilder, Musik etc.) können nicht gespeichert werden. Des Weiteren beherrscht U1DB Mehrwege-Replikation inklusive Konfliktmanagement zwischen Datenbanken / Servern. Ein Feature, das CouchDB ebenfalls besitzt. Scheinbar war man bei Canonical mit der Funktionalität von CouchDB zufrieden und hat sich an diese angelehnt, wobei es keinerlei Kompatibilität gibt.

Als Speicher setzt U1DB lokal auf den meisten Plattformen auf die SQLite Datenbank. Was in sofern auch Sinn macht, als das Python, Android und iOS diese direkt mit an Bord haben. Auf der Projektseite wird aber betont, dass auch andere Datenbanken als Backend möglich sind. Die Go-Implementierung soll z.B. auf MongoDB setzen und auch über das Internet zugängliche U1DB-Server werden wohl eher auf MySQL und andere skalierbare Datenbanken setzen als auf SQLite.

Wie bereits erwähnt ist bisher "nur" die Python-Implementierung (mit in C geschriebenen Teilen fertig). Dies ist auch die Referenz-Implementierung. Wer sich also mit der API beschäftigen möchte, der sollte darauf zurück greifen. Das Python-Modul heißt einfach "u1db" und lässt sich auf bekanntem Wege via pip oder easy_install installieren. Übrigens ist U1DB für Python-Programmierer auch außerhalb von Ubuntu interessant, wenn eine einfache API zur persistenten Speicherung und Indizierung von JSON-Daten gesucht wird.

Zurück zu Ubuntu: U1DB wird unter Ubuntu sicherlich der kommende Standard zur Synchronisation von Daten. Wie gesagt, für den Nutzer transparent. Da sich U1DB noch in einem frühen Entwicklungsstadium befindet ist im Moment offen, wenn die API als stabil deklariert wird und die volle Integration in den Desktop abgeschlossen ist.

Sonntag, 2. September 2012

Redis und Python

Redis ist ein sehr schnelles Key-Value Store, zu Deutsch: Schlüssel-Werte Datenbank. Eine Besonderheit von Redis ist zusätzlich, dass es als Werte nicht nur einfache Strings kennt, sondern fünf verschiedenen Datentypen. Da die offizielle Dokumentation von Redis ganz hervorragend ist und auch in der September-Ausgabe von FreiesMagazin ein ausführlicher Artikel zu Redis vorhanden ist, wird an dieser Stelle auf eine weitere Beschreibung der Datenbank verzichtet und "nur" die Python-Anbindung näher beschrieben. In der Python-Welt erfreut sich Redis übrigens scheinbar recht großer Beliebtheit. Wird auf der Webseite des Python Package Index "redis" als Suchbegriff eingegeben, wird eine ziemliche lange Ergebnisliste angezeigt. So gibt es z.B. Anbindungen für Django, Flask und Celery an Redis sowie eine Vielzahl anderer Module, die Python auf die ein oder andere Weise mit Redis kombinieren.

Installation

Das empfohlene Python-Modul heißt einfach nur "redis" und lässt sich ganz einfach via pip oder easy_install installieren. Die die Ergebnisse der Abfragen des Redis-Servers werden von einem in Python implementierten Parser verarbeitet - was ohne Probleme funktioniert, aber nicht ultimativ schnell ist. Wer die volle Geschwindigkeit braucht, der installiert noch das Modul "hiredis" (via pip oder easy_install). So wird der in C geschriebene Parser inklusive Python-Bindings installiert. Die Entwickler nennen eine Geschwindigkeitssteigerung um das 10-fache, im Vergleich zum Python-Parser.

Mit dem Server verbinden

Das Verbinden mit einem laufenden Server ist einfach:

>>> r = redis.StrictRedis(host='localhost',port=6379,db=0)

Im Gegensatz zur z.B. Kommandozeile von Redis muss hier eine Datenbanknummer angegeben werden. Das Python-Modul erlaubt es übrigens nicht, die Datenbank im laufenden Betrieb zu wechseln (was z.B. via Redis Kommandozeile problemlos geht), weil die Datenbankverbindung dann nicht mehr "thread-safe" wäre. Wer also mehrere Datenbanken für sein Programm benötigt, der muss mehrere Instanzen von redis.StrictRedis(...) anlegen.

Connection Pooling

Im Hintergrund legt redis.StrictRedis(...) für jede neu Instanz einen neuen, eigenen Connection Pool an. Was je nach Anwendung aber gar nicht nötig ist. Aber es ist auch möglich, dass sich mehrere Instanzen einen Connection Pool teilen:

>>> pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
>>> r = redis.Redis(connection_pool=pool)

Auch hier gilt: Ein Wechsel der Datenbank innerhalb eines Connection Pools ist nicht möglich bzw. - andersherum - ein Connection Pool kann immer nur für eine Datenbank gelten.

Datentypen

Natürlich unterstützt das Python-Modul allen Datentypen voll. Dabei werden so gut wie alle Befehle von Redis 1:1 in Python umgesetzt, so dass die Redis Befehlsreferenz bei Fragen zu Rate gezogen werden kann. Im folgenden ein paar einfache Beispiele zur Nutzung der Datentypen aus Python heraus: Strings sind der einfachste Datentyp:
>>> r.set('foo','bar')
True
>>> r.get('foo')
'bar'

Und ein paar Beispiele zu Listen in Redis:
>>> r.lpush('liste','foo')
1L
>>> r.lpush('liste','bar')
2L
>>> r.lrange('liste',0,-1)
['bar', 'foo']

Hashs lassen sich mit den bekannten Befehlen generieren:
>>> r.hset('hash','foo','bar')
1L
>>> r.hset('hash','spam','egg')
1L

Werden alle Werte innerhalb eines Hashs abgefragt, dann wird ein Python Dictionary zurück geliefert:
>>> r.hgetall('hash')
{'foo': 'bar', 'spam': 'egg'}

bei Einzelwerten logischerweise ein String:
>>> r.hget('hash','foo')
'bar'

Hashs lassen sich auch direkt aus einem Python Dictionary generieren:
>>> my_dict = {'Rhythmbox':'Audio','Totem':'Video'}
>>> r.hmset('progs',my_dict)
>>> r.hmget('progs','Rhythmbox','Totem')
['Audio', 'Video']

Anlegen eines Redis-Sets aus Python heraus:
>>> r.sadd('set','foo')
1
>>> r.sadd('set','bar')
1

Wird versucht, einen bereits vorhanden Wert zu einem Redis-Set hinzuzufügen, dann liefert die Datenbank einfach "0" (für "False") zurück:
>>> r.sadd('set','bar')
0
Werden alle Werte aus einem Redis-Set abgefragt, wird als Ergebnis ein Python-Set zurück geliefert:
>>> r.smembers('set')
set(['foo', 'bar'])
Ordered Sets werden wie folgt in Redis via Python angelegt:
>>> r.zadd('orderset',10,'bar')
1
>>> r.zadd('orderset',5,'foo')
1
Oder alternativ auch:
>>> r.zadd('orderset',spam=1.0,egg=2.0)
2
Die als Ergebnis einer Abfrage eines Ordered Sets wird entweder eine Liste von Strings oder eine Liste von Tuplen geliefert. Je nach dem, ob die Gewichtung der Werte mit abgefragt wird oder nicht:

>>> r.zrange('orderset',0,-1,withscores=True)
[('spam', 1.0), ('egg', 2.0), ('foo', 5.0), ('bar', 10.0)]
>>> r.zrange('orderset',0,-1,withscores=True,desc=True)
[('bar', 10.0), ('foo', 5.0), ('egg', 2.0), ('spam', 1.0)]

Transaktionen

Das Python Modul unterstützt auch das Zusammenfassen von mehreren Befehlen zu einer atomaren Transaktion. Allerdings setzt das Modul nicht die Redis-Befehle multi und exec um, sondern geht den Weg über Pipelines:

>>> pipe = r.pipeline()
>>> pipe.set('counter',1)
<redis.client.StrictPipeline object at 0x18cfb10>
>>> pipe.set('wort','irgendwas')
<redis.client.StrictPipeline object at 0x18cfb10>
>>> pipe.incr('counter')
<redis.client.StrictPipeline object at 0x18cfb10>
>>> pipe.execute()
[True, True, 2]

Alle Befehle werden also gepuffert und erst nach dem Aufruf von pipe.execute() im Block atomar ausgeführt. Das Ergebnis der Ausführung der einzelnen Befehle wird als Liste zurück geliefert.

Weitere Dokumentation

Hier im Blogeintrag wurden zwar die meisten, aber nicht alle Möglichkeiten des Python Redis-Moduls gezeigt. Diese sind aber natürlich in der Dokumentation aufgeführt.

Redis und ORM

Zu guter Letzt sei noch erwähnt, dass es auch auch eine Reihe von Object Relational Mapperns (ORM) für Python und Redis gibt. Diese tragen aber teilweise noch eine relativ niedrige Versionsnummer. Weiter entwickelt - wenn auch noch im Beta-Stadium - scheint Redisco zu sein. Andere ORMs bringt eine Suche im Python Package Index hervor. 

Alle Beispiele hier im Blogartikel sind unter Ubuntu 12.04, Python 2.7, redis-py 2.6.2 und dem Redis Server 2.4.16 getestet.