IOException.de

Icon

Ausgewählter Nerdkram von Informatikstudenten der Uni Ulm

Effiziente Bereichs-Queries mit CouchDB/GeoCouch

Im Vergleich zu klassischen SQL-Datenbank erfordern NoSQL-Datenbank vor allem bei der Datenabfrage ein Umdenken. Im Falle von CouchDB lässt sich zwar mit View Collation schon einiges erreichen, allerdings bei weitem nicht alles. Auf eine solche Grenze bin ich gestoßen, als ich Zeiträume speichern und abfragen wollte, also Einträge die ein Start- und Enddatum besitzen. Anfragen auf diese Daten könnten nun zu einem fixen Zeitpunkt alle darin ablaufenden Einträge erfragen, oder ausgeweitet auf einen Zeitraum auflisten, welche Einträge innerhalb eines Zeitfensters liegen. All dies ist mit CouchDB nicht wirklich lösbar.

Einen kleinen Workaround bietet die Idee, die Zeitleiste zu segmentieren und immer dann für einen Eintrag einen Key zu emitten, wenn der Zeitraum des Eintrages innerhalb dieses Bereichs liegt. Eine solche Map-Funktion könnte wie folgt aussehen. Hierbei wird für einen Eintrag jeweils ab dem Beginn für alle 5 Minuten ein Schlüssel in den Index emittiert.

Dokumentaufbau:

{
   "_id": "s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de",
   "begin": "2010-08-05T09:11:52.156Z",
   "end": "2010-08-05T09:23:13.457Z"
}

Map-Funktion:

//length of time segment (here 5 min)
var periodLength = (60*5);

function(doc)
{
        if(doc.begin && doc.end)
        {
                //start and end time as UNIX timestamps (seconds, not milliseconds)
                var begin =  Math.round(new Date(doc.begin).getTime()/1000);
                var end =  Math.round(new Date(doc.end).getTime()/1000);

                //calculate first matching segment of period
                var p = (begin - (begin%periodLength));

                //emit key for each matching period
                while(p<=end)
                {
                        emit([p, doc._id], null);
                        p = p + periodLength;
                }
        }
}

Der resultierende View sieht dann in etwa so aus:

Oder als Abfrage:
http://localhost:5984/entries/_design/entries/_view/docsByPeriodList?startkey=[1280998800]&endkey=[1281000193,{}]

{"total_rows":3,"update_seq":2,"offset":0,"rows":[
{"id":"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de","key":[1280999400,"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de"]15,"value":null},
{"id":"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de","key":[1280999700,"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de"],"value":null},
{"id":"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de","key":[1281000000,"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de"],"value":null}
]}

Eine viel bessere Lösung bietet jedoch die CouchDB-Erweiterung GeoCouch. Dank des R-Trees lassen sich Bereichtsabfragen effizient durchführen. Da GeoCouch eigentlich für zweidimensionale Geokoordinaten gedacht, muss sie und die GeoJSON-Syntax etwas missbraucht werden. Anstatt einer WGS84-Koordinate emittieren wir einfach einen UNIX-Zeitstempel und lassen den anderen Grad leer:

function(doc)
{
        if(doc.begin && doc.end)
        {
                //start and end time as UNIX timestamps (seconds, not milliseconds)
                var begin =  Math.round(new Date(doc.begin).getTime()/1000);
                var end =  Math.round(new Date(doc.end).getTime()/1000);

                emit(
                        {
                                type: "Point",
                                bbox : [0,start,0,end]
                        }, null
                );
        }
}

So lassen sich nun effiziente Bereichsabfragen durchführen:
http://localhost:5984/entries/_design/entries/_spatial/entriesByPeriod?bbox=0,1280998800,0,1281000600

{"update_seq":16,"rows":[
{"id":"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de","bbox":[0,1280999512,0,1281000193],"value":null}
]}

Barrier points in Node.js

Node.js ist ein serverseitiges, hochskalierbares Framework für die Entwicklung von asynchronen Netzwerkanwendungen in JavaScript. Aufgrund der Asynchronität ist das Framework in der Lage, tausende Verbindung gleichzeitig offen zu halten und zu verarbeiten. Ein wesentliches Merkmal hier von ist, dass ausschließlich mit Callbacks gearbeitet wird, um auf Beendigungen von Operationen zu reagieren.

Im Normalfall führt das zu einem Chaining von Callback. Im folgenden Beispiel sollen zwei Dateien geöffnet werden und ihr kompletter Inhalt zurückgegeben werden:

var size = 0;

fs.readFile('/home/benjamin/file1', function(err, data)
{
	if (!err)
	{
		// first file read
		size += data.length;
		fs.readFile('/home/benjamin/file2', function(err, data)
		{
			if (!err)
			{
				// second file read
				size += data.length;
				fs.writeFile('/home/benjamin/size', size, function(err)
				{
					if (!err)
					{
							// both file read and content written to file
							sys.log('done');
						}
					});
			}
		});
	}
});

Das Problem hierbei ist, dass zwei Aktionen, die eigentlich parallel ablaufen könnten, nämlich das lesen beider Dateien, nacheinander ablaufen müssen. Für einen parallelen Ablauf ist eine zusätzliche Koordination notwendig, da die Resultate beider Aufrufe in Verbindung stehen. Hierfür habe ich mich an den CyclicBarrier Klassen von Java orientiert und eine einfache Klasse für die Koordination von asynchronen Callbacks geschrieben. Eine Barriere wird erzeugt unter Angabe der Anzahl von teilnehmenden Parties. Desweiteren können optional ein Callback für die Beendigung sowie ein optionaler Callback im Abbruchsfall registriert werden. Anschließend können die einzelnen Aufgaben der Parties angestoßen werden. Im Erfolgsfall müssen diese an der Barriere mit submit() anzeigen, dass sie beendet sind. Durch abort() kann auch ebenfalls ein Abbruch signalisiert werden. Eine Propagation der Ergebnisdaten lässt am besten durch eine externe Variable realisieren, auf den die Funktionen ebenfalls Zugriff haben.

Der Code des Beispielszenarios ändert sich dann wie folgt:

var size = 0;

var b = new Barrier(2, function()
{
	//Success callback
	fs.writeFile('/home/benjamin/size', size, function(err)
	{
		sys.log('done');
	});
}, function()
{
	//Aborted callback
	sys.log("aborted");
});

fs.readFile('/home/benjamin/file1', function(err, data)
{
	if (err)
	{
		b.abort();
	}
	else
	{
		size += data.length;
		b.submit();
	}
});

fs.readFile('/home/benjamin/file2', function(err, data)
{
	if (err)
	{
		b.abort();
	}
	else
	{
		size += data.length;
		b.submit();
	}
});

Wie im Vergleich zu erkennen ist, lässt sich die Ausführung deutlich beschleunigen. Ein paar Rahmenbedingungen gibt es jedoch. Zunächst dürfen die Teilaufgaben keine Abhängigkeiten untereinander besitzen. Dann darf ein Abbruch einer Teilaufgabe keine Auswirkung auf noch laufende Teilaufgaben besitzen, dessen Ergebnis später verworfen wird. Schließlich muss bei der Programmierung darauf geachtet werden, dass in jedem Fall eine Teilaufgabe mit submit() oder abort() terminiert, da ansonsten ein Lock entsteht.

Die Klasse ist relativ einfach:

/**
 * @class
 *
 * Creates a new barrier for the given amount of parties.
 * @param parties
 * @param barrierCallback
 * @param abortCallback
 * @return
 */
var Barrier =  function(parties, barrierCallback, abortCallback)
{
	this.parties = parties;
	this.barrierCallback = barrierCallback;
	this.abortCallback = abortCallback;

	this.running = true;
	this.count = 0;
};

/**
 * Signals a completion of one of the parties.
 * @return
 */
Barrier.prototype.submit = function()
{
	if (++this.count === this.parties && this.running)
	{
		this.barrierCallback();
	}
};

/**
 * Signals an abort by one of the parties. If not callback is passed, the default abort callback will be executed.
 * @param customAbortCallback Optional callback that should be executed due to the abort.
 * @return
 */
Barrier.prototype.abort = function(customAbortCallback)
{
	if (this.running && customAbortCallback)
	{
		customAbortCallback();
	}
	else if (this.running && this.abortCallback)
	{
		this.abortCallback();
	}
	this.running = false;
};

Klasse auf github: http://gist.github.com/464179

Kurzpräsentation – Node.js

Auf dem gestrigen Webmontag in Ulm habe ich Node.js vorgestellt, ein Framework für serverseitiges JavaScript für skalierbare Netzwerkanwendungen. Dabei hat es sich um eine eher kurze und oberflächliche Präesentation gehandelt, die die Grundidee des asynchroner I/O Operationen betonen sollte. Detailliertere Beiträge zu Node.js wird es aber hier in Kürze geben.

Die Geolocation API in Firefox 3.5

Seit Version 3.5 unterstützt der Webbrowser Mozilla Firefox die Geolocation API – eine API zur Unterstützung von location aware webservices, also Webseiten oder Dienste, die gezielt den Ort des Benutzers mit einbeziehen und sich entsprechend anpassen. Die implementierte Geolocation API basiert auf einem Draft des W3C.
Die Geolocation API ist in das Javscript-Browsermodell integriert worden und bietet nun geolocation als Kindobjekt von navigator an. Unterstütz der Browser die API nicht oder hat sie deaktiviert, so ist navigator.geolocation nicht verfügbar. Dies sollte aus Kompatibilitätsgründen immer als erstes getestet werden.
Ist das Objekt verfügbar, so kann mit getCurrentPosition() eine Abfrage der Koordinaten abgesetzt werden, die asynchron läuft. Als Parameter erwartet diese Methode eine Callback-Methode für die Verarbeitung des Ergebnisses und optional eine zweite Methode zur Fehlerbehandlung.
Eine Live-Demo mit einer Visualisierung mit OpenStreetMap findet man unter anderem auf 3liz.org/geolocation.

if (navigator.geolocation)
{
	navigator.geolocation.getCurrentPosition(function(position)
	{
		alert('Position:' + position.coords.latitude + ' / ' + position.coords.longitude);
	});
}
else
{
	alert("Geolocation API nicht verfügbar.");
}

Doch wie funktioniert diese Lokalisierung? Firefox nutzt hierfür die Google Location Services. Dieser Dienst versucht eine öffentliche IP anhand von verschiedenen Informationen zu einer ungefähren geografischen Position zu mappen, was häufig sehr ungenau ist. Ist jedoch WLan verfügbar, so benutzt dieser Dienst eine andere Strategie. Es wird eine Liste aller erreichbaren Access Points, deren MAC-Adresse sowie deren Signalstärke übermittelt. Diese Daten werden mit einer großen Datenbank abgeglichen und eine ungefähre Position trianguliert. Hierfür müssen zunächst die entsprechenden Daten in einer Datenbank gesammelt worden sein – man muss Straße für Straße mit allen erreichbaren WLan Access Points kartografieren. Bei unseren Tests stellte sich heraus, dass dies in Ulm und Neu-Ulm bereits passiert ist, die Ergebnisse waren durchweg gut und oft auf etwa 10 Meter genau!
Interessant wäre ein Blick in das Innere dieses Dienstes, ob er zum Beispiel selbstlernend ist und mit der Zeit neue Access Points anhand der übermittelten Datensätze einer genauen Position zuordnen und hinzufügen kann. Aber auch aus datenschutzrechtlichen Sicht sind Details solcher Dienste sehr interessant.

Wordpress Navigation & jQuery

Sobald es ein paar mehr Seiten werden, wird die Standard-Seitenanzeige von Wordpress suboptimal und inflexibel. Also brauchte ich eine Methode um nur die Eltern-Elemente der aktuellen Seite anzuzeigen und den Rest einzuklappen. Bei der Suche nach einer Methode bin ich auf Folding menu for WordPress gestossen. Allerdings fand ich die Animation bei jedem Klick etwas störend.

$(".page_item ul").hide();
$(".current_page_item").parents("ul").show();
$(".current_page_item ul:first").slideDown();

Dies ist meine Version des Code-Schnipsels und tut eigentlich genau das was ich wollte (und animiert nur, wenn man ein Menüpunkt mit Unterpunkten angeklickt hat). Aber da ja sowieso schon die gesamte Navigation vorhanden ist (nur nicht sichtbar), könnte man ja auch den Rest ausklappbar machen …

$(".page_item > ul:hidden").before( '<a href="#" class="menuToggle menuToggleOpen">v</a>');
$(".page_item > ul:visible").before('<a href="#" class="menuToggle menuToggleClose">^</a>');
a = function(){
    $(this).text("^").next("ul").slideDown();
};
b = function(){
    $(this).text("v").next("ul").slideUp();
};
$(".menuToggleClose").toggle(b,a);
$(".menuToggleOpen").toggle(a,b);

Hab dazu auch mal ein kleines Demo hier aufgesetzt.

Einziges Problem ist natürlich mal wieder der Internet Explorer 6, der SlideDown/-Up nicht sonderlich schön darstellt. Aber es ist benutzbar. Je nach erwarteter IE6-Dichte könnte man einen Switch einbauen, der dann im IE 6 die Animation nicht anzeigt (und nur show()/hide() nutzt).

Originalartikel: Wordpress Navigation & jQuery

ioexception.de

Benjamin Erb studiert seit 2006 Medieninformatik und interessiert sich insbesondere für Java, Web-Technologien, Ubiquitous Computing, Cloud Computing, verteilte Systeme und Informationsdesign.


Raimar Wagner studiert seit 2005 Informatik mit Anwendungsfach Medizin und interessiert sich für C++ stl, boost & Qt Programmierung, Scientific Visualization, Computer Vision und parallele Rechenkonzepte.


David Langer studiert seit 2006 Medieninformatik und interessiert sich für Web-Entwicklung, jQuery, Business Process Management und Java.


Sebastian Schimmel studiert seit 2006 Informatik mit Anwendungsfach Medizin und interessiert sich für hardwarenahe Aspekte, Robotik, webOs, C/C++ und UNIX/Linux.


Timo Müller studiert seit 2006 Medieninformatik. Er interessiert sich allen voran für Mobile and Ubiquitous Computing, systemnahe Enwticklung und verteilte Systeme, sowie Computer Vision.


Achim Strauß studiert seit 2006 Medieninformatik. Seine Interessen liegen in Themen der Mensch-Computer Interaktion sowie Webentwicklung und UNIX/Linux.


Tobias Schlecht studiert seit 2006 Medieninformatik und interessiert sich vor allem für Software Engineering, Model Driven Development, Model Driven Architecture, Requirements Engineering, Web-Technologien, UML2 und Java.


Fabian Groh studiert seit 2006 Medieninformatik. Seine Interessengebiete sind Computer Graphics, Computer Vision, Computational Photography sowie Ubiquitos Computing.

Archiv

September 2010
M D M D F S S
« Aug    
 12345
6789101112
13141516171819
20212223242526
27282930