Blog: Latest Entries (15):



Cypher - leicht und schnell - Teil 1

Die Abfragesprache für die Neo4J Graphendatenbank ist Cypher, die sich irgendwie eine Mischung aus SQL und JSON darstellt. Das Schöne an der Sprache ist, wie einfach sie zu lernen ist, weil die Struktur sehr klar ist und einfach alles so funktioniert wie man es sich denkt. Komplexe Konstrukte wie GROUP BY aus SQL gibt es nicht und die Aggregatsfunktionen fügen sich sehr viel angenehmer in alles
ein als bei SQL.

bbcode-image


Die Grundlegende Struktur einer Abfrage ist auch sehr logisch:


* MATCH
* WHERE
* RETURN
* ORDER BY
* LIMIT


Also ich sage welches Graphen-Gebilde ich such. Dann wird festgelegt welche Eigenschaften es erfüllen soll. Das geschieht anhand der Relations und Attribute der Komponenten, die man vorher fest gelegt hat (einfache statische vorgaben wie Ids kann man sogar schon vorher fest legen wie
z.B. " (e:example{id:1})").
Nun definiert man wie das ResultSet aussehen soll. Nodes, Relations, Attribute, Ergebnisse von Aggregatsfunktionen.. kann man beliebig mischen und auch eine CASE-Anweisung ist vorhanden. Dann Legt noch die Sortierung fest und möglicher Weise eine Limitierung des Resulsets.

Besonders schön sind Funktionen wie collect(), die einem Möglichkeiten bieten, die SQL einen einfach nicht nicht bieten kann.

Gehen wir mal von einer Datenbank mit Benutzern und Gruppen aus.

SQL:

SELECT g.id group_id,
u.id user_id,
u.name user_name
FROM groups g,
users u,
users_groups ug
WHERE ug.group_id = g.id
AND u.id = ug.user_id
ORDER BY g.id


Danach müssen wir durch eine Schleife laufen und immer wenn die Group-Id
sich ändert eine neue Liste für die User aufmachen und die solange füllen
bis die nächste Liste erzeugt wird. Die listen kommen dann in eine Map
wo die Group der Key für ihre Liste ist.

Jeder hat so etwas bestimmt schon mal gemacht, um keine einzelnen Queries für jede Group absetzen zu müssen. Ein großes Query ist schneller als viele kleine, weil weniger Overhead für die Connection-Verwaltung gebraucht wird und auch weniger Objekte erzeugt werden, was immer gut für die Performance
ist.

mit Neo4j geht es sehr viel einfacher.

Cypher:

MATCH (g.group)<-[m:member]-(u:user)
RETURN
g.id as group_id,
collect(u)
ORDER BY g.id


Hiermit erhält man eine Liste aller Groups mit 2 Werten. Der erste Wert ist die Id und der zweite ist die Liste der zur Group gehörigen User. Damit ist keine zusätzliche Iteration über die Ergebnismenge mehr nötig, um solch ein Konstrukt zu erzeugen.

Wenn man nun noch die Anzahl der User pro Group direkt haben möchte
muss man das Query nur minimal anpassen. Kein GROUP BY oder ähnliches.

Cypher:

MATCH (g.group)<-[m:member]-(u:user)
RETURN
g.id as group_id,
count(u) as cnt,
collect(u)
ORDER BY g.id


Komplizierter wird es mit Befehlen wie UNWIND oder FOREACH, aber im Vergleich zu entsprechenden SQL Lösungen sind diese auch noch sehr einfach und unkompliziert.

Ein Blick über den SQL-Tellerrand lohnt auf jeden Fall, wenn man Aufwand und Zeit sparen möchte bei Abfragen, die oft geschachtelte Listen nutzen und man es Leid ist, diese aus den Resultsets wieder zu rekonstruieren.

JavaFX in schön

Das man mit JavaFX schöne Anwendungen bauen kann steht außer Frage.

Hier werden einige gezeigt: https://jaxenter.de/javafx-anwendungen-aus-der-praxis-so-schoen-kann-ein-java-ui-sein-34877

Ich zweifel aber immer noch, dass der Aufwand nicht sehr viel höher ist als für eine entsprechende Webapp mit HTML5.

Aber ich habe zur Weihnachtszeit nochmal mit alten Kollegen gesprochen und es ist einfach so, dass wenn man Desktop-Anwendungen mit Java schreiben möchte, JavaFX das Beste ist was man momentan findet. SWT ist wirklich altbackend und Swing wird nicht mehr supportet. Wenn HTML5 keine Option ist ist JavaFX einfach die Zukunft für Java Anwendungen.

Remote Debugging mit PHPStorm

Heute habe ich mal wieder gelernt wie doof es ist, wenn man keine Kopie des produktiven Servers hat und die Entwicklungsumgebungen im Gegensatz zum Server immer mal wieder aktualisiert wurden. Der Server lief mit PHP 5.3 und alle anderen hatten 5.5 oder schon 5.6.

Es kam wie es kommen musste: Auf dem Server trat ein Fehler auf, der lokal bei niemanden reproduziert werden konnte. Wo der Fehler genau auftrat war unklar und nur Debuggen konnte da helfen. Zum Glück gab es einen Test-Server für die Anwender und Kunden den man verwenden konnte. Aber es man musst trotzdem Remote Debuggen.

Uns hat der Blog Post "Remote debugging with PHPStorm and Xdebug" von Marco Bunge da sehr geholfen.
Ich guck mal ob ich ähnliches auch noch mal für Eclipse mit PDT in den nächsten Wochen zu Stande bekomme.. leider sind es mal wieder zu viele Projekte gerade, wo von aber zum Glück zwei erledigt sind und ich mich ab Mitte März wieder meinen zwei Hauptprojekten widmen kann und auch wieder mehr Zeit für den Blog haben sollte.


MP4toGIF.com Filter Update

Nach dem ich den Tag mit der Sound API für Java verbracht habe, hab ich dann für heute noch mal eine Verbesserung für MP4toGIF.com in Angriff genommen. Neben den normalen Farbfiltern wird nun noch ein Vignitierungsfilter als Ergänzung zu den Farbfiltern hinzukommen.

bbcode-image


Mit dem Canvas sind solche Effekte sehr einfach umzusetzen und sind dabei noch relativ performant.


function createVignetting(ctx){
var x=Math.round(ctx.canvas.width/2);
var y=Math.round(ctx.canvas.height/2);
var grd=ctx.createRadialGradient(x,y,1,x,y,y-5);

grd.addColorStop(0,'rgba(0,0,0,0)');
grd.addColorStop(1,'rgba(0,0,0,0.8)');

ctx.fillStyle=grd;
ctx.fillRect(0,0,ctx.canvas.width,ctx.canvas.height);

return ctx;
}



CodeMirror für HTML-Code Liebhaber

In letzter Zeit habe ich CodeMirror lieben gelernt. Ich mag es nicht mit TinyMCE zu arbeiten und normale TextAreas sind für HTML-Coding wirklich schrecklich. Wenn man in einem System HTML-Templates bearbeitet, will man Einrückungen und Syntax-Highlighting.

Mit Code-Mirror hat man da und es ist wirklich leicht zu verwenden. Man hat eine Variable pro Editor und das einbringen von Code von Außen (z.B. das hinzufügen von Bilder-Links durch ein Klick auf ein Bild) ist sehr einfach.


//for codemirror
if(main){
main.replaceSelection(textToInsert);
}


Und es gibt sogar einen Mode für BBCode. Damit wird jetzt das Blog-Modul betrieben, wenn man sich gegen HTML mit TinyMCE entscheidet.

Wer also XML oder HTML Code in seiner Web-Anwendung bearbeiten muss oder seinen Benutzern BBCode anbietet kann das mit CodeMirror wirklich leicht und schnell einbauen.


<script src="/reachableContent/codemirror/lib/codemirror.js"></script>
<link rel="stylesheet" href="/reachableContent/codemirror/lib/codemirror.css">
<script src="/reachableContent/codemirror/mode/xml/xml.js"></script>
<script src="/reachableContent/codemirror/mode/css/css.js"></script>
<script src="/reachableContent/codemirror/mode/javascript/javascript.js"></script>
<script src="/reachableContent/codemirror/mode/htmlmixed/htmlmixed.js"></script>

...

<script type="text/javascript">
var myTextArea=document.getElementById("idTextArea");
var myCodeMirror = CodeMirror.fromTextArea(myTextArea,{tabSize: 4,
indentUnit: 4,
indentWithTabs: true,
lineNumbers: true});
</script>


bbcode-image
Auch mit BBCode

Firefox Marketplace: It has begun...

Heute kam dann auch eine Email, dass eine App von mir deaktiviert wurde. Es geht also voran den Marketplace zurück zu bauen.

Firefox OS und die OpenWebApp waren eine tolle Idee und ich habe gerne dafür entwickelt. Die Möglichkeit Web-Apps wie Desktopanwendungen zu installieren war echt super und gerade für Firmen toll, da die Mitarbeiter einfach ein Icon anklicken mussten und nicht mal merkten, dass es gerade eine Webanwendung war, die sich dann öffnete.


bbcode-image

Eine Stunde früher Aufstehen für eigene Projekte

Jeder der ein oder mehrere Projekte nebenbei laufen hat, die einem Geld bringen oder einem wichtig sind, wird das Problem kennen, dass man gerne mehr Zeit in das Projekt investieren möchte. Oft kommt so etwas in Schüben, dass man plötzlich ganz viele Ideen hat und dann gerade der Job oder das Privatleben oder meistens sogar beides einen auch mehr Zeit abverlangen, als sonst üblich. Es kommt eben immer alles auf einmal.

Wo bekommt man dann die Zeit her ohne die Zeit für das andere zu beeinträchtigen? Ich habe von John Sonmez den Tipp gelesen,
einfach eine Stunde früher aufzustehen. Klingt erst einmal gut. Man hat Ruhe weil der Rest noch schläft, opfert keine Zeit die man sonst mit anderen verbracht hätte und kann das wach zur Arbeit aufbrechen. Soweit die Theorie. Ich hab es mal versucht.. es klappt nicht und hier kommt wieder etwas in Spiel, was in der heutigen Zeit leider zu oft vergessen wird: Schlaf ist wichtig. Guter gesunder und ausreichender Schlaf.
Wenn man zu wenig schläft leidet am Ende alles mehr darunter als wenn man die zusätzliche mit mit schlafen verbracht hätte. Was bringt einen die Stunde mehr am Tag, wenn dafür die Leistungsfähig abnimmt. Es ist ja nicht nur die Stunde mehr, wo de Leistungsfähigkeit fehlt sondern den ganzen Tag über. Deswegen kann es sein, dass man durch die Stunde mehr Zeit 1-2 produktive Stunden durch den Schlafmangel wieder verliert.
Ich behaupte, mit guten Schlaf kann mit einer Stunde weniger dennoch mehr an seinem Projekt in der übrigen Zeit schafen, als wenn man diese Stunde für sein Projekt genommen hätte.

bbcode-image

Windows 10 Phone Pin,Iris jedes mal verwenden

Erstmal wundert man sich, dass man Iris verwenden kann um sein Lumia 950 zu entsperren aber auch einfach so den Lockscreen hoch schieben kann. Beim Lumia 640 mit Pin ist es das Selbe.
Unter den Einstellungen unter Konto - Anmeldeoptionen muss man die 15 Minuten auf Jedes mal stellen.

Es scheint eine Konfort-Funktion zu sein, dass man erst nach 15 Minuten Inaktivität sich wichtig anmelden muss. Wohl damit man nicht immer sich da neu identifizieren muss, bloß weil man mal kurz das Smartphone aus der Hand gelegt hat.

Macht es aber auch unsicher, weil unerlaubte Zugriffe wohl häufig schnell zwischen durch passieren und 15min schon sehr lang sind, wenn man immer mal auf sein Smartphone guckt.

Das hier hat sehr geholfen, das Problem zu lösen.

Die Iris-Funktions funktioniert erstaunlich gut und zuverlässig.Ich werde noch mal mit Foto auf einem Bildschirm testen, aber bei realen Menschen war die Funktion bis jetzt 100%ig zuverlässig.

Einarbeitung in schon bekannte Programme

Wenn man die Überschrift liest, klingt es erst einmal seltsam, da man das Programme und Anwendungen ja schon kennt. Das Problem bei komplexen Anwendungen, die in Projekten und der alltäglichen Arbeit verwendet werden, um alles zu managen, dass nicht die eine Art gibt, wie man die verwendet.

Gerade bei sehr kleinen Teams von 2-3 Personen, die eine solche Anwendung einführen und benutzen, entstehen schnell Strukturen und Abläufe, die sich stark an der Arbeitweise und dem Projekt orientieren. Wenn man nun als neue Person hinzukommt und es bekannt ist, dass man mit einer Anwendung schon gearbeitet hat, ist das Erstaunen oft groß, dass man nicht gleich zu 100% in dem System steckt und auch mal nachfragen muss, wie etwas gehandhabt wird.
Ob es nun das behanedeln voon Tickets in Youtrack oder JIRA ist oder auch das Management von Branches in GIT. Es gibt nicht die eine Art und gerade in kleinen Teams, wo es keine Schnittstellen zu anderen Teams und Einblicke in deren Arbeitweisen gibt, entwickelt sich irgendwie ganz schnell die Ansicht, dass man die richtige Arbeitweise erkannt hätte und alle anderne die produkttiv mit der Anwendung arbeiten, zur gleichen Arbeitsweisse gekommen sein müssen.

Deswegen muss man sich immer im klaren sein dass sich das Einarbeiten nie nur auf die grundlegende Bedienung von Anwendungen und dem Kennenlernen von Code beschränkt, sondern viel mehr das Verstehen der Strukuren, Workflows und Ansichten bzw Dennkweisen der Teams bezieht. Wenn man plötzlich mit einer anderen Programmiersprache arbeiten soll ist auch der Syntax das gerinigste Problem und wenn man deren Konzepte verstanden hat, kommt der Syntax fast von allein hinterher.
Man darf also nicht erwarten, dass bloss weil jemand mit einer Anwendung gearbeitet hat, auch genauso damit gearbeitet hat wie man selbst. Das bietet natürlich auch die Gelegenheit mal über den Tellerrand zu gucken und seine eigenen Abläufe noch mal neu zu überdenken und zu optimieren.

Sepia-Filter für MP4toGIF.com

Nachdem längere Zeit bei MP4toGIF.com nichts mehr passiert ist, habe ich heute mich noch mal mit neuen Filtern auseinander gesetzt und mir eine Umgebung zusammen gebaut, in der ich mit neuen Filtern experimentieren kann. Es ist schon interessant, wie man mit dem Ändern weniger Parameter sehr verschiedene Effekte erzielen kann.

bbcode-image


bbcode-image


Die Test-Umgebung findet man unter http://www.annonyme.de/js/filters/. Da kann jeder gerne mal herum experimentieren und versuchen selbst Einstellungen für einen tollen Effekt zu finden.

In den nächsten Tagen und Wochen, werden dann also wohl noch ein paar Filter mehr für MP4toGIF.com entstehen und dort integriert werden.

Die zentrale Render-Function ist sehr einfach aufgebaut:

function render(src, trg, rm, ra, gm, ga, bm, ba, useGreyScale){
var srcData=src.getImageData(0,0,src.canvas.width,src.canvas.height);
var data=srcData.data;
for(var i=0;i<data.length;i+=4){
if(useGreyScale){
var avg = (data[i+0]*rm) + (data[i+1]*gm) + (data[i+2]*bm);
data[i+0]=avg;
data[i+1]=avg;
data[i+2]=avg;
}
else{
data[i+0]=data[i+0]*rm;
data[i+1]=data[i+1]*gm;
data[i+2]=data[i+2]*bm;
}

data[i+0]=Math.abs(data[i+0]+parseInt(ra));
data[i+1]=Math.abs(data[i+1]+parseInt(ga));
data[i+2]=Math.abs(data[i+2]+parseInt(ba));
}
trg.canvas.width=src.canvas.width;
trg.canvas.height=src.canvas.height;
trg.putImageData(srcData,0,0);
}


Invertieren des Bildes ist zum Beispiel: Multiplicator auf 1 und darauf -255 addieren.

Komplexe Probleme und komplexer Code

Komplexe Probleme führen zu komplexen Code. Einfache Probleme führen zu einfachen Code. Das sind die Gesetzmässigkeiten, die immer gelten. Sollte mal ein einfaches Problem zu komplexen Code führen, muss man den einfach wegwerfen und noch mal von Vorne anfangen, weil man irgendwas falsch gemacht hat. Das passiert, aber man erkennt den Fehler schnell und kann ihn korrigieren.

Schwierig wird es wenn ein Problem ein komplex ist. Komplexität ist sowie so ein Problem für sich. Jeder der schon mal Planning Poker gespielt hat, weiß wie schlecht sich Komplexität erkennen und bewerten läßt. Ich habe für mich eine kleine Staffelung erstellt, nach der ich mich richte. Der Knackpunkt bei der Bewertung sind Schnittstellen. Egal ob es andere Systeme sind oder andere Menschen.. sobald Schnittstellen da sind hat man mehr Komplexität und Overhead. Jeder muss das Problem verstehen, seine Aufgaben ausführen und Zeiten einhalten. Wenn das nicht klappt ist es oft auch nicht mal das Problem der anderen Menschen oder Systeme. Fehlerhafte Dokumentation und unklare oder unteschiedlich verwende Begriffe sind fast immer am Anfang dabei und müssen ereinmal geklärt werden.
Deswegen ist zum Beispiel Dokumentation für mich eine hoch komplexe Sache, weil dort immer gleich 2 Schnittstellen existieren. Einmal müssen die Infos rein und dann wieder verständlich für andere raus. Jeder der mal eine 70 Seiten Dokumentation über eine System für andere Entwickler geschrieben hat, wird mir hoffetnlich beipflichten können.

Aber nun zur Tabelle:

* 0: nichts zu tun, da schon erledigt oder obsolete
* 1/2: es müssen Texte oder andere statische Werte geändert werden
* 1: es muss vorhandere Code in der Logik minimal angepasst werden (if-Bedinungen anpassen oder Try-Catch Blöcke setzen)
* 2: Es muss in einer vorhanden Struktur Code erzeugt werden (eine Methode hinzufügen oder Hilfs-Klasse erstellen)
* 3: Es werden Daten verarbeitet (Einfacher Importer oder Ein- und Ausgaben)
* 5: Daten werden um gewandelt oder einfache einfache Schnittstellen entgegen genommen oder ausgegeben (Importer wo die Daten aufbereitet werden müssen)
* 8: Es wird Logik erstelle (Berechnungen und Daten operationen, einfache Planung ohne große Konzeption)
* 13: Logik mit Schnittstellen, wo die andere Seite beeinflusst werden kann, z.B. Fehler wirft, wenn fehlerhafte Daten geliefert werden (Man muss Planen und die Konzeption muss besprochen werden)
* 20: Die Schnittstellen oder Funktionsweisen sind noch zu erarbeiten (Die Realisierung ist machbar, aber es ist noch nicht klar, wie man zum Ziel kommt)
* 40: Man weiß, was man will, aber die Realiserung benötigt Forschung (Viele Meetings und es gibt Ansätze wobei diese auch sich noch als nciht zielführend erweisen können)
* 100: Die Realiserbarkeit kann angenommen werden, aber nicht mit bekannten Mitteln (man muss also richtige Grundlagenforschung betreiben und sich viel Rat einholen)
* unendlich: Man geht von einer Realiserbarkeit aus, aber weiß, dass bis jetzt niemanden gelungen ist so etwas umzusetzen.... z.B. wenn man vorhat einen Fusionsreaktor zu bauen

Da komplexe Probleme zu komplexen Code führen und für diesen gilt, dass er schwer zu warten, lesen und zu erweitern oder ändern ist, muss man den Code vereinfachen. Wer mal versucht hat ohne Neibewertung des Problems von gleichen Ausgangslagen den Code zu verbessern wird gelernt haben, dass man nur wieder anderen komplexen Code geschrieben hat. Man kann Codenicht vereinfachen ohne die Komplexität des Problems runter zu brechen. Das sollte man tun. Probleme in kleinere und einfachere Probleme aufbrechen. Lieber eine Woche länger das Problem analysieren, als Code schreiben der andere und einen selber später noch viel mehr Zeit kosten wird.

bbcode-image


Deswegen.. nie versuchen den Code zu vereinfachen sondern immer das Problem vereinfachen und dann den Code anpassen oder besser noch einfach auch neuschreiben.

Neues Projekt mit aoop

Nach 9 Jahren geht es mit aoop doch mal los. Ein weiters Projekt mit meinem Framework startet morgen. Es basiert auf der neusten Version mit dem Blog-Modul.
Ich bin auch technischer Admin und betreue alles auf technischer Ebene. Logos und Design stammen von meiner Frau und sind wirklich toll. Wer es sich mal ansehen möchte findet es unter http://www.tentacle-news.net.
bbcode-image

Neo4J: CSV Import aus MySQL

bbcode-image


Oft werden NoSQL für sehr spezielle Fälle eingesetzt. Die normale Datenhaltung bleibt weiter hin den SQL-Datenbanken überlassen. Also müssen regelmäßig die Daten aus dem SQL-Bestand in die NoSQL Datenbank kopiert werden. Das dauert oft und viele aufbereitungen der Daten wird schon hier erledigt. die NoSQL Varianten sind deswegen auch oft schneller, weil man eine Teil der Arbeit in den Import-Jobs erledigt, die sonst bei jedem Query als Overhead entstehen. Natürlich haben die NoSQL auch ohne das ihre Vorteile, aber man sollte immer im Auge behalten, ob die Performance von der Engine kommt oder auch von der Optimierung der Daten, weil die Optimierungen der Daten könnte man auch in die SQL-Struktur zurück fließen lassen und diese in die Richtung hin verbessern.

So ein Import dauert... wenn man in der Nacht ein Zeitfenster von einer Stunde hat, ist alles kein Problem. Will man aber auch in kurzen Abständen importieren, muss der Import schnell laufen. Auch wenn man als Entwickler öfters mal den Import braucht, ist es wichtig möglichst viel Performance zu haben.
Hier geht es darum wie man möglichst schnell und einfach Daten aus einer MySQL Datenbank in eine Neo4j Graphen-Datenbank importieren kann, ohne viel Overhead zu erzeugen. Ich verwende hier PHP, aber da an sich keine Logik in PHP implementiert werden wird, kann man ganz leicht auf jeden andere Sprache, wie Java, JavaScript mit node.js und so übertragen. Es werden keine ORMs verwendet (die extrem viel Overhead erzeugen und viel Performance kosten) sondern nur SQL und Cypher.

Wie man einfach sich eine oder mehrere Neo4J-Instanzen anlegt (unter Linux) kann man hier sehr gut sehen:


Wir verwenden bei Neo4j den Import über eine CSV-Datei. Wir werden also nicht jeden Datensatz einzeln Lesen und Schreiben, sondern immer sehr viele auf einmal. Ob man alles in einer Transaktion laufen lässt und erst am Ende commited hängt etwas von der Datenmenge ab. Bis 200.000 Nodes und Relations ist alles kein Problem.. bei Millionen von Datensätzen sollte man aber nochmal drüber nachdenken.
PERIODIC COMMIT ist da eine super Lösung, um alles automatisch laufen zu lassen und sich nicht selbst darum kümmern zu müssen, wann commited wird. Alle 1000 bis 10_000 Datensätze ein Commit sollte gut sein, wobei ich eher zu 10_000 raten würde, weil 1000 doch noch sehr viele Commits sind und so mit der Overhead noch relativ groß ist.

Unsere Beispiel Datenbank sieht so aus:


CREATE TABLE USERS(
USER_ID INT(11) UNSGINED NOT NULL,
USER_NAME VARCHAR(255) NOT NULL,
PRIMARY KEY (USER_ID)
);

CREATE TABLE MESSAGES(
MESSAGE_ID INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
MESSAGE_TITLE VARCHAR(255) NOT NULL,
FROM_ID INT(11) UNSIGNED NOT NULL,
TO_ID INT(11) UNSIGNED NOT NULL,
CC_ID INT(11) UNSIGNED NOT NULL,
PRIMARY KEY (MESSAGE_ID)
);


Wir legen uns 50.000 User an dann noch 100.000 Messages mit jeweils einen FROM, einem TO und einem CC (hier hätte man über eine Link-Table sollen, aber das hier ist nur ein kleines Beispiel, wo das so reicht). Das sollten erst einmal genug Daten sein. (Offtopic: da ich das gerade neben bei auch in PHP schreibe.. warum kann ich für eine 100000 nicht wie in Java 100_000 schreiben?)

Die erste Schwierigkeit ist es die Daten schnell zu exportieren. Ziel ist eine CSV. Wir könnten entweder über PHP die Daten lesen und in eine Datei schreiben oder aber einfach die OUTFILE-Funktion von MySQL nutzen, um die Datenbank diese Arbeit erledigen zu lassen. Wir werden es so machen und erstellen für jede Art von Nodes und Relations eine eigene CSV. Weil wir Header haben wollen fügen wir diese mit UNION einmal oben hinzu


$sql="
SELECT 'user_id', 'user_name'
UNION
SELECT USER_ID,USERNAME
FROM USERS
INTO OUTFILE ".$exchangeFolder."/users.csv
FIELDS TERMINATED BY ','
ENCLOSED BY ''
LINES TERMINATED BY '\n'
";


Damit schreibt MySQL das Ergebnis des Queries in die angegebene Datei. Falls ein Fehler auftritt, muss man gucken, ob der Benutzer unter dem die MySQL-DB läuft in das Verzeichnis schreiben darf und ob nicht eine Anwendung wie apparmor unter Linux nicht den Zugriff blockiert. Es darf keine Datei mit diesen Namen schon vorhanden sein, sonst liefert MySQL auch nur einen Fehler zurück. Wir müssen
die Dateien also vorher löschen und dass machen wir einfach über PHP. Also muss auch der Benutzer unter dem die PHP-Anwendung läuft entsprechende Rechte haben.
Man kann das gut einmal direkt mit phpmyadmin oder einem entsprechenden Programm wie der MySQL Workbench testen. Wenn die Datei erzeugt und befüllt wird ist alles richtig eingestellt.

Mit dem Erstellen der CSV-Datei ist schon mal die Hälfte geschafft. Damit der Import auch schnell geht brauchen wir einen Index für unsere Nodes. Man kann einen Index schon anlegen, wenn noch gar kein Node des Types erstellt wurde. Zum Importieren der User benutzen wir folgendes Cypher-Statement:


$cyp="USING PERIODIC COMMIT 10000
LOAD CSV WITH HEADERS FROM "file:///".$exchangeFolder."/messages.csv" AS row
MERGE (m:message{mid:row.msg_id,title:row.msg_title});";


Der Pfad zur Datei wird als File-URL angegeben. Hier merkt man auch Neo4J seine Java-Basis an. Wenn man mal in eine Temp-Verzeichnis schaut sieht man dort auch Spuren von Jetty.

Am Ende wird der Importer nur eine Reihe von SQL und Cypher Statements ausführen. Wir benötigen um komfortabel zu arbeiten 3 Hilfsmethoden. Dass alles in richtige Klassen zu verpacken wäre natürlich besser, aber es reicht zum erklären erst einmal ein Funktionsbasierter Ansatz.

Da MySQL keine Dateien überschreiben will, brauchen wir eine Funktion zum Aufräumen des Verzeichnisses über das die CSV-Dateien ausgetauscht werden. Wir räumen einmal davor und einmal danach auf. Dann ist es kein Problem den Importer beim Testen mal mittendrin zu stoppen oder wenn er mal doch mit einem Fehler abbricht.


function cleanFolder($folder){
$files=scandir($folder);
foreach($files as $file){
if(preg_match("/\.csv$/i", $file)){
unlink($folder."/".$file);
}
}
}


Für SQL wird wieder mein PDBC verwendet.


include_once("PDBC/PDBCDBFactory.php");
PDBCDBFactory::init("PDBC/dbclasses/","PDBC/conffiles/");
$db=PDBCCache::getInstance()->getDB("embdoop");


Für Neo4J bauen wir uns eine eigen kleine Funktion.


use Everyman\Neo4j\Client;
use Everyman\Neo4j\Cypher\Query;

$client = new Everyman\Neo4j\Client();
$client->getTransport()->setAuth("neo4j","blubb");

function executeCypher($query){
global $client;
$query=new Query($client, $query);
$query->getResultSet();
}


Der Rest ist nun sehr einfach und linear. Ich glaube ich muss da nicht viel erklären und jeder Erkennt sehr schnell wie alles abläuft. Interessant ist wohl das Cypher-Statement für die Receive-Relations, da neben der Relation diese auch mit einem Attribute versehen wird im SET Bereich.


//clear for export (if a previous import failed)
cleanFolder($exchangeFolder);

//export nodes
echo "create users.csv\n";
$sql=" SELECT 'user_id', 'user_name' UNION
SELECT USER_ID,USER_NAME
FROM USERS
INTO OUTFILE '".$exchangeFolder."/users.csv'
FIELDS TERMINATED BY ','
ENCLOSED BY ''
LINES TERMINATED BY '\n'";
$db->execute($sql);

echo "create messages.csv\n";
$sql=" SELECT 'msg_id', 'msg_title' UNION
SELECT MESSAGE_ID, MESSAGE_TITLE
FROM MESSAGES
INTO OUTFILE '".$exchangeFolder."/messages.csv'
FIELDS TERMINATED BY ','
ENCLOSED BY ''
LINES TERMINATED BY '\n'";
$db->execute($sql);

//export relations
echo "create relations_etc.csv\n";
$sql=" SELECT 'user_id', 'msg_id', 'type' UNION
SELECT TO_ID, MESSAGE_ID, 'TO'
FROM MESSAGES
UNION
SELECT CC_ID, MESSAGE_ID, 'CC'
FROM MESSAGES

INTO OUTFILE '".$exchangeFolder."/relations_etc.csv'
FIELDS TERMINATED BY ','
ENCLOSED BY ''
LINES TERMINATED BY '\n'";
$db->execute($sql);

echo "create relations_from.csv\n";
$sql=" SELECT 'user_id', 'msg_id', 'type' UNION
SELECT FROM_ID, MESSAGE_ID, 'FROM'
FROM MESSAGES

INTO OUTFILE '".$exchangeFolder."/relations_from.csv'
FIELDS TERMINATED BY ','
ENCLOSED BY ''
LINES TERMINATED BY '\n'";
$db->execute($sql);

//create indexes for fast import
echo "create index's in neo4j\n";
$cyp="CREATE INDEX ON :user(uid);";
executeCypher($cyp);
$cyp="CREATE INDEX ON :message(mid);";
executeCypher($cyp);

//import nodes
echo "import users.csv\n";
$cyp="USING PERIODIC COMMIT 10000\n
LOAD CSV WITH HEADERS FROM "file:///".$exchangeFolder."/users.csv" AS row\n
MERGE (u:user{uid:row.user_id,name:row.user_name});";
executeCypher($cyp);

echo "import messages.csv\n";
$cyp="USING PERIODIC COMMIT 10000
LOAD CSV WITH HEADERS FROM "file:///".$exchangeFolder."/messages.csv" AS row
MERGE (m:message{mid:row.msg_id,title:row.msg_title});";
executeCypher($cyp);

//import relations
echo "import relations_from.csv\n";
$cyp="USING PERIODIC COMMIT 10000
LOAD CSV WITH HEADERS FROM "file:///".$exchangeFolder."/relations_from.csv" AS row
MATCH(u:user{uid:row.user_id})
MATCH(m:message{mid:row.msg_id})
MERGE (u)-[r:send]->(m);";
executeCypher($cyp);

echo "import relations_etc.csv\n";
$cyp="USING PERIODIC COMMIT 10000
LOAD CSV WITH HEADERS FROM "file:///".$exchangeFolder."/relations_etc.csv" AS row
MATCH(u:user{uid:row.user_id})
MATCH(m:message{mid:row.msg_id})
MERGE (m)-[r:receive]->(u)
SET r.type=row.type;";
executeCypher($cyp);

//clear after import
cleanFolder($exchangeFolder);


Hier sieht man wie der Importer die 50.000 User, 100.000 Messages und insgesamt 300.000 Relations von einer MySQL in die Neo4J Instanz importiert.



Die Festplatte ist nur über SATA-2 Angeschlossen und nicht besonders schnell. Eine SSD, wie für Neo4J empfohlen, würde alles sehr beschleunigen.

Zum Löschen aller Daten aus der Neo4J kann man diese Statement verwenden:


MATCH (n)
DETACH DELETE n

Older posts:

Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?